UDP 是OSI(Open System Interconnection,开放式系统互联) 参考模型中一种无连接的传输层协议。
UDP协议与TCP协议一样用于处理数据包。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。UDP用来支持那些需要在计算机之间传输数据的网络应用。包括网络视频会议系统在内的众多的客户/服务器模式的网络应用都需要使用UDP协议。UDP协议从问世至今已经被使用了很多年,虽然其最初的光彩已经被一些类似协议所掩盖,但即使在今天UDP仍然不失为一项非常实用和可行的网络传输层协议。
当强调传输性能而不是传输的完整性时,如:音频和多媒体应用,UDP是最好的选择。在数据传输时间很短,以至于此前的连接过程成为整个流量主体的情况下,UDP也是一个好的选择。 [3]
UDP的一般实现流程图:
- //初始化套接字库
- WORD wVersion;
- WSADATA wsaData;
- int err;
-
- wVersion = MAKEWORD(1, 1);
- err = WSAStartup(wVersion, &wsaData);
- //判断执行结果
- if (err != 0) {
- return err;
- }
- //判断初始化结果是否与选择的版本库一致
- if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
- //清理套接字库
- WSACleanup();
- return -1;
- }
在开之前需要先初始化套接字库,告诉操作系统我们选择的套接字库的版本号。这里我们选择的是 1.1版本的套接字库 。
初始化完成后需要判断程序的执行结果,一个是函数本身的运行情况,判断返回结果是否为0;另一个是我们初始化的套接字库的版本号是否和我们指定的相一致。
- //创建Socket
- //指定协议族为IPV4,socket通信类型为支持UDP连接
- SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
-
- //准本一个处理网络通信的地址
- SOCKADDR_IN addrSrv;
- //指定要绑定的IP地址为本机上的任意IP地址
- addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
- //协议族要与创建给的Socket一致
- addrSrv.sin_family = AF_INET;
- //指明绑定的端口号
- addrSrv.sin_port = htons(6001);
创建一个服务器的Socket, 并指明协议族为IPV4,通信类型为支持UDP协议。
还需要准本一个用于处理网络通信的结构体,并指绑定的IP地址、端口号和协议族(协议族要与创建Socket时的协议族保持一致)。
- //绑定套接字
- bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
bind函数会将端口和IP地址绑定到我们的socket描述符上,返回0时表示绑定成功 。
- //保存发送数据的地址
- SOCKADDR_IN addrCli;
-
- int len = sizeof(SOCKADDR_IN);
- char recvBuf[100];
- char sendBuf[100];
- while (true) {
- //接收数据
- recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
- //将接收数据打印下来
- std::cout << recvBuf << std::endl;
- sprintf_s(sendBuf, 100, "ACK%s", recvBuf);
- //发送数据
- sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrCli, len);
- }
UDP没有TCP的监听和请求步骤,而是直接去接收客户端发来的信息,并将客户端的socket信息保存下来,在发送数据的时候使用。
recvfrom函数用于从(已连接)套接口上接收数据,并捕获数据发送源的地址。
recvfrom函数原型为
- int recvfrom(
- SOCKET s,
- char* buf,
- int len,
- int flags,
- sockaddr * from,
- int* fromlen
- );
sendto是一个向指定目的地发送数据的函数,将recvfrom捕获的客户端地址传入sendto里,就可以向该客户端发送数据
sendto函数原型
- int sendto(
- int s,
- const void * msg,
- int len,
- unsigned int flags,
- const struct sockaddr * to,
- int tolen
- );
s:标识绑定的套接字描述符
msg:发送数据的缓冲区
len:指向发送缓冲区的长度
flags:可一般默认为0
to:向指定地址发送数据的地址长度
- #include
- #include
-
- int main() {
-
-
- //初始化套接字库
- WORD wVersion;
- WSADATA wsaData;
- int err;
-
- wVersion = MAKEWORD(1, 1);
- err = WSAStartup(wVersion, &wsaData);
- //判断执行结果
- if (err != 0) {
- return err;
- }
- //判断初始化结果是否与选择的版本库一致
- if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
- //清理套接字库
- WSACleanup();
- return -1;
- }
-
- //创建Socket
- //指定协议族为IPV4,socket通信类型为支持UDP连接
- SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);
- //准本一个处理网络通信的地址
- SOCKADDR_IN addrSrv;
- //指定要绑定的IP地址为本机上的任意IP地址
- addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
- //协议族要与创建给的Socket一致
- addrSrv.sin_family = AF_INET;
- //指明绑定的端口号
- addrSrv.sin_port = htons(6001);
- //绑定套接字
- bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
-
- //保存发送数据的地址
- SOCKADDR_IN addrCli;
-
- int len = sizeof(SOCKADDR_IN);
-
- char recvBuf[100];
- char sendBuf[100];
- while (true) {
- //接收数据
- recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
- //将接收数据打印下来
- std::cout << recvBuf << std::endl;
- sprintf_s(sendBuf, 100, "ACK%s", recvBuf);
- //发送数据
- sendto(sockSrv, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrCli, len);
- }
-
-
- //关闭套接字
- closesocket(sockSrv);
- //清理套接字库
- WSACleanup();
-
- system("pause");
- return 0;
- }
- //初始化套接字库
- WORD wVersion;
- WSADATA wsaData;
- int err;
-
- wVersion = MAKEWORD(1, 1);
- err = WSAStartup(wVersion, &wsaData);
- //判断执行结果
- if (err != 0) {
- return err;
- }
- //判断初始化结果是否与选择的版本库一致
- if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
- //清理套接字库
- WSACleanup();
- return -1;
- }
客户端的实现也需要先初始化套接字库,注意库的版本和服务器的版本相一致。
- //创建Socket
- //指定协议族为IPV4,socket通信类型为支持UDP连接
- SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
-
- //准本一个处理网络通信的地址
- SOCKADDR_IN addrSrv;
- //指定要连接的IP地址
- addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
- //协议族要与创建给的Socket一致
- addrSrv.sin_family = AF_INET;
- //指明绑定的端口号
- addrSrv.sin_port = htons(6001);
创建一个套接字,套接字的协议族与通信类型要和服务器的保持一致。之后准备一个处理网络通信的地址,也就是需要连接服务器的地址,并指明连接的IP地址和端口号。
- //保存发送数据的地址
- SOCKADDR_IN addrCli;
-
- int len = sizeof(SOCKADDR_IN);
- char recvBuf[100];
- char sendBuf[100] = "UDP Cli come to connect";
- //发送数据
- sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len);
- //接收数据,这里也可也不保存地址
- recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
- //将发送数据打印下来
- std::cout << recvBuf << std::endl;
其实这里我们可以不写保存地址的SOCKADDR_IN结构体,因为我们已经知道要发送到的地址了
- #include
- #include
-
- #pragma comment(lib,"ws2_32.lib")
-
- int main()
- {
- //初始化套接字库
- WORD wVersion;
- WSADATA wsaData;
- int err;
-
- wVersion = MAKEWORD(1, 1);
- err = WSAStartup(wVersion, &wsaData);
- //判断执行结果
- if (err != 0) {
- return err;
- }
- //判断初始化结果是否与选择的版本库一致
- if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1) {
- //清理套接字库
- WSACleanup();
- return -1;
- }
-
- //创建Socket
- //指定协议族为IPV4,socket通信类型为支持UDP连接
- SOCKET sockCli = socket(AF_INET, SOCK_DGRAM, 0);
- //准本一个处理网络通信的地址
- SOCKADDR_IN addrSrv;
- //指定要连接的IP地址
- addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
- //协议族要与创建给的Socket一致
- addrSrv.sin_family = AF_INET;
- //指明绑定的端口号
- addrSrv.sin_port = htons(6001);
-
- //保存发送数据的地址
- SOCKADDR_IN addrCli;
- int len = sizeof(SOCKADDR_IN);
- char recvBuf[100];
- char sendBuf[100] = "UDP Cli come to connect";
- //发送数据
- sendto(sockCli, sendBuf, strlen(sendBuf) + 1, 0, (SOCKADDR*)&addrSrv, len);
- //接收数据
- recvfrom(sockCli, recvBuf, 100, 0, (SOCKADDR*)&addrCli, &len);
- //将发送数据打印下来
- std::cout << recvBuf << std::endl;
-
- system("pause");
- return 0;
- }