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 }