简单易懂 UDP实现客户端通信

在写面向UDP连接的 socket 的通信程序时 , 我先总结归纳一些关于面向TCP和UDP连接的socket 通信程序的相关知识:
面向TCP连接的 socket 通信程序:
服务端:创建套接字 , 指定协议族(sockaddr_in) , 绑定 , 监听 , 接受连接 , 发送或接受数据 , 关闭连接;
客户端:创建套接字 , 指定协议族 , 连接(connect) , 发送或接受数据 , 关闭连接;
TCP在接受数据时:write/send/sendto, read/recv/recvfrom都可以用 ,  通常会用send, recv;
但在面向UDP的socket程序中 , 发送数据时用sendto的话 , 就不用connect了 , 但是在面向TCP的程序中 , 在发送数据时 , 即使用sendto,也必须用connect
面向UDP连接的socket通信程序:
服务端:创建套接字 , 指定协议族(sockaddr_in),绑定(不需要listen和accept),发送或接收数据;
客户端:创建套接字 , 指定协议族 , 连接(和TCP的客户端步骤一样) , 发送或接受数据;
UDP常用sendto,recvfrom; 注意:用sendto时 , 就不用connect了(用了也没事) , 其他的(write, send)
必须用connect;
补充:1、无论是TCP还是UDP,默认情况下创建的都是阻塞模式的套接字 , 执行到(accept,connect, write/send/sento,read/recv/recvfrom)等语句时 , 会一直等待(connect)有点列外 , 它连接一段时间 , 如果连接不成功 , 会以错误形式返回 , 不会一直等待
2、可以把socket设置成非阻塞模式 ,  linux下用fcntl函数 , TCP和UDP设置成非阻塞模式以后 , 效果是一样的 , 都不再等待而是立即返回
3、TCP面向连接 ,  UDP面向无连接
TCP:客户端退出程序时或断开连接时 , TCP的这个函数会立即返回不在阻塞(因为服务端自己知道客户端已经退出或断开连接 , 证明它是面向连接的)
UDP:始终保持阻塞(服务端不知道客户端已经退出或断开连接 , 证明它是面向无连接的)
4、TCP无边界 , UDP有边界
TCP:客户端连续发送数据 , 只要服务端的这个函数的缓冲区足够大 , 会一次性接收过来
(客户端是分好几次发过来 , 是有边界的 , 而服务端却一次性接收过来 , 所以证明是无边界的)
UDP:客户端连续发送数据 , 即使服务端的这个函数的缓冲区足够大 , 也只会一次一次的接收 , 客户端分几次发送过来 , 服务端就必须按几次接收
补充:
1、socket()的参数不同
2、UDP Server不需要调用listen和accept
3、UDP收发数据用sendto/recvfrom函数
4、UDP:shutdown函数无效
5、TCP:地址信息在connect/accept时确定 UDP:在sendto/recvfrom函数中每次均需指定地址信息Sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输 。由于本地socket并没有与远端机器进行连接 , 所以在发送数据时应指明目的地址
下面就是我写的利用UDP连接和多线程实现的客户端之间的通信代码:
服务器端:UdpServer.c
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>#include <stdlib.h> #define PORT 8888 struct info{ char buf[100]; int port;}; int main(){ int sockfd, length, ret, j, i = 0; struct sockaddr_in server_addr; struct sockaddr_in client_addr[10] = {0}; struct sockaddr_in tmp_addr; struct info RecvBuf = {0};sockfd = socket(PF_INET, SOCK_DGRAM, 0); if (-1 == sockfd) {perror("socket");exit(1); }bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = PORT; server_addr.sin_addr.s_addr = inet_addr("192.168.0.128");ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (ret < 0) {perror("bind");exit(1); } while (1) {length = sizeof(client_addr[0]);ret = recvfrom(sockfd, &RecvBuf, sizeof(RecvBuf), 0, (struct sockaddr *)&tmp_addr, &length);if (ret < 0){perror("recvfrom");exit(1);}printf("Recv From Client %d : %sn", tmp_addr.sin_port, RecvBuf.buf);if (0 == i){client_addr[0].sin_family = tmp_addr.sin_family;client_addr[0].sin_port = tmp_addr.sin_port;client_addr[0].sin_addr.s_addr = tmp_addr.sin_addr.s_addr;i++;}else{for (j = 0; j < i; j++){if (tmp_addr.sin_port == client_addr[j].sin_port){break;}if (j == i - 1){client_addr[i].sin_family = tmp_addr.sin_family;client_addr[i].sin_port = tmp_addr.sin_port;client_addr[i].sin_addr.s_addr = tmp_addr.sin_addr.s_addr;i++;}}}if (!strcmp(RecvBuf.buf, "bye")){break;}strcat(RecvBuf.buf, "-server");for(j = 0; j < i; j++){if (RecvBuf.port == client_addr[j].sin_port){break;}if (j == i - 1){break;}}ret = sendto(sockfd, &RecvBuf, sizeof(RecvBuf), 0, (struct sockaddr *)&client_addr[j], sizeof(client_addr[0]));if (ret < 0){perror("sendto");exit(1);}memset(&RecvBuf, 0, sizeof(RecvBuf)); } close(sockfd); return 0;}


推荐阅读