UDP 编程
要求
实验一
- 客户端和服务端交互,客户端发送信息
- 服务端显示信息来源并且将接收到的信息回传给发送方
- 服务端使用多线程实现并发
- 观察在服务端启动或者不启动时,客户端运行情况
实验二
- 客户端向服务端发送文件名
- 客户端想服务端传输文件内容
- 双方关闭套接字
- 注意收发速度对文件的传输结果的影响
分析
实验一
首先服务端启动
和tcp的服务端启动类似,不过不需要listen,
只要创建socket并且绑定端口就行。然后就是recvfrom等待客户端连接
一旦客户端发送数据过来了,就创建新线程,在新线程中创建一个新的socket,绑定一个新的未被占用的端口,然后给客户端返回信息。
//创建一个新的socket
newsocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (newsocket == INVALID_SOCKET)
{
cout << "Create Transfer socket failed with error: " << WSAGetLastError() << endl;
return -1;
}
sockaddr_in newaddr;
newaddr.sin_family = AF_INET;
newaddr.sin_addr.s_addr = inet_addr(IP_ADDR);
newaddr.sin_addr.S_un.S_addr = INADDR_ANY;
memset(newaddr.sin_zero, 0x00, 8);
int notfirst = 0;
int newaddr_len = sizeof(newaddr);
//bind socket,一个新的端口
iResult = bind(newsocket, (struct sockaddr *)&newaddr, newaddr_len);
if (iResult != 0)
{
cout << "Bind socket failed: " << WSAGetLastError() << endl;
closesocket(newsocket);
return -1;
}
采用循环交互,只要客户端不发送“End”,双方的通信就不会结束
UDP是无连接的,所以无论在服务端是否开启的情况下,客户端都能进行数据发送,因为客户根本不在乎服务端是否接受。
在服务端没有开启的情况下,客户端不能收到服务端的返回,所以会超时错误。
实验二
整个交互的过程是客户端发送文件名,服务端接受到之后,开一个新线程,创建一个新的套接字,绑定一个新的端口来进行余下的文件传输,通过给客户端返回信息来通知新的端口信息。然后,客户端发送文件长度,然后是文件内容,文件发送和接受都是循环的,直到文件传输完全成功
设置了超时时间,在文件传输过程中,一旦发生问题,服务端接收不到后续信息,超时之后自动退出
//设置超时时间
int nTimeOver = 10000;
setsockopt(TransSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&nTimeOver, sizeof(nTimeOver));
- 客户端也设置了超时时间,如果在发送文件结束之后,如果超时时间内没有收到服务端的确认信息就自动退出
int nTimeover = 1000;
setsockopt(ClientSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&nTimeover, sizeof(nTimeover));
- 关于传输时间控制,使用的是sleep()函数,在前后两次发送之间调用sleep()来降低传输速度,从而可以使得UDP报文的数据不会因为传输速度太快而淹没。
Sleep(100);
实验结果
实验一
服务端开启的时候
客户端参数设置
可以多次发送数据
可以多次发送数据可以接受多个客户端的同时发送数据
服务端关闭的时候
当服务器被关闭的时候
客户端可以正常发送数据,因为UDP不需要建立连接
但是客户端是不可能接收到服务端的返回信息所以会显示错误代码10060,就是在超时时间内接受不到,停止阻塞。
实验二
短文件传输
- 客户端参数设置
PS:
2.txt大小为94643个字节
- 文件传输
此时文件长度还不是很长,每次发送都能被服务端接收到,而且返回信息也是成功。
长文件传输
- 客户端参数设置
PS:
3.pdf大小为183662个字节
- 文件传输
可以从运行结果看出来,当文件过大的时候,容易有一部分的数据不能被接收,非常容易造成内容的丢失。
此时服务端会等到超时时间到了之后自动停止阻塞
- 修改——控制发送速度
在每次发送,开始下一次发送之前,调用sleep函数来降低发送速度,此时,整个文件都能被服务端正确接受。
思考总结
注意初始化,有时候没有初始化会造成很大的问题
UDP发送端的发送频率如果过快,就会导致信息的淹没,接收方不能接收到完整信息
UDP是不可靠传输,所以需要设置超时时间,不然容易导致程序一直等待
如果UDP发送速度过快,会导致数据的淹没,从而出错。
总的来说,可靠的传输TCP虽然设置稍微复杂,但是稳定程度,可靠程度高很多,编程的时候也不容易出现特别奇怪的问题,不可靠传输UDP,虽然看上去简单一些,但是实际编程中会遇到各种乱七八糟的问题,也更加难以调试,难以发现错误。
代码
实验一
Client
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <WinSock2.h>
#include <iostream>
#pragma comment (lib,"ws2_32.lib")
#define MAX_LEN 100000
using namespace std;
int main(int argc, char * argv[])
{
WSADATA ws;
int iResult;
SOCKET ClientSocket = INVALID_SOCKET;
struct sockaddr_in Server_addr;
int Server_len;
//检查参数个数
if (argc != 3)
{
cout << "Need enter target IP and port !" << endl;
return -1;
}
int PORT = atoi(argv[2]);
//初始化 socket
iResult = WSAStartup(MAKEWORD(2, 2), &ws);
if (iResult != 0)
{
cout << "Initiate failed with error: " << GetLastError() << endl;
WSACleanup();
return -1;
}
//创建 socket
ClientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (ClientSocket == INVALID_SOCKET)
{
cout << "Create Client socket failed with error: " << WSAGetLastError() << endl;
WSACleanup();
return -1;
}
Server_addr.sin_family = AF_INET;
Server_addr.sin_addr.s_addr = inet_addr(argv[1]);
Server_addr.sin_port = htons(PORT);
memset(Server_addr.sin_zero, 0x00, 8);
Server_len = sizeof(Server_addr);
char data[MAX_LEN];
cout << "########################################### Interactive Mode ############################################" << endl;
while (TRUE)
{
cout << "Please enter the data to send! Enter \"End\" to close the Interactive mode" << endl;
cin >> data;
cout << endl;
int len = strlen(data);
iResult = sendto(ClientSocket, data, strlen(data), 0, (sockaddr *)&Server_addr, Server_len);
if (iResult != strlen(data) && iResult != MAX_LEN)
{
cout << "Sending failed with error :" << WSAGetLastError() << endl;
cout << "Client exit." << endl;
closesocket(ClientSocket);
WSACleanup();
return -1;
}
else if (iResult == MAX_LEN)
{
cout << "Sending data buffer is already full!\n " << WSAGetLastError() << endl;
}
int Recv_Server_addr_len = 0;
char recvdata[MAX_LEN];
sockaddr_in confire_addr;
int nTimeover = 1000;
setsockopt(ClientSocket, SOL_SOCKET, SO_RCVTIMEO, (char*)&nTimeover, sizeof(nTimeover));
iResult = recvfrom(ClientSocket, recvdata, MAX_LEN, 0, (sockaddr