Socket编程实践(12) --UDP编程基础

UDP特点

   无连接,面向数据报(基于消息,不会粘包)的数据传输服务;

   不可靠(可能会丢包, 乱序, 重复), 但因此一般情况下UDP更加高效;


UDP客户/服务器模型

 

 

UDP-API使用

  1. #include <sys/types.h>  
  2. #include <sys/socket.h>  
  3. ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,  
  4.                  struct sockaddr *src_addr, socklen_t *addrlen);  
  5. ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,  
  6.                const struct sockaddr *dest_addr, socklen_t addrlen);  

  1. /**实践: 实现一个基于UDP的echo回声server/client**/  
  2. //server端代码  
  3. void echoServer(int sockfd);  
  4. int main()  
  5. {  
  6.     int sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
  7.     if (sockfd == -1)  
  8.         err_exit("socket error");  
  9.   
  10.     struct sockaddr_in servAddr;  
  11.     servAddr.sin_family = AF_INET;  
  12.     servAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  13.     servAddr.sin_port = htons(8001);  
  14.     if (bind(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)  
  15.         err_exit("bind error");  
  16.   
  17.     echoServer(sockfd);  
  18. }  
  19. void echoServer(int sockfd)  
  20. {  
  21.     char buf[BUFSIZ];  
  22.     ssize_t recvBytes = 0;  
  23.     struct sockaddr_in clientAddr;  
  24.     socklen_t addrLen;  
  25.     while (true)  
  26.     {  
  27.         memset(buf, 0, sizeof(buf));  
  28.         addrLen = sizeof(clientAddr);  
  29.         memset(&clientAddr, 0, addrLen);  
  30.         recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0,  
  31.                              (struct sockaddr *)&clientAddr, &addrLen);  
  32.         //如果recvBytes=0, 并不代表对端连接关闭, 因为UDP是无连接的  
  33.         if (recvBytes < 0)  
  34.         {  
  35.             if (errno == EINTR)  
  36.                 continue;  
  37.             else  
  38.                 err_exit("recvfrom error");  
  39.         }  
  40.   
  41.         cout << buf ;  
  42.         if (sendto(sockfd, buf, recvBytes, 0,  
  43.                    (const struct sockaddr *)&clientAddr, addrLen) == -1)  
  44.             err_exit("sendto error");  
  45.     }  
  46. }  
  1. /**client端代码**/  
  2. void echoClient(int sockfd);  
  3. int main()  
  4. {  
  5.     int sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
  6.     if (sockfd == -1)  
  7.         err_exit("socket error");  
  8.     echoClient(sockfd);  
  9.     cout << "Client exiting..." << endl;  
  10. }  
  11. void echoClient(int sockfd)  
  12. {  
  13.     struct sockaddr_in servAddr;  
  14.     servAddr.sin_family = AF_INET;  
  15.     servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  16.     servAddr.sin_port = htons(8001);  
  17.     char buf[BUFSIZ] = {0};  
  18.     while (fgets(buf, sizeof(buf), stdin) != NULL)  
  19.     {  
  20.         if (sendto(sockfd, buf, strlen(buf), 0,  
  21.                    (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)  
  22.             err_exit("sendto error");  
  23.         memset(buf, 0, sizeof(buf));  
  24.         int recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);  
  25.         if (recvBytes == -1)  
  26.         {  
  27.             if (errno == EINTR)  
  28.                 continue;  
  29.             else  
  30.                 err_exit("recvfrom error");  
  31.         }  
  32.         cout << buf ;  
  33.         memset(buf, 0, sizeof(buf));  
  34.     }  
  35. }  

实践解析:编译运行server,在两个终端里各开一个client与server交互,可以看到server具有并发服务的能力。用<Ctrl+C>关闭server,然后再运行server,此时client还能和server联系上。和前面TCP程序的运行结果相比较,我们可以体会无连接的含义。udp 协议来说,server与client 的界限更模糊了,只要知道对等方地址(ip和port) 都可以主动发数据。

 

UDP编程注意事项

   1.UDP报文可能会丢失(超时重传)、重复、乱序(维护一个序号)

   2.UDP缺乏流量控制:当缓冲区写满以后,由于UDP没有流量控制机制,因此会覆盖缓冲区。

   3.UDP协议数据报文截断:如果对端发送的UDP数据报大于本地接收缓冲区,报文可能被截断,后面的部分会丢失(而不是像我们想象的下一次能够接收到)。

   4.recvfrom可以返回0,并不代表连接关闭,因为UDP是无连接的, 代表发送端没有发送任何数据[sendto可以发送数据0包(只含有UDP+IP首部40B)]。

   5.ICMP异步错误

      观察现象:使用上例,关闭UDP服务端,启动客户端,从键盘接受数据后,再发送数据。如果recvfrom中flags标志为0, 且client端没有调用connect的情况下, UDP客户端阻塞在recvfrom位置(见测试代码3);

      说明:

         1)UDP发送报文的时,只把数据copy到发送缓冲区。在服务器没有起来的情况下,可以发送成功

         2)所谓ICMP异步错误是指:发送的报文的时候,没有错误,接受报文recvfrom的时候,回收到ICMP应答.

         3)异步的错误,无法返回未连接的套接字, 因此如果上例我们调用了connect, 是可以收到该异步ICMP报文的;

   6.UDP调用connect

      1)UDP调用connet,并没有三次握手,只是维护了一个(和对等方的)状态信息, 因此我们可以看到即使server没有开启, client端的connect依然还可以正确返回的!(测试代码如测试代码2)

      2)一但调用connect, 发送可以使用send/write, 接收可以使用recv/read函数(见测试代码3)

   7.UDP外出接口的确定:

      假设客户端有多个IP地址,由connect /sendto 函数提供的远程地址的参数,系统会选择一个合适的出口,比如Server的IP是192.168.2.10, 而客户端现在的IP有 192.168.1.32 和 192.168.2.75 那么会自动选择192.168.2.75 这个IP出去。

  1. /**测试1: 测试注意点3, UDP报文截断, recvfrom返回-1, errno值为EAGAIN**/  
  2. int main()  
  3. {  
  4.     int sockfd = socket(AF_INET, SOCK_DGRAM, 0);  
  5.     if (sockfd == -1)  
  6.         err_exit("socket error");  
  7.   
  8.     struct sockaddr_in servAddr;  
  9.     servAddr.sin_family = AF_INET;  
  10.     servAddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  11.     servAddr.sin_port = htons(8001);  
  12.     if (bind(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)  
  13.         err_exit("bind error");  
  14.     //给自己发送数据  
  15.     if (sendto(sockfd, "ABCDE", 5, 0,  
  16.                (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)  
  17.         err_exit("sendto error");  
  18.   
  19.     for (int i = 0; i < 5; ++i)  
  20.     {  
  21.         char ch;  
  22.         int recvBytes =  recvfrom(sockfd, &ch, 1, MSG_DONTWAIT, NULL, NULL);  
  23.         if (recvBytes == -1)  
  24.         {  
  25.             if (errno == EINTR)  
  26.                 continue;  
  27.             else if (errno == EAGAIN)  
  28.                 err_exit("recvfrom error");  
  29.         }  
  30.         else  
  31.             cout << "char = " << ch << ", recvBytes = " << recvBytes << endl;  
  32.     }  
  33. }  
  1. /**测试2:将client端echoClient函数的代码改造如下, 注意是在server端尚未开启时执行该程序**/  
  2. void echoClient(int sockfd)  
  3. {  
  4.     struct sockaddr_in servAddr;  
  5.     servAddr.sin_family = AF_INET;  
  6.     servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  7.     servAddr.sin_port = htons(8001);  
  8.     // UDP client端调用connect  
  9.     if (connect(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)  
  10.         err_exit("connect error");  
  11.   
  12.     char buf[BUFSIZ] = {0};  
  13.     while (fgets(buf, sizeof(buf), stdin) != NULL)  
  14.     {  
  15.         if (sendto(sockfd, buf, strlen(buf), 0,  
  16.                    (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)  
  17.             err_exit("sendto error");  
  18.         memset(buf, 0, sizeof(buf));  
  19.         int recvBytes = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);  
  20.         if (recvBytes == -1)  
  21.             err_exit("recvfrom error");  
  22.         cout << buf ;  
  23.         memset(buf, 0, sizeof(buf));  
  24.     }  
  25. }  
  1. /**测试3: client端在调用connect之后调用send, 而不是send**/  
  2. void echoClient(int sockfd)  
  3. {  
  4.     struct sockaddr_in servAddr;  
  5.     servAddr.sin_family = AF_INET;  
  6.     servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");  
  7.     servAddr.sin_port = htons(8001);  
  8.     // UDP client端调用connect  
  9.     if (connect(sockfd, (const struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)  
  10.         err_exit("connect error");  
  11.   
  12.     char buf[BUFSIZ] = {0};  
  13.     while (fgets(buf, sizeof(buf), stdin) != NULL)  
  14.     {  
  15.         if (send(sockfd, buf, strlen(buf), 0) == -1)  
  16.             err_exit("send error");  
  17.         memset(buf, 0, sizeof(buf));  
  18.         int recvBytes = recv(sockfd, buf, sizeof(buf), 0);  
  19.         if (recvBytes == -1)  
  20.             err_exit("recv error");  
  21.         cout << buf ;  
  22.         memset(buf, 0, sizeof(buf));  
  23.     }  
  24. }  
发布了52 篇原创文章 · 获赞 10 · 访问量 9万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章