计网实验一

Uncategorized
32k words

注:以下为本人实验报告原文,切勿直接copy
实验报告采用C++
代码分享项目:https://github.com/buyun14/PoCN
(与blog使用代码多数不同)

Socket接口

1. 实验目的

  • 了解网络进程间通信的基本原理。
  • 掌握Client/Server模式的工作机制。
  • 学习Socket编程接口及其在TCP与UDP协议下的应用。

2. 实验环境

  • 操作系统:Windows/Linux
  • 编程语言:C/C++/python/Java/node.js/Go等等
  • 开发工具:[如Visual Studio 2022,pycharm,VS Code, GCC等]

3. 实验原理

3.1 网络进程间通信

  • 描述网络进程间通信的基本概念。
  • 介绍进程标识的方法:在同一台主机上通过进程ID标识,网络上通过(主机IP地址,端口号)标识。

3.2 Client/Server模式


  • 描述Client/Server模式的基本工作流程。

Client/Server (C/S) 模式是一种常见的网络通信模型,其中客户端(Client)和服务端(Server)通过网络进行交互。客户端主动发起服务请求,服务端被动等待并响应这些请求。以下是C/S模式的基本工作流程:

1. 服务端初始化
  1. 创建套接字(Socket)

    • 服务端首先调用 socket() 函数创建一个套接字,用于监听客户端的连接请求。
    • 例如:SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
  2. 绑定地址信息(Bind)

    • 使用 bind() 函数将套接字绑定到特定的本地地址和端口。
    • 例如:bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    • 其中,serverAddr 是一个包含本地IP地址和端口号的 sockaddr_in 结构体。
  3. 监听连接请求(Listen)

    • 调用 listen() 函数使套接字进入监听状态,等待客户端的连接请求。
    • 例如:listen(serverSocket, backlog);
    • backlog 参数指定了连接请求队列的最大长度。
  4. 接受连接请求(Accept)

    • 使用 accept() 函数接受客户端的连接请求,建立一个新的套接字用于与客户端通信。
    • 例如:SOCKET clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddr, &clientAddrLen);
    • clientSocket 是与特定客户端通信的新套接字,clientAddr 包含客户端的地址信息。
2. 客户端初始化
  1. 创建套接字(Socket)

    • 客户端调用 socket() 函数创建一个套接字,用于与服务端进行通信。
    • 例如:SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
  2. 连接服务端(Connect)

    • 使用 connect() 函数向服务端发起连接请求。
    • 例如:connect(clientSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    • serverAddr 是一个包含服务端IP地址和端口号的 sockaddr_in 结构体。
3. 数据传输
  1. 客户端发送数据

    • 客户端使用 send() 函数向服务端发送数据。
    • 例如:send(clientSocket, sendData, sendDataSize, 0);
  2. 服务端接收数据

    • 服务端使用 recv() 函数接收客户端发送的数据。
    • 例如:recv(clientSocket, receiveData, receiveDataSize, 0);
  3. 服务端发送数据

    • 服务端使用 send() 函数向客户端发送数据作为响应。
    • 例如:send(clientSocket, responseData, responseSize, 0);
  4. 客户端接收数据

    • 客户端使用 recv() 函数接收服务端发送的数据。
    • 例如:recv(clientSocket, receiveResponse, receiveResponseSize, 0);
4. 断开连接
  1. 关闭套接字(Close)
    • 客户端和服务端在通信结束后,分别调用 closesocket() 函数关闭套接字,释放资源。
    • 例如:closesocket(clientSocket);closesocket(serverSocket);

  • 解释TCP和UDP协议的区别,特别是在建立连接、数据传输可靠性和系统资源需求方面的差异。
    TCP(传输控制协议)和UDP(用户数据报协议)都是Internet协议套件的一部分,用于在网络中传输数据。它们在设计上有着根本的不同,适用于不同类型的网络通信需求。以下是TCP与UDP在建立连接、数据传输可靠性以及系统资源需求方面的主要区别:
建立连接
  • TCP 是一种面向连接的协议。在两个设备之间开始数据交换之前,必须先建立一个可靠的连接。这个过程通常涉及到三次握手(SYN, SYN-ACK, ACK),确保双方都准备好接收数据。这种机制保证了数据传输前的连接可靠性。

  • UDP 是一种无连接的协议。它不需要在发送数据包之前建立连接。这意味着发送方可以立即开始发送数据,而无需等待接收方的确认或准备状态。这使得UDP具有更低的延迟,但同时也缺乏TCP的连接可靠性。

数据传输可靠性
  • TCP 提供了高度的数据传输可靠性。它通过序列号、确认应答(ACK)、重传丢失的数据包等机制来确保数据的完整性和顺序。如果数据包在网络中丢失,TCP会自动请求重新发送这些数据包。此外,TCP还提供了流量控制和拥塞控制功能,以避免网络过载。

  • UDP 不提供数据传输的可靠性保证。它不会检查数据包是否成功到达目的地,也不会重传丢失的数据包。因此,使用UDP时可能会出现数据包丢失或乱序的情况。然而,对于某些应用来说,如在线视频直播或语音通话,即使有轻微的数据丢失,也能接受,因为这些应用更关注实时性而非绝对的可靠性。

系统资源需求
  • TCP 因为其连接建立、数据确认、重传机制以及流控和拥塞控制等功能,通常需要更多的系统资源(例如内存和CPU)。每个TCP连接都会占用一定的内核资源,当大量并发连接存在时,可能会对服务器性能产生影响。

  • UDP 相对而言对系统资源的需求较低。由于UDP没有复杂的握手过程和数据确认机制,它的开销较小。这对于需要处理大量简单请求的应用(如DNS查询)来说是一个优点。

总结来说,TCP更适合那些需要高可靠性的应用,如文件传输、网页浏览等;而UDP则适用于对实时性要求较高且能容忍一定数据丢失的应用,比如在线游戏、视频会议等。选择哪种协议取决于具体应用场景的需求。


3.3 Socket编程接口

  • 介绍Socket编程接口的主要API函数,包括socket(), bind(), listen(), accept(), connect(), send(), recv(), sendto(), recvfrom(), closesocket()等。
Socket编程接口的主要API函数

Socket编程接口提供了一系列函数,用于在网络中进行进程间的通信。以下是这些主要API函数的详细介绍:

1. socket()
  • 功能:创建一个套接字。
  • 原型SOCKET socket(int af, int type, int protocol);
  • 参数
    • af:地址族,通常为 AF_INET(IPv4)或 AF_INET6(IPv6)。
    • type:套接字类型,常见类型有 SOCK_STREAM(面向连接的TCP)和 SOCK_DGRAM(无连接的UDP)。
    • protocol:协议类型,通常为 IPPROTO_TCPIPPROTO_UDP
  • 返回值:成功返回一个套接字描述符(非负整数),失败返回 INVALID_SOCKET
2. bind()
  • 功能:将套接字绑定到本地地址和端口。
  • 原型int bind(SOCKET s, const struct sockaddr *name, int namelen);
  • 参数
    • s:要绑定的套接字描述符。
    • name:指向包含本地地址和端口的 sockaddr 结构体的指针。
    • namelensockaddr 结构体的大小。
  • 返回值:成功返回0,失败返回 SOCKET_ERROR
3. listen()
  • 功能:将套接字设置为监听状态,准备接收连接请求。
  • 原型int listen(SOCKET s, int backlog);
  • 参数
    • s:要监听的套接字描述符。
    • backlog:连接请求队列的最大长度。
  • 返回值:成功返回0,失败返回 SOCKET_ERROR
4. accept()
  • 功能:接受一个连接请求,创建一个新的套接字用于与客户端通信。
  • 原型SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen);
  • 参数
    • s:监听套接字描述符。
    • addr:指向存储客户端地址信息的 sockaddr 结构体的指针。
    • addrlensockaddr 结构体的大小。
  • 返回值:成功返回一个新的套接字描述符,失败返回 INVALID_SOCKET
5. connect()
  • 功能:向服务端发起连接请求。
  • 原型int connect(SOCKET s, const struct sockaddr *name, int namelen);
  • 参数
    • s:要连接的套接字描述符。
    • name:指向包含服务端地址和端口的 sockaddr 结构体的指针。
    • namelensockaddr 结构体的大小。
  • 返回值:成功返回0,失败返回 SOCKET_ERROR
6. send()
  • 功能:向指定的套接字发送数据。
  • 原型int send(SOCKET s, const char *buf, int len, int flags);
  • 参数
    • s:要发送数据的套接字描述符。
    • buf:指向要发送的数据缓冲区的指针。
    • len:要发送的数据长度。
    • flags:发送标志,通常为0。
  • 返回值:成功返回实际发送的字节数,失败返回 SOCKET_ERROR
7. recv()
  • 功能:从指定的套接字接收数据。
  • 原型int recv(SOCKET s, char *buf, int len, int flags);
  • 参数
    • s:要接收数据的套接字描述符。
    • buf:指向接收数据缓冲区的指针。
    • len:缓冲区的大小。
    • flags:接收标志,通常为0。
  • 返回值:成功返回实际接收到的字节数,失败返回 SOCKET_ERROR,如果连接被关闭则返回0。
8. sendto()
  • 功能:向指定的地址发送数据(用于UDP)。
  • 原型int sendto(SOCKET s, const char *buf, int len, int flags, const struct sockaddr *to, int tolen);
  • 参数
    • s:要发送数据的套接字描述符。
    • buf:指向要发送的数据缓冲区的指针。
    • len:要发送的数据长度。
    • flags:发送标志,通常为0。
    • to:指向目标地址的 sockaddr 结构体的指针。
    • tolensockaddr 结构体的大小。
  • 返回值:成功返回实际发送的字节数,失败返回 SOCKET_ERROR
9. recvfrom()
  • 功能:从指定的地址接收数据(用于UDP)。
  • 原型int recvfrom(SOCKET s, char *buf, int len, int flags, struct sockaddr *from, int *fromlen);
  • 参数
    • s:要接收数据的套接字描述符。
    • buf:指向接收数据缓冲区的指针。
    • len:缓冲区的大小。
    • flags:接收标志,通常为0。
    • from:指向存储发送方地址的 sockaddr 结构体的指针。
    • fromlensockaddr 结构体的大小。
  • 返回值:成功返回实际接收到的字节数,失败返回 SOCKET_ERROR
10. closesocket()
  • 功能:关闭套接字,释放相关资源。
  • 原型int closesocket(SOCKET s);
  • 参数
    • s:要关闭的套接字描述符。
  • 返回值:成功返回0,失败返回 SOCKET_ERROR

  • 说明TCP与UDP在Socket编程中的不同之处。

TCP与UDP在Socket编程中的不同之处

TCP(传输控制协议)和UDP(用户数据报协议)是两种常用的传输层协议,它们在Socket编程中有许多不同之处。以下是一些主要的区别:

1. 连接方式
  • TCP:面向连接的协议。在数据传输之前,客户端和服务器之间需要通过三次握手建立连接。数据传输完成后,通过四次挥手断开连接。
  • UDP:无连接的协议。数据传输前不需要建立连接,每个数据包独立发送,接收端也不需要确认。
2. 数据传输可靠性
  • TCP:提供可靠的数据传输。数据包按顺序传输,确保数据的完整性和顺序性。如果数据包丢失或损坏,TCP会自动重传。
  • UDP:不保证数据传输的可靠性。数据包可能丢失、重复或乱序到达。UDP不进行重传,也不保证数据的顺序。
3. 数据传输模式
  • TCP:采用字节流模式。数据被视为一个连续的字节流,没有边界。接收端需要自行处理数据的分段和重组。
  • UDP:采用数据报模式。每个数据包都有明确的边界,发送和接收的数据包保持一致。
4. 系统资源需求
  • TCP:需要更多的系统资源。维护连接状态、重传机制和拥塞控制等都需要额外的资源。
  • UDP:对系统资源的需求较少。由于没有连接状态和重传机制,UDP的开销较低。
5. 系统函数调用
  • TCP

    • 创建套接字socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    • 绑定地址bind(socket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    • 监听连接listen(socket, backlog);
    • 接受连接accept(socket, (struct sockaddr *)&clientAddr, &clientAddrLen);
    • 连接服务端connect(socket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    • 发送数据send(socket, buffer, length, flags);
    • 接收数据recv(socket, buffer, length, flags);
    • 关闭套接字closesocket(socket);
  • UDP

    • 创建套接字socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    • 绑定地址bind(socket, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    • 发送数据sendto(socket, buffer, length, flags, (struct sockaddr *)&destAddr, sizeof(destAddr));
    • 接收数据recvfrom(socket, buffer, length, flags, (struct sockaddr *)&srcAddr, &srcAddrLen);
    • 关闭套接字closesocket(socket);
6. 客户端和服务端的识别
  • TCP:服务器通过 accept() 函数获取客户端的地址信息,客户端的地址信息在连接建立时由操作系统提供。
  • UDP:每次发送数据时,客户端需要指定目标地址信息。服务器通过 recvfrom() 函数获取发送方的地址信息。
7. 适用场景
  • TCP:适用于需要高可靠性的应用场景,如文件传输、Web服务、电子邮件等。
  • UDP:适用于对实时性要求较高且可以容忍一定数据丢失的应用场景,如视频流、在线游戏、VoIP等。

4. 实验内容与步骤

4.1 基于TCP协议的通信编程

  • 单向通信
  • 双向通信
    鉴于单向通信即双向通信的阉割实现,少了服务端回复客户端的过程,因此只需要实现双向通信即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//server
#include <iostream>
#include <winsock2.h> // Winsock头文件
#include <ws2tcpip.h> // 包含inet_ntop等函数
#include <string>

#pragma comment(lib, "ws2_32.lib") // 链接Winsock库

#define PORT 5050

void handle_client(int connfd, const struct sockaddr_in& clientAddr) {
char clientIP[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP)) == nullptr) {
std::cerr << "inet_ntop failed" << std::endl;
closesocket(connfd);
return;
}

std::cout << "Connected to client IP: " << clientIP << ", Port: " << ntohs(clientAddr.sin_port) << std::endl;

char buffer[1024] = { 0 };
while (true) {
int n = recv(connfd, buffer, sizeof(buffer), 0);
if (n <= 0) {
std::cerr << "Client disconnected or receive failed" << std::endl;
break;
}

std::string message(buffer, n);
if (message == "exit") {
std::cout << "Client requested to exit" << std::endl;
break;
}

std::cout << "Received message: " << message << std::endl;

// 发送回消息
send(connfd, buffer, n, 0);
}

closesocket(connfd);
}

int main() {
WSADATA wsaData;
int result;

// 初始化Winsock
result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
std::cerr << "WSAStartup failed: " << result << std::endl;
return -1;
}

int listenfd;
struct sockaddr_in serverAddr;

// 创建TCP套接字
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
std::cerr << "Socket creation failed" << std::endl;
WSACleanup();
return -1;
}

// 设置服务器地址信息
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = INADDR_ANY;
serverAddr.sin_port = htons(PORT);

// 绑定套接字到指定地址
if (bind(listenfd, (const struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
std::cerr << "Bind failed" << std::endl;
closesocket(listenfd);
WSACleanup();
return -1;
}

// 监听连接请求
if (listen(listenfd, 5) < 0) {
std::cerr << "Listen failed" << std::endl;
closesocket(listenfd);
WSACleanup();
return -1;
}

std::cout << "Server listening on port " << PORT << std::endl;

while (true) {
struct sockaddr_in clientAddr;
socklen_t addr_len = sizeof(clientAddr);
int connfd = accept(listenfd, (struct sockaddr*)&clientAddr, &addr_len);
if (connfd < 0) {
std::cerr << "Accept failed" << std::endl;
continue;
}

handle_client(connfd, clientAddr);
}

closesocket(listenfd);
WSACleanup();
return 0;
}

//client
#include <iostream>
#include <winsock2.h> // Winsock头文件
#include <ws2tcpip.h> // 包含inet_pton等函数
#include <string>

#pragma comment(lib, "ws2_32.lib") // 链接Winsock库

#define SERVER_IP "127.0.0.1" // 服务器IP地址
#define PORT 5050

int main() {
WSADATA wsaData;
int result;

// 初始化Winsock
result = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (result != 0) {
std::cerr << "WSAStartup failed: " << result << std::endl;
return -1;
}

int sockfd;
struct sockaddr_in serverAddr;

// 创建TCP套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
std::cerr << "Socket creation failed" << std::endl;
WSACleanup();
return -1;
}

// 设置服务器地址信息
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(PORT);

// 使用inet_pton转换IP地址
if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) <= 0) {
std::cerr << "Invalid address/ Address not supported" << std::endl;
closesocket(sockfd);
WSACleanup();
return -1;
}

// 连接到服务器
if (connect(sockfd, (const struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
std::cerr << "Connect failed" << std::endl;
closesocket(sockfd);
WSACleanup();
return -1;
}

std::cout << "Connected to server" << std::endl;

while (true) {
std::string message;
std::cout << "Enter message (type 'exit' to quit): ";
std::getline(std::cin, message);

if (message == "exit") {
break;
}

// 发送消息
send(sockfd, message.c_str(), message.length(), 0);

// 接收回消息
char buffer[1024] = { 0 };
int n = recv(sockfd, buffer, sizeof(buffer), 0);
if (n > 0) {
std::cout << "Received message: " << buffer << std::endl;
}
else {
std::cerr << "Receive failed" << std::endl;
break;
}
}

// 关闭连接
closesocket(sockfd);
WSACleanup();
return 0;
}
  • 多媒体文件传输
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    291
    292
    293
    294
    295
    296
    297
    298
    299
    300
    301
    302
    303
    304
    305
    306
    307
    308
    309
    310
    311
    312
    313
    314
    315
    // server_file
    #include <iostream>
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #include <fstream>
    #include <vector>
    #include <thread>
    #include <chrono>
    #include <map>
    #include <mutex>

    #pragma comment(lib, "ws2_32.lib")

    #define PORT 5050
    #define BUFFER_SIZE 1024
    #define FILE_NAME "received_file.dat"

    std::mutex file_mutex;

    void handle_client(int connfd, const struct sockaddr_in& clientAddr) {
    char clientIP[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP)) == nullptr) {
    std::cerr << "inet_ntop failed" << std::endl;
    closesocket(connfd);
    return;
    }

    std::cout << "Connected to client IP: " << clientIP << ", Port: " << ntohs(clientAddr.sin_port) << std::endl;

    std::ofstream file(FILE_NAME, std::ios::binary | std::ios::ate);

    // 接收文件大小
    size_t file_size;
    if (recv(connfd, reinterpret_cast<char*>(&file_size), sizeof(file_size), 0) < 0) {
    std::cerr << "Failed to receive file size" << std::endl;
    closesocket(connfd);
    return;
    }

    std::cout << "File size: " << file_size << " bytes" << std::endl;

    char buffer[BUFFER_SIZE] = { 0 };
    int n;
    size_t total_received = 0;

    while (total_received < file_size) {
    size_t start, end;
    if (recv(connfd, reinterpret_cast<char*>(&start), sizeof(start), 0) < 0) {
    std::cerr << "Failed to receive start position" << std::endl;
    break;
    }
    if (recv(connfd, reinterpret_cast<char*>(&end), sizeof(end), 0) < 0) {
    std::cerr << "Failed to receive end position" << std::endl;
    break;
    }

    size_t chunk_received = 0;
    while (chunk_received < (end - start) && (n = recv(connfd, buffer, sizeof(buffer), 0)) > 0) {
    file_mutex.lock();
    file.seekp(start + chunk_received);
    file.write(buffer, n);
    file_mutex.unlock();

    chunk_received += n;
    total_received += n;
    std::cout << "已接收: " << total_received << " 字节" << std::endl;

    // 发送确认消息
    char ack[] = "OK";
    if (send(connfd, ack, 2, 0) < 0) {
    std::cerr << "Ack send failed" << std::endl;
    break;
    }
    }

    if (n < 0) {
    std::cerr << "Receive failed" << std::endl;
    break;
    }
    }

    if (total_received < file_size) {
    std::cerr << "File transfer incomplete" << std::endl;
    }

    file.close();
    closesocket(connfd);
    }

    int main() {
    WSADATA wsaData;
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result != 0) {
    std::cerr << "WSAStartup failed: " << result << std::endl;
    return -1;
    }

    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) {
    std::cerr << "Socket creation failed" << std::endl;
    WSACleanup();
    return -1;
    }

    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(PORT);

    if (bind(listenfd, (const struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
    std::cerr << "Bind failed" << std::endl;
    closesocket(listenfd);
    WSACleanup();
    return -1;
    }

    if (listen(listenfd, 5) < 0) {
    std::cerr << "Listen failed" << std::endl;
    closesocket(listenfd);
    WSACleanup();
    return -1;
    }

    std::cout << "Server listening on port " << PORT << std::endl;

    while (true) {
    struct sockaddr_in clientAddr;
    socklen_t addr_len = sizeof(clientAddr);
    int connfd = accept(listenfd, (struct sockaddr*)&clientAddr, &addr_len);
    if (connfd < 0) {
    std::cerr << "Accept failed" << std::endl;
    continue;
    }

    // 创建新线程处理客户端连接
    std::thread client_thread(handle_client, connfd, clientAddr);
    client_thread.detach(); // 分离线程,允许其独立运行
    }

    closesocket(listenfd);
    WSACleanup();
    return 0;
    }

    // client_file
    #include <iostream>
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #include <fstream>
    #include <vector>
    #include <thread>
    #include <mutex>
    #include <chrono>

    #pragma comment(lib, "ws2_32.lib")

    #define SERVER_IP "127.0.0.1"
    #define PORT 5050
    #define BUFFER_SIZE 1024
    #define CHUNK_SIZE 1048576 // 1MB
    #define TIMEOUT_MS 5000 // 超时时间,单位为毫秒
    #define MAX_RETRIES 3 // 最大重试次数

    std::mutex mtx;

    void send_chunk(int sockfd, std::ifstream& file, size_t start, size_t end) {
    char buffer[BUFFER_SIZE] = { 0 };

    // 发送起始位置和结束位置
    if (send(sockfd, reinterpret_cast<const char*>(&start), sizeof(start), 0) < 0) {
    std::cerr << "Failed to send start position" << std::endl;
    return;
    }
    if (send(sockfd, reinterpret_cast<const char*>(&end), sizeof(end), 0) < 0) {
    std::cerr << "Failed to send end position" << std::endl;
    return;
    }

    file.seekg(start);

    while (file.tellg() < end) {
    file.read(buffer, sizeof(buffer));
    size_t bytes_read = file.gcount();
    if (bytes_read == 0) {
    break;
    }

    int sent_bytes = send(sockfd, buffer, bytes_read, 0);
    if (sent_bytes < 0) {
    std::cerr << "Send failed" << std::endl;
    break;
    }

    // 等待服务器确认
    char ack[2] = { 0 };
    int retries = 0;
    while (retries < MAX_RETRIES) {
    fd_set readfds;
    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);

    struct timeval timeout;
    timeout.tv_sec = TIMEOUT_MS / 1000;
    timeout.tv_usec = (TIMEOUT_MS % 1000) * 1000;

    int select_ret = select(sockfd + 1, &readfds, nullptr, nullptr, &timeout);
    if (select_ret < 0) {
    std::cerr << "Select failed" << std::endl;
    break;
    }
    else if (select_ret == 0) {
    std::cerr << "Timeout waiting for acknowledgment, retrying... (" << retries + 1 << "/" << MAX_RETRIES << ")" << std::endl;
    retries++;
    continue;
    }
    else {
    if (recv(sockfd, ack, 2, 0) <= 0) {
    std::cerr << "Ack receive failed, retrying... (" << retries + 1 << "/" << MAX_RETRIES << ")" << std::endl;
    retries++;
    continue;
    }
    break;
    }
    }

    if (retries >= MAX_RETRIES) {
    std::cerr << "Max retries reached, giving up." << std::endl;
    break;
    }
    }
    }

    int main() {
    WSADATA wsaData;
    int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result != 0) {
    std::cerr << "WSAStartup failed: " << result << std::endl;
    return -1;
    }

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
    std::cerr << "Socket creation failed" << std::endl;
    WSACleanup();
    return -1;
    }

    struct sockaddr_in serverAddr;
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);
    if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) <= 0) {
    std::cerr << "Invalid address/ Address not supported" << std::endl;
    closesocket(sockfd);
    WSACleanup();
    return -1;
    }

    if (connect(sockfd, (const struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
    std::cerr << "Connect failed" << std::endl;
    closesocket(sockfd);
    WSACleanup();
    return -1;
    }

    std::cout << "Connected to server" << std::endl;

    std::ifstream file("C:\\Users\\21354\\Videos\\asdf.mp4", std::ios::binary);
    if (!file) {
    std::cerr << "无法打开文件" << std::endl;
    closesocket(sockfd);
    WSACleanup();
    return -1;
    }

    file.seekg(0, std::ios::end);
    size_t file_size = file.tellg();
    file.seekg(0, std::ios::beg);

    std::cout << "文件大小: " << file_size << " 字节" << std::endl;

    // 发送文件大小
    if (send(sockfd, reinterpret_cast<const char*>(&file_size), sizeof(file_size), 0) < 0) {
    std::cerr << "Failed to send file size" << std::endl;
    closesocket(sockfd);
    WSACleanup();
    return -1;
    }

    size_t num_threads = 1; // 可以根据需要调整线程数量
    size_t chunk_size = (file_size + num_threads - 1) / num_threads;
    std::vector<std::thread> threads;

    for (size_t i = 0; i < num_threads; ++i) {
    size_t start = i * chunk_size;
    size_t end = (i + 1) * chunk_size;
    if (i == num_threads - 1) {
    end = file_size;
    }
    threads.emplace_back(send_chunk, sockfd, std::ref(file), start, end);
    }

    for (auto& thread : threads) {
    thread.join();
    }

    std::string end_message = "exit";
    send(sockfd, end_message.c_str(), end_message.length(), 0);

    file.close();
    closesocket(sockfd);
    WSACleanup();
    return 0;
    }

4.2 基于UDP协议的通信编程

  • 单向通信
  • 双向通信
    鉴于单向通信即双向通信的阉割实现,少了服务端回复客户端的过程,因此只需要实现双向通信即可。
    winsock2.hws2tcpip.h 是 Windows 特有的头文件,专门用于 Windows 平台上的网络编程。这些头文件提供了 Windows Sockets API(Winsock API)的相关定义和函数声明。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    //server
    #include <iostream>
    #include <winsock2.h> // Winsock头文件
    #include <ws2tcpip.h> // 包含inet_ntop等函数
    #include <string>

    #pragma comment(lib, "ws2_32.lib") // 链接Winsock库

    #define PORT 56443
    #define BUFFER_SIZE 1024

    int main() {
    WSADATA wsaData;
    int result;

    // 初始化Winsock
    result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result != 0) {
    std::cerr << "WSAStartup failed: " << result << std::endl;
    return -1;
    }

    int sockfd; // 套接字描述符
    char buffer[BUFFER_SIZE]; // 接收缓冲区
    struct sockaddr_in serverAddr, clientAddr; // 服务器和客户端的地址结构体
    int addr_len = sizeof(clientAddr); // 地址结构体的长度

    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
    std::cerr << "Socket creation failed" << std::endl;
    WSACleanup();
    return -1;
    }

    // 设置服务器地址信息
    memset(&serverAddr, 0, sizeof(serverAddr)); // 清零地址结构体
    serverAddr.sin_family = AF_INET; // 使用IPv4地址族
    serverAddr.sin_addr.s_addr = INADDR_ANY; // 通配IP地址
    serverAddr.sin_port = htons(PORT); // 设置端口号

    // 绑定套接字到指定地址
    if (bind(sockfd, (const struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
    std::cerr << "Bind failed" << std::endl;
    closesocket(sockfd);
    WSACleanup();
    return -1;
    }

    std::cout << "UDP服务器启动,正在监听端口 " << PORT << "..." << std::endl;

    while (true) {
    // 接收来自客户端的消息
    int n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&clientAddr, &addr_len);
    buffer[n] = '\0'; // 添加字符串终止符

    // 使用inet_ntop转换IP地址
    char clientIP[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP)) == nullptr) {
    std::cerr << "inet_ntop failed" << std::endl;
    continue;
    }

    std::cout << "收到IP地址为 " << clientIP << " 端口号为 " << ntohs(clientAddr.sin_port) << " 发送来的内容: " << buffer << std::endl;

    // 向客户端发送应答
    std::string response = "消息已收到";
    sendto(sockfd, response.c_str(), response.size(), 0, (const struct sockaddr*)&clientAddr, addr_len);

    // 打印客户端IP地址和端口号
    std::cout << "发送应答至 IP地址 " << clientIP << " 端口号 " << ntohs(clientAddr.sin_port) << std::endl;
    }

    // 关闭套接字
    closesocket(sockfd);
    WSACleanup(); // 清理Winsock
    return 0;
    }

    //client
    #include <iostream>
    #include <winsock2.h> // Winsock头文件
    #include <ws2tcpip.h> // 包含inet_pton和inet_ntop等函数
    #include <string>

    #pragma comment(lib, "ws2_32.lib") // 链接Winsock库

    #define SERVER_IP "127.0.0.1" // 服务器IP地址
    #define PORT 56443
    #define BUFFER_SIZE 1024

    int main() {
    WSADATA wsaData;
    int result;

    // 初始化Winsock
    result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result != 0) {
    std::cerr << "WSAStartup failed: " << result << std::endl;
    return -1;
    }

    int sockfd; // 套接字描述符
    char buffer[BUFFER_SIZE]; // 发送和接收缓冲区
    struct sockaddr_in serverAddr; // 服务器地址结构体
    int addr_len = sizeof(serverAddr); // 地址结构体的长度
    int n; // 接收消息的长度

    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
    std::cerr << "Socket creation failed" << std::endl;
    WSACleanup();
    return -1;
    }

    // 设置服务器地址信息
    memset(&serverAddr, 0, sizeof(serverAddr)); // 清零地址结构体
    serverAddr.sin_family = AF_INET; // 使用IPv4地址族
    serverAddr.sin_port = htons(PORT); // 设置端口号

    // 使用inet_pton转换IP地址
    if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) <= 0) {
    std::cerr << "Invalid address/ Address not supported" << std::endl;
    closesocket(sockfd);
    WSACleanup();
    return -1;
    }

    int N = 0; // 消息计数器
    while (true) {
    // 从键盘读取输入
    std::cout << "请输入要发送的消息(输入exit退出): ";
    std::cin.getline(buffer, BUFFER_SIZE);

    // 如果输入的是exit,则退出
    if (std::string(buffer) == "exit" || std::string(buffer) == "EXIT") {
    break;
    }

    // 发送消息到服务器
    sendto(sockfd, buffer, strlen(buffer), 0, (const struct sockaddr*)&serverAddr, addr_len);
    std::cout << "已发送消息: " << buffer << std::endl;

    // 接收服务器的应答
    n = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, (struct sockaddr*)&serverAddr, &addr_len);
    buffer[n] = '\0'; // 添加字符串终止符

    // 使用inet_ntop转换IP地址
    char serverIP[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &serverAddr.sin_addr, serverIP, sizeof(serverIP)) == nullptr) {
    std::cerr << "inet_ntop failed" << std::endl;
    continue;
    }

    std::cout << "收到IP地址为 " << serverIP << " 端口号为 " << ntohs(serverAddr.sin_port) << " 发送来的应答: " << buffer << std::endl;

    N++;
    }

    // 关闭套接字
    closesocket(sockfd);
    WSACleanup(); // 清理Winsock
    return 0;
    }
  • 多媒体文件传输
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    //server_file
    #include <iostream>
    #include <winsock2.h> // Winsock头文件
    #include <ws2tcpip.h> // 包含inet_ntop等函数
    #include <fstream>
    #include <vector>
    #include <chrono>
    #include <thread>

    #pragma comment(lib, "ws2_32.lib") // 链接Winsock库

    #define PORT 50500
    #define BUFFER_SIZE 1024
    #define FILE_NAME "received_file.dat"
    #define TIMEOUT_MS 5000 // 超时时间,单位为毫秒

    struct Packet {
    int seq_num; // 序号
    char data[BUFFER_SIZE]; // 数据
    };

    int main() {
    WSADATA wsaData;
    int result;

    // 初始化Winsock
    result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result != 0) {
    std::cerr << "WSAStartup failed: " << result << std::endl;
    return -1;
    }

    int sockfd; // 套接字描述符
    struct sockaddr_in serverAddr, clientAddr; // 服务器和客户端的地址结构体
    int addr_len = sizeof(clientAddr); // 地址结构体的长度

    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
    std::cerr << "Socket creation failed" << std::endl;
    WSACleanup();
    return -1;
    }

    // 设置服务器地址信息
    memset(&serverAddr, 0, sizeof(serverAddr)); // 清零地址结构体
    serverAddr.sin_family = AF_INET; // 使用IPv4地址族
    serverAddr.sin_addr.s_addr = INADDR_ANY; // 通配IP地址
    serverAddr.sin_port = htons(PORT); // 设置端口号

    // 绑定套接字到指定地址
    if (bind(sockfd, (const struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
    std::cerr << "Bind failed" << std::endl;
    closesocket(sockfd);
    WSACleanup();
    return -1;
    }

    std::cout << "UDP服务器启动,正在监听端口 " << PORT << "..." << std::endl;

    std::ofstream file(FILE_NAME, std::ios::binary);

    while (true) {
    Packet packet;
    int n = recvfrom(sockfd, (char*)&packet, sizeof(packet), 0, (struct sockaddr*)&clientAddr, &addr_len);
    if (n < 0) {
    std::cerr << "Receive failed" << std::endl;
    continue;
    }

    // 使用inet_ntop转换IP地址
    char clientIP[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, sizeof(clientIP)) == nullptr) {
    std::cerr << "inet_ntop failed" << std::endl;
    continue;
    }

    std::cout << "收到IP地址为 " << clientIP << " 端口号为 " << ntohs(clientAddr.sin_port) << " 序号为 " << packet.seq_num << " 的数据包" << std::endl;

    // 写入文件
    file.write(packet.data, n - sizeof(int));

    // 发送确认
    Packet ack;
    ack.seq_num = packet.seq_num;
    sendto(sockfd, (const char*)&ack, sizeof(ack), 0, (const struct sockaddr*)&clientAddr, addr_len);

    std::cout << "发送确认序号为 " << ack.seq_num << " 的确认消息" << std::endl;
    }

    // 关闭文件
    file.close();

    // 关闭套接字
    closesocket(sockfd);
    WSACleanup(); // 清理Winsock
    return 0;
    }

    //client_file
    #include <iostream>
    #include <winsock2.h> // Winsock头文件
    #include <ws2tcpip.h> // 包含inet_pton和inet_ntop等函数
    #include <fstream>
    #include <vector>
    #include <chrono>
    #include <thread>

    #pragma comment(lib, "ws2_32.lib") // 链接Winsock库

    #define SERVER_IP "127.0.0.1" // 服务器IP地址
    #define PORT 50500
    #define BUFFER_SIZE 1024
    #define TIMEOUT_MS 5000 // 超时时间,单位为毫秒

    struct Packet {
    int seq_num; // 序号
    char data[BUFFER_SIZE]; // 数据
    };

    int main() {
    WSADATA wsaData;
    int result;

    // 初始化Winsock
    result = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result != 0) {
    std::cerr << "WSAStartup failed: " << result << std::endl;
    return -1;
    }

    int sockfd; // 套接字描述符
    struct sockaddr_in serverAddr; // 服务器地址结构体
    int addr_len = sizeof(serverAddr); // 地址结构体的长度

    // 创建UDP套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
    std::cerr << "Socket creation failed" << std::endl;
    WSACleanup();
    return -1;
    }

    // 设置服务器地址信息
    memset(&serverAddr, 0, sizeof(serverAddr)); // 清零地址结构体
    serverAddr.sin_family = AF_INET; // 使用IPv4地址族
    serverAddr.sin_port = htons(PORT); // 设置端口号

    // 使用inet_pton转换IP地址
    if (inet_pton(AF_INET, SERVER_IP, &serverAddr.sin_addr) <= 0) {
    std::cerr << "Invalid address/ Address not supported" << std::endl;
    closesocket(sockfd);
    WSACleanup();
    return -1;
    }

    //std::ifstream file("file_to_send.mp4", std::ios::binary);
    std::ifstream file("D:\\Users\\21354\\Videos\\无标题视频 - Screen Recording - 2023_9_8 23_11_24.webm", std::ios::binary);
    if (!file) {
    std::cerr << "无法打开文件" << std::endl;
    closesocket(sockfd);
    WSACleanup();
    return -1;
    }

    file.seekg(0, std::ios::end);
    size_t file_size = file.tellg();
    file.seekg(0, std::ios::beg);

    int total_packets = (file_size + BUFFER_SIZE - 1) / BUFFER_SIZE;
    std::cout << "文件大小: " << file_size << " 字节,总共 " << total_packets << " 个数据包" << std::endl;

    for (int i = 0; i < total_packets; ++i) {
    Packet packet;
    packet.seq_num = i;

    file.read(packet.data, BUFFER_SIZE);// 读取数据包
    size_t bytes_read = file.gcount();// 读取的字节数

    sendto(sockfd, (const char*)&packet, bytes_read + sizeof(int), 0, (const struct sockaddr*)&serverAddr, addr_len);// 发送数据包
    std::cout << "发送序号为 " << packet.seq_num << " 的数据包" << std::endl;

    // 等待确认
    Packet ack;
    bool received_ack = false;// 确认消息是否已收到
    auto start_time = std::chrono::steady_clock::now();// 开始时间
    while (!received_ack) {
    fd_set readfds;
    FD_ZERO(&readfds);// 定义文件描述符集
    FD_SET(sockfd, &readfds);// 将套接字加入文件描述符集

    timeval timeout;// 定义超时时间
    timeout.tv_sec = TIMEOUT_MS / 1000;
    timeout.tv_usec = (TIMEOUT_MS % 1000) * 1000;

    int select_ret = select(sockfd + 1, &readfds, NULL, NULL, &timeout);// 等待套接字可读
    if (select_ret > 0 && FD_ISSET(sockfd, &readfds)) {
    int n = recvfrom(sockfd, (char*)&ack, sizeof(ack), 0, (struct sockaddr*)&serverAddr, &addr_len);
    if (n >= 0 && ack.seq_num == i) {
    received_ack = true;
    std::cout << "收到序号为 " << ack.seq_num << " 的确认消息" << std::endl;
    }
    }
    else {
    // 超时,重新发送数据包
    std::cout << "超时,重新发送序号为 " << packet.seq_num << " 的数据包" << std::endl;
    sendto(sockfd, (const char*)&packet, bytes_read + sizeof(int), 0, (const struct sockaddr*)&serverAddr, addr_len);
    }

    // 模拟网络中断
    if (i == 10) { // 在第11个数据包时模拟网络中断
    std::this_thread::sleep_for(std::chrono::seconds(5));
    }
    }
    }

    // 关闭文件
    file.close();

    // 关闭套接字
    closesocket(sockfd);
    WSACleanup(); // 清理Winsock
    return 0;
    }

5. 实验结果与分析

  • 分别展示TCP和UDP协议下实验的结果截图或代码片段。
    TCP通信:
    TCP双向通信
    TCP文件传输
    UDP通信:
    UDP文件传输
    视频演示
  • 对比分析两种协议下的通信效果,特别是关注数据传输的可靠性和效率。
    对比两种传输方式,在视频文件的传输中,我们可以发现,UDP的传输效率要高于TCP的传输效率(之后我尝试设计了TCP的多线程实现)。(速度更快)但由于UDP传输方式不可靠,在使用UDP传输时出现了丢包、乱序等问题,最终收到的视频文件出现花屏,部分绿屏,甚至损坏无法播放等问题。
    为了解决UDP传输方式的不可靠性问题,我在设计中加入序列号和确认机制,在收到确认消息后才认为数据包已收到,从而保证数据传输的可靠性。(此处在应用层实现,原理部分参照了在传输层的TCP的可靠性实现。)

6. 总结与体会

  • 总结实验中遇到的问题及解决方法。
  • 在udp传输文件的实验中,由于udp传输的不可靠性导致文件损坏等等情况的出现。
    解决方案:协议本身的不可靠性,实验中属于正常现象。为了保证可靠传输,我在应用层程序设计中仿照tcp的部分原理,将数据包的开头替换插入为序列号,每次发送数据包等收到回复确认之后再进行发送,保证了数据的可靠传输。问题成功解决。
  • 在设计多线程的tcp传输提高大文件的传输效率时,由于能力有限,设计出的服务端无法处理单个连接建立的套接字当中收到的多线程发送的文件数据包,导致文件损坏。暂未找到解决方法。但也因此尝试了md5校验等等保证和检查文件完整性的方法略有其他收获。

7. 附录

  • 包含完整的实验代码。
  • 提交报告所需的所有附件资料。
Comments