linuxudp数据接收数据
‘壹’ 使用recvfrom接收UDP包在Windows和Linux平台的不同表现
1 UDP接收原理
操作系统的UDP接收流程如下:收到一个UDP包后,验证没有错误后,放入一个包队列中,队列中的每一个元素就是一个完整的UDP包。当应用程序通过recvfrom()读取时,OS把相应的一个完整UDP包取出,然后拷贝到用户提供的内存中,物理用户提供的内存大小是多少,OS都会完整取出一个UDP包。如果用户提供的内存小于这个UDP包的大小,那么在填充慢内存后,UDP包剩余的部分就会被丢弃,以后再也无法取回。
这与TCP接收完全不同,TCP没有完整包的概念,也没有边界,OS只会取出用户要求的大小,剩余的仍然保留在OS中,下次还可以继续取出。
socket编程虽然是事实上的标准,而且不同平台提供的接口函数也非常类似,但毕竟它不存在严格的标准。所以各个平台的实现也不完全兼容。下面就从recvfrom()这个函数看看Window平台和Linux平台的不同。
2 Windows平台的表现
先看头文件中的声明:
[cpp] view plain在CODE上查看代码片派生到我的代码片
int
WSAAPI
recvfrom(
_In_ SOCKET s,
_Out_writes_bytes_to_(len, return) __out_data_source(NETWORK) char FAR * buf,
_In_ int len,
_In_ int flags,
_Out_writes_bytes_to_opt_(*fromlen, *fromlen) struct sockaddr FAR * from,
_Inout_opt_ int FAR * fromlen
);
再看MSDN说明:
If the datagram or message is larger than the buffer specified, the buffer is filled with the first part of the datagram, and recvfrom generates the error WSAEMSGSIZE. For unreliable protocols (for example, UDP) the excess data is lost.
可以看出,buf大小小于UDP包大小的时候,recvfrom()会返回-1,并设置错误WSAEMSGSIZE。
实际编程测试验证确实是这样的表现。
3 Linux平台的表现
先看头文件中的声明:
[cpp] view plain在CODE上查看代码片派生到我的代码片
__extern_always_inline ssize_t
recvfrom (int __fd, void *__restrict __buf, size_t __n, int __flags,
__SOCKADDR_ARG __addr, socklen_t *__restrict __addr_len)
可以看出与Windows平台的函数原型相同。但是在其man手册里,没有看到UDP包大于接收缓冲区情况的特殊说明。
写代码测试表明,buf小于UDP包大小的时候,recvfrom()仍然返回复制到缓冲区的字节数,调用者无法得知UDP包被截断的情况。
4 写代码注意事项
UDP包最大是多大呢?UDP头部大小字段占16字节,所以理论上是65535个字节大小。但是UDP如果是通过IP(大多数情况)来传送,由于UDP本身不支持分片,所以一个UDP包只能通过一个IP包来传送,一个IP包大大小理论上也是用16字节表示,这样UDP最大大小就是(65535-IP头部)。
而现实中如果IP包大小大于底层链路层帧的最大数据区大小,则必须对IP包进行分片传送。分片会严重影响传送效率,而且增大不稳定性,所以实际的网络程序发送的IP包都封装到单一的链路层帧中,从而避免分片。问题是链路层帧是多大呢?答案是不一定,因为不同的物理网络的帧大小不一样,如以太网是1500字节,但是其他物理网络可能更小,Internet上的有个最小的限制,那就是576字节。如果UDP程序运行在只运行在以太网中,那么为了避免IP分片,可以采用的最大大小为(1500-20-8)=1472字节。如果UDP程序需要运行在Internet上,那么建议最大大小为(576-20-8)=548字节。
上面是实践中的最佳UDP大小,但是并不是所有程序都采用上述经验,所以对于接收缓冲区的大小也就没有一个标准,而是取决于应用程序设计者本身。虽然对于Windows平台,recvfrom()能够提示调用者buf过小的问题,但是即使得到了这个错误,包还是被丢弃了。所以在接收UDP包时,一定要事先了解应用层设计的最大UDP包大小,然后按照最大值开辟接收缓冲区。
‘贰’ Linux网络 - 数据包在内核中接收和发送的过程(转)
本文将介绍在Linux系统中, 数据包是如何一步一步从网卡传到进程手中的 以及 数据包是如何一步一步从应用程序到网卡并最终发送出去的 。
如果英文没有问题,强烈建议阅读后面参考里的文章,里面介绍的更详细。
本文只讨论以太网的物理网卡,不涉及虚拟设备,并且以一个UDP包的接收过程作为示例.
网卡需要有驱动才能工作,驱动是加载到内核中的模块,负责衔接网卡和内核的网络模块,驱动在加载的时候将自己注册进网络模块,当相应的网卡收到数据包时,网络模块会调用相应的驱动程序处理数据。
下图展示了数据包(packet)如何进入内存,并被内核的网络模块开始处理:
软中断会触发内核网络模块中的软中断处理函数,后续流程如下
由于是UDP包,所以第一步会进入IP层,然后一级一级的函数往下调:
应用层一般有两种方式接收数据,一种是recvfrom函数阻塞在那里等着数据来,这种情况下当socket收到通知后,recvfrom就会被唤醒,然后读取接收队列的数据;另一种是通过epoll或者select监听相应的socket,当收到通知后,再调用recvfrom函数去读取接收队列的数据。两种情况都能正常的接收到相应的数据包。
了解数据包的接收流程有助于帮助我们搞清楚我们可以在哪些地方监控和修改数据包,哪些情况下数据包可能被丢弃,为我们处理网络问题提供了一些参考,同时了解netfilter中相应钩子的位置,对于了解iptables的用法有一定的帮助,同时也会帮助我们后续更好的理解Linux下的网络虚拟设备。
ndo_start_xmit会绑定到具体网卡驱动的相应函数,到这步之后,就归网卡驱动管了,不同的网卡驱动有不同的处理方式,这里不做详细介绍,其大概流程如下:
在网卡驱动发送数据包过程中,会有一些地方需要和netdevice子系统打交道,比如网卡的队列满了,需要告诉上层不要再发了,等队列有空闲的时候,再通知上层接着发数据。
‘叁’ 基于Linux的远程指令系统(使用udp而不是tcp)
一. Linux下UDP编程框架
使用UDP进行程序设计可以分为客户端和服务器端两部分。
1.服务器端程序包括:
? 建立套接字
? 将套接字地址结构进行绑定
? 读写数据
? 关闭套接字
2.客户端程序包括:
? 建立套接字
? 读写数据
? 关闭套接字
3.服务器端和客户端程序之间的差别
服务器端和客户端两个流程之间的主要差别在于对地址的绑定函数(bind()函数),而客户端可以不用进行地址和端口的绑定操作。
二.Linux中UDP套接字函数
从图可知,UDP协议的服务端程序设计的流程分为套接字建立,套接字与地址结构进行绑定,收发数据,关闭套接字;客户端程序流程为套接字建立,收发数据,关闭套接字等过程。它们分别对应socket(),bind(),sendto(),recvfrom(),和close()函数。
网络程序通过调用socket()函数,会返回一个用于通信的套接字描述符。Linux应用程序在执行任何形式的I/O操作的时候,程序是在读或者写一个文件描述符。因此,可以把创建的套接字描述符看成普通的描述符来操作,并通过读写套接字描述符来实现网络之间的数据交流。
1. socket
1> 函数原型:
int socket(int domain,int type,int protocol)
2> 函数功能:
函数socket()用于创建一个套接字描述符。
3> 形参:
? domain:用于指定创建套接字所使用的协议族,在头文件
中定义。
常见的协议族如下:
AF_UNIX:创建只在本机内进行通信的套接字。
AF_INET:使用IPv4 TCP/IP协议
AF_INET6:使用IPv6 TCP/IP协议
说明:
AF_UNIX只能用于单一的UNIX系统进程间通信,而AF_INET是针对Interne的,因而可以允许在远程主机之间通信。一般把它赋为AF_INET。
? type:指明套接的类型,对应的参数如下
SOCK_STREAM:创建TCP流套接字
SOCK_DGRAM:创建UDP数据报套接字
SOCK_RAW:创建原始套接字
? protocol:
参数protocol通常设置为0,表示通过参数domain指定的协议族和参数type指定的套接字类型来确定使用的协议。当为原始套接字时,系统无法唯一的确定协议,此时就需要使用使用该参数指定所使用的协议。
4> 返回值:执行成功后返回一个新创建的套接字;若有错误发生则返回一个-1,错误代码存入errno中。
5> 举例:调用socket函数创建一个UDP套接字
int sock_fd;
sock_fd = socket(AF_INET,SOCK_DGRAM,0);
if(sock_fd < 0){
perror(“socket”);
exit(1);
}
2. bind
1> 函数原型:
int bind(int sockfd,struct sockaddr *my_addr,socklen_taddrlen)
2> 函数功能
函数bind()的作用是将一个套接字文件描述符与一个本地地址绑定在一起。
3> 形参:
? sockfd:sockfd是调用socket函数返回的文件描述符;
? addrlen是sockaddr结构的长度。
? my_addr: 是一个指向sockaddr结构的指针,它保存着本地套接字的地址(即端口和IP地址)信息。不过由于系统兼容性的问题,一般不使用这个结构,而使用另外一个结构(struct sockaddr_in)来代替
4> 套接字地址结构:
(1)structsockaddr:
结构struct sockaddr定义了一种通用的套接字地址,它在
Linux/socket.h 中定义。
struct sockaddr{
unsigned short sa_family;/*地址类型,AF_XXX*/
char sa_data[14];/*14字节的协议地址*/
}
a. sin_family:表示地址类型,对于使用TCP/IP协议进行的网络编程,该值只能是AF_INET.
b. sa_data:存储具体的协议地址。
(2)sockaddr_in
每种协议族都有自己的协议地址格式,TCP/IP协议组的地址格式为结构体struct sockaddr_in,它在netinet/in.h头文件中定义。
struct sockaddr_in{
unsigned short sin_family;/*地址类型*/
unsigned short sin_port;/*端口号*/
struct in_addr sin_addr;/*IP地址*/
unsigned char sin_zero[8];/*填充字节,一般赋值为0*/
}
a. sin_family:表示地址类型,对于使用TCP/IP协议进行的网络编程,该值只能是AF_INET.
b. sin_port:是端口号
c. sin_addr:用来存储32位的IP地址。
d. 数组sin_zero为填充字段,一般赋值为0.
e. structin_addr的定义如下:
struct in_addr{
unsignedlong s_addr;
}
结构体sockaddr的长度为16字节,结构体sockaddr_in的长度为16字节。可以将参数my_addr的sin_addr设置为INADDR_ANY而不是某个确定的IP地址就可以绑定到任何网络接口。对于只有一IP地址的计算机,INADDR_ANY对应的就是它的IP地址;对于多宿主主机(拥有多个网卡),INADDR_ANY表示本服务器程序将处理来自所有网络接口上相应端口的连接请求
5> 返回值:
函数成功后返回0,当有错误发生时则返回-1,错误代码存入errno中。
6>举例:调用socket函数创建一个UDP套接字
struct sockaddr_in addr_serv,addr_client;/*本地的地址信息*/
memset(&serv_addr,0,sizeof(struct sockaddr_in));
addr_serv.sin_family = AF_INET;/*协议族*/
addr_serv.sin_port = htons(SERV_PORT);/*本地端口号*/
addr_serv.sin_addr.s_addr = htonl(INADDR_ANY); /*任意本地地址*/
/*套接字绑定*/
if(bind(sock_fd,(struct sockaddr *)&addr_serv),sizeof(structsockaddr_in)) <0)
{
perror(“bind”);
exit(1);
}
3.close
1>函数原型:
int close(intfd);
2>函数功能:
函数close用来关闭一个套接字描述符。
3>函数形参:
? 参数fd为一个套接字描述符。
4>返回值:
执行成功返回0,出错则返回-1.错误代码存入errno中。
说明:
以上三个函数中,前两个要包含头文件
#include
#include
后一个包含:
#include
4.sendto
1>函数原型:
#include
#include
ssize_t sendo(ints,const void *msg,size_t len,int flags,const struct sockaddr *to,socklen_ttolen);
2>函数功能:
向目标主机发送消息
3>函数形参:
? s:套接字描述符。
? *msg:发送缓冲区
? len:待发送数据的长度
? flags:控制选项,一般设置为0或取下面的值
(1)MSG_OOB:在指定的套接字上发送带外数据(out-of-band data),该类型的套接字必须支持带外数据(eg:SOCK_STREAM).
(2)MSG_DONTROUTE:通过最直接的路径发送数据,而忽略下层协议的路由设置。
? to:用于指定目的地址
? tolen:目的地址的长度。
4>函数返回值:
执行成功后返回实际发送数据的字节数,出错返回-1,错误代码存入errno中。
5>函数举例:
char send_buf[BUFFERSIZE];
struct sockaddr_in addr_client;
memset(&addr_client,0,sizeof(struct sockaddr_in));
addr_client.sin_family = AF_INET;
addr_client.sin_port = htons(DEST_PORT);
if(inet_aton(“172.17.242.131”,&addr_client.sin_addr)<0){
perror(“inet_aton”);
exit(1);
}
if(sendto(sock_fd,send_buf,len,0,(strut sockaddr*)&addr_client,sizeof(struct sockaddr_in)) <0){
perror(“sendto”);
exit(1);
}
5.recvfrom
1>函数原型:
#include
#include
ssize_t recvfrom(int s,void *buf,size_t len,intflags,struct sockaddr *from,socklen_t *fromlen);
2>函数功能:接收数据
3>函数形参:
? int s:套接字描述符
? buf:指向接收缓冲区,接收到的数据将放在这个指针所指向的内存空间。
? len:指定了缓冲区的大小。
? flags:控制选项,一般设置为0或取以下值
(1)MSG_OOB:请求接收带外数据
(2)MSG_PEEK:只查看数据而不读出
(3)MSG_WAITALL:只在接收缓冲区时才返回。
? *from:保存了接收数据报的源地址。
? *fromlen:参数fromlen在调用recvfrom前为参数from的长度,调用recvfrom后将保存from的实际大小。
4>函数返回值:
执行成功后返回实际接收到数据的字节数,出错时则返回-1,错误代码存入errno中。
5>函数实例:
char recv_buf[BUFFERSIZE];
struct sockaddr_in addr_client;
int src_len;
src_len = sizeof(struct sockaddr_in);
int src_len;
src_len = sizeof(struct sockaddr_in);
if(recvfrom(sock_fd,recv_buf,sizeof(recv_buf),0,(structsockaddr *)&src_addr,&src_len)<0){
perror(“again_recvfrom”);
exit(1);
}
三.UDP编程实例
客户端向服务器发送字符串Hello tiger,服务器接收到数据后将接收到字符串发送回客户端。
1.服务器端程序
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9
10 #define SERV_PORT 3000
11
12 int main()
13 {
14 int sock_fd; //套接子描述符号
15 int recv_num;
16 int send_num;
17 int client_len;
18 char recv_buf[20];
19 struct sockaddr_in addr_serv;
20 struct sockaddr_in addr_client;//服务器和客户端地址
21 sock_fd = socket(AF_INET,SOCK_DGRAM,0);
22 if(sock_fd < 0){
23 perror("socket");
24 exit(1);
25 } else{
26
27 printf("sock sucessful\n");
28 }
29 //初始化服务器断地址
30 memset(&addr_serv,0,sizeof(struct sockaddr_in));
31 addr_serv.sin_family = AF_INET;//协议族
32 addr_serv.sin_port = htons(SERV_PORT);
33 addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);//任意本地址
34
35 client_len = sizeof(struct sockaddr_in);
36 /*绑定套接子*/
37 if(bind(sock_fd,(struct sockaddr *)&addr_serv,sizeof(struct sockaddr_in))<0 ){
38 perror("bind");
39 exit(1);
40 } else{
41
42 printf("bind sucess\n");
43 }
44 while(1){
45 printf("begin recv:\n");
46 recv_num = recvfrom(sock_fd,recv_buf,sizeof(recv_buf),0,(struct sockaddr *)&addr_client,&client_len);
47 if(recv_num < 0){
48 printf("bad\n");
49 perror("again recvfrom");
50 exit(1);
51 } else{
52 recv_buf[recv_num]='\0';
53 printf("recv sucess:%s\n",recv_buf);
54 }
55 printf("begin send:\n");
56 send_num = sendto(sock_fd,recv_buf,recv_num,0,(struct sockaddr *)&addr_client,client_len);
57 if(send_num < 0){
58 perror("sendto");
59 exit(1);
60 } else{
61 printf("send sucessful\n");
62 }
63 }
64 close(sock_fd);
65 return 0;
66 }
2.客户端程序
1 #include
2 #include
3 #include
4 #include
5 #include
6
7 #include
8 #include
9 #include
10
11 #define DEST_PORT 3000
12 #define DSET_IP_ADDRESS "192.168.1.103"
13
14 int main()
15 {
16 int sock_fd;/*套接字文件描述符*/
17 int send_num;
18 int recv_num;
19 int dest_len;
20 char send_buf[20]={"hello tiger"};
21 char recv_buf[20];
22 struct sockaddr_in addr_serv;/*服务端地址,客户端地址*/
23
24 sock_fd = socket(AF_INET,SOCK_DGRAM,0);//创建套接子
25 //初始化服务器端地址
26 memset(&addr_serv,0,sizeof(addr_serv));
27 addr_serv.sin_family = AF_INET;
28 addr_serv.sin_addr.s_addr = inet_addr(DSET_IP_ADDRESS);
29 addr_serv.sin_port = htons(DEST_PORT);
30
31 dest_len = sizeof(struct sockaddr_in);
32 printf("begin send:\n");
33 send_num = sendto(sock_fd,send_buf,sizeof(send_buf),0,(struct sockaddr *)&addr_serv,dest_len);
34 if(send_num < 0){
35 perror("sendto");
36 exit(1);
37 } else{
38
39 printf("send sucessful:%s\n",send_buf);
40 }
41 recv_num = recvfrom(sock_fd,recv_buf,sizeof(recv_buf),0,(struct sockaddr *)&addr_serv,&dest_len);
42 if(recv_num <0 ){
43
44 perror("recv_from");
45 exit(1);
46 } else{
47 printf("recv sucessful\n");
48 }
49 recv_buf[recv_num]='\0';
50 printf("the receive:%s\n",recv_buf);
51 close(sock_fd);
52 return 0;
53 }