首页建站recvfrom(Linux中使用recvfrom函数接收数据)

recvfrom(Linux中使用recvfrom函数接收数据)

编程之家2024-02-06102次浏览

一、使用recvfrom接收UDP包在Windows和Linux平台的不同表现

1 UDP接收原理

recvfrom(Linux中使用recvfrom函数接收数据)

操作系统的UDP接收流程如下:收到一个UDP包后,验证没有错误后,放入一个包队列中,队列中的每一个元素就是一个完整的UDP包。当应用程序通过recvfrom()读取时,OS把相应的一个完整UDP包取出,然后拷贝到用户提供的内存中,物理用户提供的内存大小是多少,OS都会完整取出一个UDP包。如果用户提供的内存小于这个UDP包的大小,那么在填充慢内存后,UDP包剩余的部分就会被丢弃,以后再也无法取回。

这与TCP接收完全不同,TCP没有完整包的概念,也没有边界,OS只会取出用户要求的大小,剩余的仍然保留在OS中,下次还可以继续取出。

socket编程虽然是事实上的标准,而且不同平台提供的接口函数也非常类似,但毕竟它不存在严格的标准。所以各个平台的实现也不完全兼容。下面就从recvfrom()这个函数看看Window平台和Linux平台的不同。

2 Windows平台的表现

recvfrom(Linux中使用recvfrom函数接收数据)

先看头文件中的声明:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片

int

WSAAPI

recvfrom(

_In_ SOCKET s,

recvfrom(Linux中使用recvfrom函数接收数据)

_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 plaincopy在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的远程指令系统(使用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}

三、send的Linux C 函数

经套接字传送消息

相关函数

sendto,sendmsg,recv,recvfrom,recvmsg,socket

表头文件

#include< sys/socket.h>

定义函数

ssize_t send(int s,const void*msg,size_t len,int flags);

参数说明

第一个参数指定发送端套接字描述符;

第二个参数指明一个存放应用程式要发送数据的缓冲区;

第三个参数指明实际要发送的数据的字符数;

第四个参数一般置0。

函数说明

send()用来将数据由指定的 socket传给对方主机。使用 send时套接字必须已经连接。send不包含传送失败的提示信息,如果检测到本地错误将返回-1。因此,如果send成功返回,并不必然表示连接另一端的进程接收数据。所保证的仅是当send成功返回时,数据已经无错误地发送到网络上。

对于支持为报文设限的协议,如果单个报文超过协议所支持的最大尺寸,send失败并将 errno设为 EMSGSIZE;对于字节流协议,send会阻塞直到整个数据被传输。

flags参数有如下的选择:

MSG_DONTROUTE勿将数据路由出本地网络

MSG_DONTWAIT允许非阻塞操作(等价于使用O_NONBLOCK)

MSG_EOR如果协议支持,此为记录结束

MSG_OOB如果协议支持,发送带外数据

MSG_NOSIGNAL禁止向系统发送异常信息

返回值

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

错误代码

EBADF参数 s非法的 socket处理代码。

EFAULT参数中有一指针指向无法存取的内存空间。

WNOTSOCK参数 s为一文件描述词,非 socket。

EINTR被信号所中断。

EAGAIN此动作会令进程阻断,但参数 s的 socket为不可阻断的。

ENOBUFS系统的缓冲内存不足。

EINVAL传给系统调用的参数不正确。

jsp格式(jsp是什么格式)版本库(如何查看 Linux 系统中的库版本?)