之前的网络编程主要针对的是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作用:
端口复用。
setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
设置缓冲区大小
int n = 220x1024
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));开放广播权限
setsockopt(sock_fd,SOL_SOCKET,SO_BROADCAST,&flag,sizeof(flag));
开放组播权限
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group));
加入组播组。
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 ad
1: 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 forever
2: 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;
}