网络编程中的UDP通信

来源:互联网 发布:淘宝买东西物流不更新 编辑:程序博客网 时间:2024/05/17 02:47

之前的网络编程主要针对的是TCP协议,现在总结一下学习UDP协议的知识点!

基本概念

  传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议。TCP协议在网络通信中占主导地位,绝大多数的网络通信借助TCP协议完成数据传输。但UDP也是网络通信中不可或缺的重要通信手段。
  相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和正确率便得不到保证。因此,我们称UDP为“无连接的不可靠报文传递”。
  那么与我们熟知的TCP相比,UDP有哪些优点和不足呢?由于无需创建连接,所以UDP**开销较小,数据传输速度快实时性较强。多用于对实时性要求较高的通信场合,如视频会议、电话会议等。但随之也伴随着数据传输不可靠,传输数据的正确率、传输顺序和流量都得不到控制和保证。所以,通常情况下,使用UDP协议进行数据传输,为保证数据的正确性,我们需要在应用层添加辅助校验协议来弥补UDP的不足**,以达到数据可靠传输的目的。
  与TCP类似的,UDP也有可能出现缓冲区被填满后,再接收数据时丢包的现象。由于它没有TCP滑动窗口的机制,通常采用如下两种方法解决:
  1) 服务器应用层设计流量控制,控制发送数据速度。
  2) 借助setsockopt函数改变接收缓冲区大小。如:

#include <sys/socket.h>int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);    int n = 220x1024    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

特点

UDP的特点

  • 无连接
  • 基于消息的数据传输服务,不是字节流
  • 不可靠
  • 一般情况下UDP更加高效

常用API

recvfrom函数

  • 相关函数:recv, recvmsg, send, sendto, socket

  • 头文件:#include <sys/types.h> #include <sys/socket.h>

  • 定义函数:int recvfrom(int s, void *buf, int len, unsigned int flags, struct sockaddr *from,int *fromlen);

  • 函数说明:recv()用来接收远程主机经指定的socket 传来的数据, 并把数据存到由参数buf 指向的内存空间, 参数len 为可接收数据的最大长度. 参数flags 一般设0, 其他数值定义请参考recv(). 参数from 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). 参数fromlen 为sockaddr 的结构长度.

  • 返回值:成功则返回接收到的字符数, 失败则返回-1, 错误原因存于errno 中.

  • 错误代码:
    EBADF 参数s 非合法的socket 处理代码
    EFAULT 参数中有一指针指向无法存取的内存空间.
    ENOTSOCK 参数s 为一文件描述词, 非socket.
    EINTR 被信号所中断.
    EAGAIN 此动作会令进程阻断, 但参数s 的socket 为不可阻断.
    ENOBUFS 系统的缓冲内存不足
    ENOMEM 核心内存不足
    EINVAL 传给系统调用的参数不正确.

sendto函数

  • 相关函数:send, sendmsg, recv, recvfrom, socket

  • 头文件:#include <sys/types.h> #include <sys/socket.h>

  • 定义函数:int sendto(int s, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tolen);

  • 函数说明:sendto() 用来将数据由指定的socket 传给对方主机. 参数s 为已建好连线的socket, 如果利用UDP协议则不需经过连线操作. 参数msg 指向欲连线的数据内容, 参数flags 一般设0, 详细描述请参考send(). 参数to 用来指定欲传送的网络地址, 结构sockaddr 请参考bind(). 参数tolen 为sockaddr 的结果长度.

  • 返回值:成功则返回实际传送出去的字符数, 失败返回-1, 错误原因存于errno 中.

  • 错误代码:
    1、EBADF 参数s 非法的socket 处理代码.
    2、EFAULT 参数中有一指针指向无法存取的内存空间.
    3、WNOTSOCK canshu s 为一文件描述词, 非socket.
    4、EINTR 被信号所中断.
    5、EAGAIN 此动作会令进程阻断, 但参数s 的soket 为补课阻断的.
    6、ENOBUFS 系统的缓冲内存不足.
    7、EINVAL 传给系统调用的参数不正确.

setsockopt作用

  1. 端口复用。
    setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));

  2. 设置缓冲区大小
    int n = 220x1024
    setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));

  3. 开放广播权限
    setsockopt(sock_fd,SOL_SOCKET,SO_BROADCAST,&flag,sizeof(flag));

  4. 开放组播权限
    setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group));

  5. 加入组播组。
    setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));

关于设置组播地址的API和结构体

  • inet_pton–man命令查看帮助信息

  • struct ip_mreqn group;–grep命令查看定义

UDP会用到的其他函数

  • bind
  • connect
  • send
    可以参考前面TCP相关的介绍和man文档

UDP经典的C/S模型

这里写图片描述

  由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,保证通讯可靠性的机制需要在应用层实现。
  编译运行server,在两个终端里各开一个client与server交互,看看server是否具有并发服务的能力。用Ctrl+C关闭server,然后再运行server,看此时client还能否和server联系上。和前面TCP程序的运行结果相比较,体会无连接的含义。

  • 客户端程序
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <ctype.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#define MAX_BUF 80#define SRV_PORT 8001 int main(){  int ret = 0;  struct sockaddr_in srv_addr;  int sock_fd;  char buf[MAX_BUF]={0};  socklen_t addr_len;  //创建UDP套接字  sock_fd = socket(AF_INET,SOCK_DGRAM,0);  //填充服务器地址  bzero(&srv_addr,sizeof(srv_addr));  srv_addr.sin_family = AF_INET;  srv_addr.sin_port = htons(SRV_PORT);  srv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //获取地址长度  addr_len = sizeof(srv_addr);  //从标准输入获取数据发送给服务器并等待服务器返回数据  while(fgets(buf,sizeof(buf),stdin) != NULL)  {      addr_len = sizeof(srv_addr);      //sendto第一次发送的时候,会绑定地址,需要指定对方的地址    ret = sendto(sock_fd,buf,strlen(buf),0,(struct sockaddr*)&srv_addr,sizeof(srv_addr));//开始发送    if(ret == -1)    {      perror("sendto");      exit(-1);    }    ret = recvfrom(sock_fd,buf,sizeof(buf),0,(struct sockaddr*)&srv_addr,&addr_len);//等待服务器反馈数据    if(ret == -1)    {      if (errno == EINTR)            continue;      perror("recvfrom");      exit(-1);    }    printf("recv from IP:%s,IP:%d\n",inet_ntoa(srv_addr.sin_addr),ntohs(srv_addr.sin_port));    fputs(buf,stdout);//打印接收到的数据    memset(buf,0,sizeof(buf));//清空缓冲区  }  return 0;}
  • 服务器端程序
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <ctype.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#define MAX_BUF 80#define SRV_PORT 8001 int main(){  struct sockaddr_in srv_addr,clt_addr;  socklen_t addr_len;  int sock_fd;  int ret = 0;  int i= 0;  char buf[MAX_BUF]={0};  sock_fd = socket(AF_INET,SOCK_DGRAM,0);  if(-1 == sock_fd)  {    perror("socket");    exit(-1);  }  bzero(&srv_addr,sizeof(srv_addr));  srv_addr.sin_family = AF_INET;  srv_addr.sin_port = htons(SRV_PORT);  srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  ret = bind(sock_fd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));  if(ret == -1)  {    perror("bind");    exit(-1);  }  addr_len = sizeof(clt_addr);  printf("Accepting connections ...\n");  while(1)  {      addr_len = sizeof(clt_addr);    memset(buf,0,sizeof(buf));    ret = recvfrom(sock_fd,buf,sizeof(buf),0,(struct sockaddr*)&clt_addr,&addr_len);    if(ret == -1)    {        if (errno == EINTR)              continue;      perror("recvfrom");      exit(-1);    }    printf("recv from IP:%s,IP:%d\n",inet_ntoa(clt_addr.sin_addr),ntohs(clt_addr.sin_port));//打印对方地址    fputs(buf,stdout);    for(i = 0;i < ret ;i++)      buf[i] = toupper(buf[i]);    ret = sendto(sock_fd,buf,ret,0,(struct sockaddr*)&clt_addr,sizeof(clt_addr));    if(-1 == ret)    {      perror("sendto");      exit(-1);    }  }    close(sock_fd);  return 0;}

UDP广播通信

  在一个局域网内,如果一台主机想给所有本地网络中的所有其他主机发送数据,可以使用广播,比如多媒体教学,视频会议这类!
  广播有特殊的IP地址,如果网关是192.168.1.1的话,广播地址就是192.168.1.255,套接字默认是没有开启广播权限的,需要使用setsockopt函数获取该权限。

  • 服务器端代码
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <ctype.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#define MAX_BUF 80#define SRV_PORT 8001 #define CLT_PORT 9000#define BRD_ADDR "192.168.1.255"int main(){  struct sockaddr_in srv_addr,clt_addr;  int sock_fd;  int ret = 0;  int i= 0;  int flag = 1;  char buf[MAX_BUF]={0};  sock_fd = socket(AF_INET,SOCK_DGRAM,0);  if(-1 == sock_fd)  {    perror("socket");    exit(-1);  }  bzero(&srv_addr,sizeof(srv_addr));  srv_addr.sin_family = AF_INET;  srv_addr.sin_port = htons(SRV_PORT);//服务器端可以是一个随机的端口--在不接收客户端反馈数据的情况下  srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //获取广播权限--默认没有开启  setsockopt(sock_fd,SOL_SOCKET,SO_BROADCAST|SO_REUSEADDR,&flag,sizeof(flag));  ret = bind(sock_fd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));  if(ret == -1)  {    perror("bind");    exit(-1);  }  //接收端的地址变得很重要--要指定接收端的端口号--双方端口要一致   bzero(&clt_addr,sizeof(clt_addr));  clt_addr.sin_family = AF_INET;  clt_addr.sin_port = htons(CLT_PORT);  clt_addr.sin_addr.s_addr = inet_addr(BRD_ADDR);  while(1)  {    sprintf(buf,"Fuck it %d times\n",i++);    ret = sendto(sock_fd,buf,strlen(buf),0,(struct sockaddr*)&clt_addr,sizeof(clt_addr));    if(-1 == ret)    {      perror("sendto");      exit(-1);    }      sleep(1);  }    close(sock_fd);  return 0;}
  • 客户端代码
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <ctype.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#define MAX_BUF 80#define SRV_PORT 8001 #define CLT_PORT 9000#define SRV_ADDR "127.0.0.1"int main(){  int ret = 0;  struct sockaddr_in local_addr,srv_addr;  int sock_fd;  char buf[MAX_BUF]={0};  socklen_t addr_len;  //创建UDP套接字  sock_fd = socket(AF_INET,SOCK_DGRAM,0);  //填充服务器地址  bzero(&local_addr,sizeof(local_addr));  local_addr.sin_family = AF_INET;  local_addr.sin_port = htons(CLT_PORT);  local_addr.sin_addr.s_addr = htonl(INADDR_ANY);   bzero(&srv_addr,sizeof(srv_addr));  ret = bind(sock_fd,(struct sockaddr *)&local_addr,sizeof(local_addr));  if(ret == -1)  {    perror("bind");    exit(-1);  }  printf("Waitting broadcast ...\n");  while(1)  {    ret = recvfrom(sock_fd,buf,sizeof(buf),0,(struct sockaddr*)&srv_addr,&addr_len);//等待服务器反馈数据    if(ret == -1)    {      if (errno == EINTR)            continue;      perror("recvfrom");      exit(-1);    }    printf("recv from IP:%s,IP:%d\n",inet_ntoa(srv_addr.sin_addr),ntohs(srv_addr.sin_port));    fputs(buf,stdout);//打印接收到的数据    memset(buf,0,sizeof(buf));//清空缓冲区  }  close(sock_fd);  return 0;}

UDP组播/多播通信

  组播出现的原因是广播有可能造成网络污染–局域网内有的主机并不想收到来自服务器的广播,只是某些个主机需要,所以需要对网络进行细分,进行分组管理分组发送数据,这样既高效又避免多余的数据包在网络里乱串,另一方面提高了服务器端组播数据的安全性。
  组播组可以是永久的也可以是临时的。组播组地址中,有一部分由官方分配的,称为永久组播组。永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。永久组播组中成员的数量都可以是任意的,甚至可以为零。那些没有保留下来供永久组播组使用的ip组播地址,可以被临时组播组利用。

224.0.0.0~224.0.0.255 为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
224.0.1.0~224.0.1.255 是公用组播地址,可以用于Internet;欲使用需申请。
224.0.2.0~238.255.255.255 为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效。

可使用ip ad命令查看网卡编号,如:

$ ip ad1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default     link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00    inet 127.0.0.1/8 scope host lo       valid_lft forever preferred_lft forever    inet6 ::1/128 scope host        valid_lft forever preferred_lft forever2: eth0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000    link/ether 00:0c:29:0a:c4:f4 brd ff:ff:ff:ff:ff:ff    inet6 fe80::20c:29ff:fe0a:c4f4/64 scope link        valid_lft forever preferred_lft forever

  if_nametoindex 函数可以根据网卡名,获取网卡序号。
  如果出现setsockopt“找不到设备“的情况,请参考:
  http://blog.csdn.net/dengjin20104042056/article/details/52357157
  以及http://blog.csdn.net/air_penguin/article/details/51350910
  如果是出现段错误(Core dump),一般用GDB调试可以发现问题所在,一般是在inet_pton(AF_INET,"0.0.0.0",&group.imr_address);//本地任意IP进行组播--向哪一个组播组发送数据出错,要注意要显示使用“0.0.0.0”,不能使用INADDR_ANY宏定义。
  
* 服务器端代码

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <ctype.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <net/if.h>#define MAX_BUF 80#define SRV_PORT 8001 #define CLT_PORT 9000#define GROUP "239.0.0.2"int main(){  struct sockaddr_in srv_addr,clt_addr;  int sock_fd;  int ret = 0;  int i= 0;  struct ip_mreqn group;  char buf[MAX_BUF]={0};  sock_fd = socket(AF_INET,SOCK_DGRAM,0);  if(-1 == sock_fd)  {    perror("socket");    exit(-1);  }  bzero(&srv_addr,sizeof(srv_addr));  srv_addr.sin_family = AF_INET;  srv_addr.sin_port = htons(SRV_PORT);//服务器端可以是一个随机的端口--在不接收客户端反馈数据的情况下  srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  ret = bind(sock_fd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));//绑定服务器地址  if(ret == -1)  {    perror("bind");    exit(-1);  }  inet_pton(AF_INET,GROUP,&group.imr_multiaddr);//设置组播组IP地址  inet_pton(AF_INET,"0.0.0.0",&group.imr_address);//本地任意IP进行组播--向哪一个组播组发送数据  group.imr_ifindex = if_nametoindex("eth0");//通过网卡名字获取网卡编号  //设置组播权限--后面两个参数变复杂了--要注意不是简单0或者1  setsockopt(sock_fd,IPPROTO_IP,IP_MULTICAST_IF,&group,sizeof(group));  //接收端的地址变得很重要--要指定接收端的端口号--双方端口要一致   bzero(&clt_addr,sizeof(clt_addr));  clt_addr.sin_family = AF_INET;  clt_addr.sin_port = htons(CLT_PORT);  clt_addr.sin_addr.s_addr = inet_addr(GROUP);  while(1)  {    sprintf(buf,"Fuck it %d times\n",i++);    ret = sendto(sock_fd,buf,strlen(buf),0,(struct sockaddr*)&clt_addr,sizeof(clt_addr));    if(-1 == ret)    {      perror("sendto");      exit(-1);    }      sleep(1);  }    close(sock_fd);  return 0;}
  • 客户端代码
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <ctype.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <net/if.h>#define MAX_BUF 80#define SRV_PORT 8001 #define CLT_PORT 9000#define GROUP "239.0.0.2"int main(){  int ret = 0;  struct sockaddr_in local_addr,srv_addr;  int sock_fd;  char buf[MAX_BUF]={0};  socklen_t addr_len;  struct ip_mreqn group;//组播IP+自己IP+网卡编号  //创建UDP套接字  sock_fd = socket(AF_INET,SOCK_DGRAM,0);  //填充服务器地址  bzero(&local_addr,sizeof(local_addr));  local_addr.sin_family = AF_INET;  local_addr.sin_port = htons(CLT_PORT);//强行指定端口号--否则服务器广播/组播以后不知道如何和对等方的进程通信--必须要端口相同  local_addr.sin_addr.s_addr = htonl(INADDR_ANY);  bzero(&srv_addr,sizeof(srv_addr));  //强行绑定本机地址--要明确自己的IP地址和端口才能进行广播和组播  //不同于TCP/IP或者简单的C/S模型,一旦涉及到广播和组播--必须要绑定自己的地址  ret = bind(sock_fd,(struct sockaddr *)&local_addr,sizeof(local_addr));  if(ret == -1)  {    perror("bind");    exit(-1);  }  inet_pton(AF_INET,GROUP,&group.imr_multiaddr);//设置组播组IP地址  inet_pton(AF_INET,"0.0.0.0",&group.imr_address);//使用本地/客户端IP添加到组播组--本地IP成为组播里面的一员--和服务器端不太一样  group.imr_ifindex = if_nametoindex("eth0");//通过网卡名字获取网卡编号  //设置client加入组播组--后面两个参数变复杂了--要注意不是简单0或者1  setsockopt(sock_fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&group,sizeof(group));  printf("Waitting multicast ...\n");  while(1)  {    ret = recvfrom(sock_fd,buf,sizeof(buf),0,(struct sockaddr*)&srv_addr,&addr_len);//等待服务器反馈数据    if(ret == -1)    {      if (errno == EINTR)            continue;      perror("recvfrom");      exit(-1);    }    printf("recv from IP:%s,IP:%d\n",inet_ntoa(srv_addr.sin_addr),ntohs(srv_addr.sin_port));    fputs(buf,stdout);//打印接收到的数据    memset(buf,0,sizeof(buf));//清空缓冲区  }  close(sock_fd);  return 0;}

UDP注意点

  • UDP报文可能会丢失、重复 、乱序
  • UDP缺乏流量控制
  • UDP协议数据报文截断
    如果接收到的数据报,大于缓冲区;报文可以被截断;后面的部分会丢失。
  • recvfrom返回0,不代表连接关闭,因为udp是无连接的。
  • sendto可以发送数据0包。。。只含有udp头部。
  • ICMP异步错误
    观察现象: 关闭udp服务端,若启动udp客户端,从键盘接受数据后,再发送数据。udp客户端阻塞在sendto位置;
    • 说明1:udp发送报文的时,只把数据copy到发送缓冲区。在服务器没有起来的情况下,可以发送成功。
    • 说明2:所谓ICMP异步错误是指:发送的报文的时候,没有错误,接受报文recvfrom的时候,会收到ICMP应答
    • 说明3:异步的错误,是无法返回未连接的套接字
  • udp也可以调用connect
    • 说明1:udp调用connet,并没有三次握手,只是维护了一个状态信息(和对等方的)。。。
    • 说明2)一但调用connect,就可以使用send函数

关于UDP使用connect调用send函数的示例代码

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <ctype.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#define MAX_BUF 80#define SRV_PORT 8001 int main(){  int ret = 0;  struct sockaddr_in srv_addr;  int sock_fd;  char buf[MAX_BUF]={0};  socklen_t addr_len;  //创建UDP套接字  sock_fd = socket(AF_INET,SOCK_DGRAM,0);  //填充服务器地址  bzero(&srv_addr,sizeof(srv_addr));  srv_addr.sin_family = AF_INET;  srv_addr.sin_port = htons(SRV_PORT);  srv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //获取地址长度  addr_len = sizeof(srv_addr);    //udp 也可以 调用connet    //udp调用connet,并没有三次握手,只是维护了一个状态信息(和对等方的)  connect(sock_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));  //从标准输入获取数据发送给服务器并等待服务器返回数据  while(fgets(buf,sizeof(buf),stdin) != NULL)  {      addr_len = sizeof(srv_addr);    //一但调用connect,就可以使用send函数        ret = send(sock_fd, buf, strlen(buf), 0);    //如果    connect 已经指定了对方的地址。        //sendto可以这样写 sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);    //ret = sendto(sock_fd, buf, strlen(buf), 0, NULL, 0);    //sendto第一次发送的时候,会绑定地址,需要指定对方的地址    //ret = sendto(sock_fd,buf,strlen(buf),0,(struct sockaddr*)&srv_addr,sizeof(srv_addr));//开始发送    if(ret == -1)    {      perror("sendto");      exit(-1);    }    ret = recvfrom(sock_fd,buf,sizeof(buf),0,(struct sockaddr*)&srv_addr,&addr_len);//等待服务器反馈数据    if(ret == -1)    {      if (errno == EINTR)            continue;      perror("recvfrom");      exit(-1);    }    printf("recv from IP:%s,IP:%d\n",inet_ntoa(srv_addr.sin_addr),ntohs(srv_addr.sin_port));    fputs(buf,stdout);//打印接收到的数据    memset(buf,0,sizeof(buf));//清空缓冲区  }  return 0;}

关于UDP不是基于字节流且缓冲区太小导致丢包的测试代码

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <errno.h>#include <unistd.h>#include <ctype.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#define SRV_PORT 8001 int main(){  struct sockaddr_in srv_addr;  int sock_fd;  int ret = 0;  int i= 0;  char rcv_buf[1] = {0};  sock_fd = socket(AF_INET,SOCK_DGRAM,0);  if(-1 == sock_fd)  {    perror("socket");    exit(-1);  }  bzero(&srv_addr,sizeof(srv_addr));  srv_addr.sin_family = AF_INET;  srv_addr.sin_port = htons(SRV_PORT);  srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  ret = bind(sock_fd,(struct sockaddr *)&srv_addr,sizeof(srv_addr));  sendto(sock_fd, "ABCD", 4, 0, (struct sockaddr*)&srv_addr, sizeof(srv_addr));//自己给自己发送四个字节“ABCD”  for(i = 0;i < 4;i++)//  {    //由于给定的缓冲区是1个字节--recvfrom指定的缓冲区大小--第三个参数    //如果数据包的实际大小大于recvfrom的第三个参数,则指定大小后面的数据将会被丢弃    //虽然发送到个数据包有4个字节,但是第三个参数指定的缓冲区大小只是一个字节,所以后面三个字节被内核抛弃--协议栈缓冲区已经没有数据可读    ret = recvfrom(sock_fd,rcv_buf,sizeof(rcv_buf),0,NULL,NULL);//在第二次循环的时候阻塞--因为没有数据可读了    if(-1 == ret)    {      if(errno == EINTR)        continue;      perror("recvfrom");      exit(-1);    }    printf("ret = %d\tbuf:%c\n",ret,rcv_buf[0]);//只能打印第一个字节  }  close(sock_fd);  return 0;}
0 0