Linux 進程間通信(二)(網絡IPC:套接字)


socket描述符

套接字是通信端點的抽象,創建一個套接字使用如下函數:

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

返回值:若成功,返回套接字描述符;若出錯,返回-1

說明:

domain: 指定通信的特征,包括地址格式,以AF_開頭的常數表示地址族(address family):

說明

AF_INET

IPv4因特網域

AF_INET6

IPv6因特網域

AF_UNIX

UNIX域

AF_UPSPEC

未指定(可以代表任何域)

 

 

 

 

 

 

 

 

 

type: 指定套接字的類型。如下為POSIX.1的套接字類型,也可以增加其他類型的支持:

類型

說明

SOCK_DGRAM

固定長度、無連接、不可靠

SOCK_RAM

IP協議的數據報接口

SOCK_SEQPACKET

固定長度、面向連接、有序、可靠

SOCK_STREAM

有序、可靠、雙向、面向連接

 

 

 

 

 

 

 

 

 

protocol: 根據指定的domain和type所提供的默認協議,通常設為0即可。在AF_INET通信域中,SOCK_STREAM默認的協議為TCP;SOCK_DGRAM默認的協議為UDP。

 

使用無連接的數據報通信類似於郵寄信件,你不能保證傳遞的次序,信件可能會丟失,每封信都包含收信人地址;相反地,使用面向連接的通信協議類似於打電話,首先需要建立連接,連接建立好以后,彼此可以雙向地通信,對話中不需要包含地址信息,連接本身指定了通話的源和目的,就像是端與端之間有一條虛擬鏈路一樣。

SOCK_STREAM套接字提供了字節流服務,應用程序不能分辨出報文的界限;SOCK_SEQPACKET套接字提供了基於報文的服務,這意味着接受的數據量和發送的數據量完全一致;SOCK_RAW套接字提供了一個數據報接口,用於直接訪問下面的網絡層,使用該套接字時,必須有root用戶權限,並且需要應用程序自己負責構造協議頭。

 

套接字通信是雙向的,可以使用shutdown函數來禁止一個套接字I/O:

#include <sys/socket.h>

int shutdown(int sockfd, int how);

返回值:若成功,返回0;若出錯,返回-1。

說明:

how: SHUT_RD(關閉讀),SHUT_WR(關閉寫),SHUT_RDWR(關閉讀寫).

套接字是一個文件描述符,那么就可以使用close釋放一個套接字。既然如此,為何還使用shutdown呢?首先,只有最后一個活動引用關閉時,close才釋放網絡端點。這意味着如果復制了一個套接字(dup),要直到關閉了最后一個引用它的文件描述符才會釋放這個套接字。而shutdown允許使一個套接字處於不活動狀態,和引用它的文件描述符數目無關。其次,有時可以很方便地關閉套接字雙向傳輸中的一個方向。

 

補充:套接字本質上是一個文件描述符,如下為適用於文件描述符的函數在套接字中的表現行為:

函數

說明

close

釋放套接字

dup / dup2

復制套接字

fchdir

失敗,並且將errno設置為ENOTDIR

fchomod

未指定

fchown

由實現定義

fcntl

支持某些命令

fdatasync / fsync

由實現定義

fstat

支持一些stat結構成員,如何支持由實現定義

ftruncate

未指定

ioctl

依賴於底層設備驅動

lseek

由實現定義,失敗時將errno設置為ESPIPE

mmap

未指定

poll

正常工作

pread / pwrite

失敗時,將errno設置為ESPIPE

read / readv

與沒有任何標志位的recv等價

select

正常工作

write / writev

與沒有任何標志位的send等價

 

 

尋址

不同的處理器架構有着不同的字節序,如果需要實現異構通信,必須統一字節序方式。網絡協議指定了字節序(網絡字節序),TCP/IP協議棧使用大端方式,對於使用TCP/IP的應用程序,有4個函數可以用來在處理器字節序和網絡字節序之間進行轉換:

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostint32);  // 32位主機-->32位網絡

uint16_t htons(uint16_t hostint16);  // 16位主機-->16位網絡

uint32_t ntohl(uint32_t netint32); // 32位網絡-->32位主機

uint16_t ntohs(uint16_t netint16); // 16位網絡-->16位主機

說明:

h: 主機字節序

n: 網絡字節序

l: 4字節的長整型

s: 2字節的短整型

 1 #include <arpa/inet.h>
 2 #include <stdio.h>
 3 
 4 int main(void)
 5 {
 6         unsigned int bytes = 0x12345678;
 7         const char* p = (const char*)&bytes;
 8         printf("%x_%x_%x_%x\n", p[0], p[1], p[2], p[3]);
 9 
10         unsigned int netbytes = htonl(bytes);    /* 將主機字節序轉換為網絡字節序 */
11         p = (const char*)&netbytes;
12         printf("%x_%x_%x_%x\n", p[0], p[1], p[2], p[3]);
13         
14         return 0;
15 }
16 
17 [root@benxintuzi benxin]# ./addr01
18 78_56_34_12
19 12_34_56_78
View Code

 

由於地址格式與特定的通信域相關,因此,為了使得不同格式的地址都能夠傳入套接字函數,所有的地址必須先被強制轉換為一個通用的地址結構sockaddr:

struct sockaddr

{

    unsigned char sa_len;    /* total length */

    sa_family_t sa_family;   /* address family */

    char sa_data[14];    /* variable-length address */

};

因特網地址定義在<netinet/in.h>中:

struct in_addr

{

    in_addr_t       s_addr;     /* IPv4 address */

};

struct sockaddr_in

{

    sa_family_t     sin_family; /* address_family */

    in_port_t       sin_port;    /* port number */

    struct in_addr  sin_addr;   /* IPv4 address */

};

數據類型in_port_t為uint16_t,in_addr_t為uint32_t,在<stdint.h>中定義了這些類型。

 

如下函數可以在數值地址和文本格式字符串地址之間進行轉換:

#include <arpa/inet.h>

/* 將數值地址轉換為文本字符串格式 */

const char* inet_ntop(int domain, const void *restrict addr, char* restrict str, socklen_t size);

/* 將文本字符串格式轉換為數值地址 */

int inet_pton(int domain, const char* restrict str, void* restrict addr);

 

說明:

這兩個函數同時支持IPv4和IPv6地址。

domain: AF_INET或者AF_INET6.

size: 指定保存文本字符串緩沖區str的大小,該參數為INET_ADDRSTREAM或者INET6_ADDRSTREAM時,表明使用足夠大的空間來存放該地址。

inet_pton的輸出為網絡字節序,inet_ntop的輸入為網絡字節序,要注意轉換。

 1 #include <arpa/inet.h>
 2 #include <stdio.h>
 3 
 4 int main(void)
 5 {
 6         char dotaddr[] = "192.168.8.128";
 7         struct in_addr ipaddr;
 8 
 9         inet_pton(AF_INET, dotaddr, (void*)&ipaddr);
10 
11         ipaddr.s_addr = ntohl(ipaddr.s_addr);    /* 將網絡字節序轉換為主機字節序 */
12         printf("addr = %x\n", ipaddr.s_addr);
13 
14         ipaddr.s_addr = htonl(ipaddr.s_addr);    /* 將主機字節序轉換為網絡字節序 */
15         inet_ntop(AF_INET, (void*)&ipaddr, dotaddr, 16);
16         printf("addr = %s\n", dotaddr);
17 
18         return 0;
19 }        
20 
21 [root@benxintuzi benxin]# ./addr02   
22 addr = c0a80880
23 addr = 192.168.8.128
View Code

 

地址查詢

#include <netdb.h>

struct hostent* gethostent(void);      /* 返回下一個文件 */

void sethostent(int stayopen);         /* 打開文件 */

void endhostent(void);                 /* 關閉文件 */

說明:

gethostent打開文件或者返回下一個文件,sethostent設置文件指針處於文件起始位置,endhostent關閉打開的文件。

gethostent返回一個指向hostent結構體的指針,hostent結構如下:

struct hostent

{

    char *h_name;                      /* 主機名 */

    char **h_aliases;                   /* 可選的別名列表 */

    int h_addrtype;                    /* 地址類型,一般為AF_INET */

    int h_length;                      /* 地址長度 */

    char **h_addr_list;                /* 網絡地址列表 */

 

    #define h_addr h_addr_list[0];      /* 第一個網絡地址 */

};

注:

之所以主機的地址是一個列表的形式,其原因是一個主機可能有多個網絡接口。

返回的地址為網絡字節序。

 1 #include <stdio.h>
 2 #include <sys/socket.h>
 3 #include <netdb.h>
 4 #include <netinet/in.h>
 5 
 6 int main()
 7 {
 8         struct hostent* h;
 9         h = gethostbyname("benxintuzi");
10         printf("host: %s\n", h->h_name);
11         printf("addr: %s\n", inet_ntoa(*((struct in_addr*)h->h_addr)));
12 
13         return 0;
14 }
15 
16 [root@benxintuzi benxin]# ./addr04
17 host: benxintuzi
18 addr: 192.168.8.128
View Code

 

如下函數用來獲得網絡名字和網絡編號:

#include <netdb.h>

struct netent* getnetbyaddr(uint32_t net, int type);

struct netent* getnetbyname(const char* name);

struct netent* getnetent(void);

void setnetent(int stayopen);

void endnetent(void);

 

netent結構體如下定義:

struct netent

{

    char* n_name;       /* network name */

    char** n_aliases;   /* alternate network name array pointer */

    int n_addrtype;     /* net address type */

uint32_t n_net;     /* network number*/

...

}; 

 1 #include <stdio.h>
 2 #include <netdb.h>
 3 
 4 void printnet(struct netent* net)
 5 {
 6         char** p = net->n_aliases;
 7         printf("net name: %s\n", net->n_name);
 8         
 9         while(*p != NULL)
10         {
11                 printf("alias name: %s\n", *p);
12                 p++;
13         }
14         
15         printf("address type: %d\n", net->n_addrtype);
16 
17         printf("net number: %u\n", net->n_net);
18 }
19 
20 int main()
21 {
22         struct netent* net = NULL;
23         setnetent(1);
24         while((net = getnetent()) != NULL)
25         {
26                 printnet(net);
27                 printf("\n");
28         }
29         endnetent();
30 
31         return 0;
32 }
33 
34 [root@benxintuzi benxin]# ./addr05
35 net name: default
36 address type: 2
37 net number: 0
38 
39 net name: loopback
40 address type: 2
41 net number: 2130706432
42 
43 net name: link-local
44 address type: 2
45 net number: 2851995648
View Code

 

如下函數用於在協議名和協議編號之間進行映射:

#include <netdb.h>

struct protoent* getprotobyname(const char* name);

struct protoent* getprotobynumber(int proto);

struct protoent* getprotoent(void);

void setprotoent(int stayopen);

void endprotoent(void);

 

struct protoent

{

  char *p_name;                 /* Official protocol name.  */

  char **p_aliases;             /* Alias list.  */

  int p_proto;                  /* Protocol number.  */

  ...

};

 1 #include <stdio.h>
 2 #include <netdb.h>
 3 
 4 void printproto(struct protoent* proto)
 5 {
 6         char** p = proto->p_aliases;
 7         printf("proto name: %s\n", proto->p_name);
 8 
 9         while(*p != NULL)
10         {
11                 printf("alias name: %s\n", *p);
12                 p++;
13         }
14 
15 
16         printf("proto number: %d\n", proto->p_proto);
17 }
18 
19 int main()
20 {
21         struct protoent* proto = NULL;
22         setprotoent(1);
23         while((proto = getprotoent()) != NULL)
24         {
25                 printproto(proto);
26                 printf("\n");
27         }
28         endprotoent();
29 
30         return 0;
31 }
32 
33 [root@benxintuzi network]# ./addr06 | more
34 proto name: ip
35 alias name: IP
36 proto number: 0
37 
38 proto name: hopopt
39 alias name: HOPOPT
40 proto number: 0
41 
42 proto name: icmp
43 alias name: ICMP
44 proto number: 1
45 
46 proto name: igmp
47 alias name: IGMP
48 proto number: 2
49 
50 proto name: ggp
51 alias name: GGP
52 proto number: 3
View Code

 

如下函數用於操作服務(每個服務由一個唯一的端口號來支持):

#include <netdb.h>

struct servent* getservbyname(const char* name, const char* proto);

struct servent* getservbyport(int port, const char* proto);

struct servent* getservent(void);

void setservent(int stayopen);

void endservent(void);

 

struct servent

{

  char *s_name;                 /* Official service name.  */

  char **s_aliases;             /* Alias list.  */

  int s_port;                   /* Port number.  */

  char *s_proto;                /* Protocol to use.  */

  ...

};

 1 #include <stdio.h>
 2 #include <netdb.h>
 3 
 4 void printservent(struct servent* serv)
 5 {
 6         char** p = serv->s_aliases;
 7         printf("servent name: %s\n", serv->s_name);
 8 
 9         while(*p != NULL)
10         {
11                 printf("alias name: %s\n", *p);
12                 p++;
13         }
14 
15         printf("port number: %d\n", serv->s_port);
16 
17         printf("proto to use: %s\n", serv->s_proto);
18 }
19 
20 int main()
21 {
22         struct servent* serv = NULL;
23         setservent(1);
24         while((serv = getservent()) != NULL)
25         {
26                 printservent(serv);
27                 printf("\n");
28         }
29         endservent();
30 
31         return 0;
32 }
33 
34 [root@benxintuzi network]# ./addr07 | more
35 servent name: tcpmux
36 port number: 256
37 proto to use: tcp
38 
39 servent name: tcpmux
40 port number: 256
41 proto to use: udp
42 
43 servent name: rje
44 port number: 1280
45 proto to use: tcp
46 
47 servent name: rje
48 port number: 1280
49 proto to use: udp
View Code

 

POSIX.1中定義的函數getaddrinfo用來代替過時的gethostbyname和gethostbyaddr,getaddrinfo函數允許將一個主機名和一個服務名映射到一個地址:

#include <sys/socket.h>

#include <netdb.h>

int getaddrinfo(const char* restrict host, const char* restrict service, const struct addrinfo* restrict hint, struct addrinfo** restrict res);

返回值:成功,返回0;失敗,返回非0錯誤碼

void freeaddrinfo(sruct addrinfo* ai);

 

struct addrinfo

{

  int ai_flags;                 /* Input flags.  */

  int ai_family;                /* Protocol family for socket.  */

  int ai_socktype;              /* Socket type.  */

  int ai_protocol;              /* Protocol for socket.  */

  socklen_t ai_addrlen;         /* Length of socket address.  */

  struct sockaddr *ai_addr;     /* Socket address for socket.  */

  char *ai_canonname;           /* Canonical name for service location.  */

  struct addrinfo *ai_next;     /* Pointer to next in list.  */

  ...

};

說明:

getaddrinfo函數根據提供的主機名或服務名返回一個addrinfo鏈表;freeaddrinfo用來釋放一個addrinfo結構體。如果getaddrinfo失敗了,必須調用gai_strerror將返回的錯誤碼轉換成錯誤消息:

const char* gai_strerror(int error);

如果host非空,則指向一個長度為hostlen字節的緩沖區,用於存放返回的主機名;同樣,如果service非空,則指向一個長度為servlen字節的緩沖區,用於返回的服務名。

具體的ai_flags參數如下:

標志

說明

AI_ADDRCONFIG

查詢配置的地址類型(IPv4或IPv6)

AI_ALL

查找IPv4和IPv6地址

AI_CANONNAME

需要一個規范的名字(與別名相對)

AI_NUMERICHOST

以數字格式指定主機地址

AI_NUMERICSERV

將服務指定為數字端口號

AI_PASSIVE

套接字地址用於監聽綁定

AI_V4MAPPED

如果沒有找到IPv6地址,返回映射到IPv6格式的IPv4地址

 

 1 #include <stdio.h>
 2 #include <arpa/inet.h>
 3 #include <netdb.h>
 4 #include <sys/socket.h>
 5 #include <netinet/in.h>
 6 
 7 int main(void)
 8 {
 9         struct addrinfo*        ailist, *aip;
10         struct addrinfo         hint;
11         struct sockaddr_in*     sinp;
12         const char*             addr;
13         int                     err;
14         char                    abuf[INET_ADDRSTRLEN];
15 
16         hint.ai_flags   = AI_CANONNAME;
17         hint.ai_family  = 0;
18         hint.ai_socktype        = 0;
19         hint.ai_protocol        = 0;
20         hint.ai_addrlen         = 0;
21         hint.ai_canonname       = NULL;
22         hint.ai_addr            = NULL;
23         hint.ai_next            = NULL;
24 
25         if((err = getaddrinfo("benxintuzi", "nfs", &hint, &ailist)) != 0)
26                 printf("getaddrinfo error: %s", gai_strerror(err));
27         for(aip = ailist; aip != NULL; aip = aip->ai_next)
28         {
29                 printf("host: %s\n", aip->ai_canonname ? aip->ai_canonname : "-");
30                 if(aip->ai_family == AF_INET)
31                 {
32                         sinp = (struct sockaddr_in*)aip->ai_addr;
33                         addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf, INET_ADDRSTRLEN);
34                         printf("address: %s\n", addr ? addr : "Unknown");
35 
36                 }
37         printf("\n");
38         }
39 
40         return 0;
41 }
42 
43 [root@benxintuzi network]# ./addr03
44 host: benxintuzi
45 address: 192.168.8.128
46 
47 host: -
48 address: 192.168.8.128
49 
50 host: -
51 address: 192.168.8.128
52 
53 host: -
54 address: 192.168.8.128
View Code

 

getnameinfo將一個地址轉換為一個主機名和一個服務名:

#include <sys/socket.h>

#include <netdb.h>

int getnameinfo(const struct sockaddr* addr, socklen_t alen, char* restrict host,

socklen_t hostlen, char* restrict service, socklen_t servlen, int flags);

 

struct sockaddr

{

unsigned short sa_family;   /* address family, AF_xxx */

char sa_data[14];           /* 14 bytes of protocol address */

};

 

flags參數提供一些控制方式:

標志

說明

NI_DGRAM

服務基於數據報而非基於流

NI_NAMEREQD

如果找不到主機名,將其作為一個錯誤對待

NI_NOFQDN

對於本地主機,僅返回全限定域名的節點名部分

NI_NUMERICHOST

返回主機地址的數字形式,而非主機名

NI_NUMERICSCOPE

對於IPv6,返回數字形式

NI_NUMERICSERV

返回服務地址的數字形式(即端口號)

 

 

套接字與地址的關聯

給某個服務器關聯一個眾所周知的地址,使得客戶端可以訪問該服務器,最簡單的一個辦法是服務器保留一個地址並且將其注冊在/etc/services中,使用bind函數:

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr* addr, socklen_t len);

返回值:成功,返回0;失敗,返回-1

說明:

地址中的端口號一般不小於1024,並且一般只能將一個套接字端口綁定在一個給定的地址上。

對於英特網域,如果指定IP地址為INADDR_ANY(<netinet/in.h>中定義),套接字端點可以被綁定到所有的系統網絡接口上,這意味着可以接收這個系統所安裝的任何一個網卡的數據包。如果調用connect或listen,但沒有將地址綁定到套接字上,系統會選一個地址綁定到套接字上。

 

使用getsockname找到綁定到套接字上的地址。如果套接字已經和連接上,則可以使用getpeername來找到對方的地址。

int getsockname(int sockfd, struct sockaddr* restrict addr, socklen_t* restrict alenp);

int getpeername(int sockfd, struct sockaddr* restrict addr, socklen_t* restrict alenp);

返回值:成功,返回0;失敗,返回-1

 

建立連接

對於面向連接的服務而言,進行交換數據之前需要在客戶端套接字和服務器套接字之間建立一個連接,使用connect函數:

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr* addr, socklen_t len);

返回值:成功,返回0; 失敗,返回-1

說明:

在connect中指定的地址是我們想與之通信的服務器地址,如果sockfd沒有綁定到一個地址上,connect會給調用者綁定一個默認地址。

 

當連接服務器時,由於某種原因,連接可能會失敗。因此,應用程序必須能夠處理connect函數返回的錯誤,如下介紹一種指數補償算法用於處理連接失敗的情況:

 1 #define MAXSLEEP 128
 2 
 3 int connect_retry(int domain, int type, int protocol, const struct sockaddr* addr, socklen_t alen)
 4 {
 5     int numsec, fd;
 6     /*
 7 指數補償算法:如果connect調用失敗,進程休眠一段時間,然后進入下一次循環嘗試連接,每次休眠時間以指數級增長,直到最大延遲
 8 */
 9 for(mumsec = 1; numsec <= MAXSLEEP; numsec <<= 1)
10 {
11     if((fd = socket(domain, type, protocol)) < 0)
12         return -1;
13     if(connect(fd, addr, alen) == 0)
14         return fd;    /* connect success */
15     
16 close(fd);    /* connect fail */
17     if(numsec <= MAXSLEEP / 2)
18         sleep(numsec);
19 }
20 return -1;
21 }
View Code

 

服務器調用listen函數來表示它正在等待連接請求:

#include <sys/socket.h>

int listen(int sockfd, int backlog);

返回值:成功,返回0;失敗,返回-1

說明:

backlog:表示系統隊列中未完成連接請求的數量,如果隊列已滿,則系統會拒絕多余的連接請求。一旦服務器調用了listen,就可以接受連接請求了,然后就能使用accept函數建立連接:

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr* addr, socklen_t* len);

返回值:成功,返回套接字描述符;失敗,返回-1

說明:

如果不關心客戶端標識,可以將參數addr和len設為NULL;否則,在調用accept之前,將addr參數設為足夠大來保存客戶端地址,len指向的整數設置為addr的大小。如果沒有連接請求在等待,accept會阻塞,直到請求到來。如果sockfd處於非阻塞模式,則accept返回-1,並且將errno設置為EAGAIN或者EWOULDBLOCK。此外,服務器還可以使用pool或者select來等待一個連接請求的到來。

 

如下函數用來分配和初始化套接字供服務器進程使用:

 1 int initserver(int type, const struct sockaddr* addr, socklen_t alen, int qlen)
 2 {
 3     int fd;
 4     int err = 0;
 5 
 6     if((fd = socket(addr->sa_family, type, 0)) < 0)
 7         return -1;
 8     if(bind(fd, addr, alen) < 0)
 9         goto errout;
10     if(type == SOCK_STREAM || type == SOCK_SEQPACKET)
11         if(listen(fd, qlen) < 0)
12             goto errout;
13     return fd;
14 
15 errout:
16     err = errno;
17     close(fd);
18     errno = err;
19     return -1;
20 }
View Code

 

數據傳輸

因為一個套接字表示為一個文件描述符,因此只要建立連接,就可以使用read和write來操作套接字了。這意味着可以將套接字傳遞給處理文件的函數,而該文件處理函數並不需要了解套接字即可工作。

除了read和write,還有6個專為套接字設計的函數:3個用於發送數據,3個用於接收數據。

3個用於發送數據的函數如下:

#include <sys/socket.h>

ssize_t send(int sockfd, const void* buf, size_t nbytes, int flags);

返回值:成功,返回發送的字節數;失敗,返回-1

說明:

send類似於write,使用send時套接字必須已經連接。然而,send支持選項flags,如下:

Linux標志

描述

MSG_CONFIRM

提供鏈路層反饋以保證地址映射有效

MSG_DONTROUTE

禁止將數據報路由出本地網絡

MSG_DONTWAIT

允許非阻塞通信

MSG_EOR

標記結束

MSG_MORE

延遲發送數據報允許寫更多數據

MSG_NOSIGNAL

在無連接的情況下不產生SIGPIPE信號

MSG_OOB

允許發送帶外數據

 

 

 

 

 

 

 

 

 

 

 

 

 

 

send的成功返回表示數據被無差錯地發送到網絡驅動程序中,並不表示對方成功地接受了數據。對於支持報文邊界的協議,如果發送的單個報文長度超過了協議所支持的最大長度,那么send發送失敗,並將errno設置為EMSGSIZE;對於字節流協議,send會阻塞直到整個數據傳輸完成。

 

#include <sys/socket.h>

ssize_t sento(int sockfd, const void* buf, size_t nbytes, int flags, const struct sockaddr* destaddr, socklen_t destlen);

返回值:成功,返回發送的字節數;失敗,返回-1

說明:

與send相比,sendto可以指定目的地址,當然這是對於無連接的情況而言的,對於面向連接的情況,目的地址是被忽略的,因為連接中隱含了地址。在無連接的情況下,可以直接使用sendto,或者先使用connect設置目的地址,然后使用send發送數據。

 

通過套接字發送數據時,還可以調用帶有msghdr結構的sendmsg來指定多重緩沖區傳輸數據,這與writev類似:

#include <sys/socket.h>

ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flags);

返回值:成功,返回發送的字節數;失敗,返回-1

struct msghdr

{

    void*         msg_name;         /* 目的地址名字,是一個指向結構體struct sockaddr的指針 */

    socklen_t     msg_namelen;      /* 目的地址的長度 */

    struct iovec*     msg_iov;          /* 消息內容,指向struct iovec的指針 */

    int           msg_iovlen;       /* 長度 */

    void*         msg_control;      /* 控制消息 */

    socklen_t     msg_controllen;

    int           msg_falgs;        /* 標記如何接受數據 */

}

sendmsg函數可以通過msghdr指定多個緩沖區發送數據,同時可以發送輔助數據。

 

相應地,3個用於接收數據的函數如下:

#include <sys/socket.h>

ssize_t recv(int sockfd, void* buf, size_t nbytes, int flags);

返回值:成功,返回接收數據的字節長度;若未接收到數據或發送方已結束發送,返回0; 失敗,返回-1

說明:

flags如下:

Linux標志

描述

MSG_CMSG_CLOEXEC

為UNIX域套接字上接受的文件描述符設置執行時關閉標志

MSG_DONTWAIT

啟用非阻塞接受

MSG_WAITALL

等待直到所有可用數據到達(SOCK_STREAM)

MSG_ERRQUEUE

接受錯誤信息作為輔助數據

MSG_PEEK

僅查看數據包內容而不真正接收數據包

MSG_TRUNC

即使數據包被截斷,也返回數據包的實際長度

MSG_OOB

接受帶外數據

 

 

 

 

 

 

 

 

 

 

 

 

 

 

如果發送者已經調用了shutdown結束傳輸,或者網絡協議支持按默認的順序關閉並且發送端已經關閉,那么當所有的數據接收完畢后,recv返回0。

 

可以使用recvfrom來得到數據發送者的地址:

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags,struct sockaddr* addr, socklen_t* addrlen);

返回值:成功,返回接收的實際字節數;若無可用數據或對方已經結束傳輸,返回0;失敗,返回-1

說明:

recvfrom函數可以用於追蹤發送者的地址,將其存儲在addr指向的結構體中,addrlen表示其長度。該函數一般用於無連接的套接字通信中,在面向連接的情況下,等同於recv。

 

為了將接收到的數據送入多個緩沖區,可以使用recvmsg,類似於readv:

#include <sys/socket.h>

ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);

返回值:成功,返回接收的實際字節數;若無可用數據或對方已經結束傳輸,返回0;失敗,返回-1

說明:

recvmsg可以通過msghdr指定多個緩沖區接收數據,同時也可以接收輔助數據,其flags標志如下:

Linux標志

描述

MSG_CTRUNC

控制數據被截斷

MSG_EOR

接受記錄結束符

MSG_ERRQUEUE

接受錯誤信息作為輔助數據

MSG_TRUNC

一般數據被截斷

MSG_OOB

接受帶外數據

 

 

 

 

 

 

 

 

 

 

 

套接字選項

套接字選項提供了兩個接口來控制套接字行為:一個接口用來設置選項,另一個接口用於查詢選項的狀態。可以獲取或設置以下3種選項:

(1)       通用選項,工作在所有套接字類型上。

(2)       在套接字層次管理的選項,但是依賴於下層協議的支持。

(3)       特定於某協議的選項,每個協議獨有。

可以使用setsockopt函數來設置套接字選項:

#include <sys/socket.h>

int setsockopt(int sockfd, int level, int option, const void* val, socklen_t len);

返回值:成功,返回0;失敗,返回-1

說明:

level標識了選項應用的協議。如果選項是通用的套接字層次選項,擇level設置成SOL_SOCKET。否則,level設置成控制這個選項的協議編號。對於TCP選項,level為IPPROTO_TCP,對於IP,level為IPPROTO_IP。如下為通用套接字層次選項:

 

val根據選項的不同指向一個數據結構或一個整數,一些選項是on/off開關。如果整數非0,則啟動該選項,如果為0,則禁止該選項。

len指定了val指向的對象的大小。

 

可以使用getsockopt函數查看選項的當前值:

#include <sys/socket.h>

int getsockopt(int sockfd, int level, int option, void* val, socklen_t* restrict lenp);

返回值:成功,返回0;失敗,返回-1

說明:

lenp是一個指向整數的指針。在調用getsockopt之前,設置該整數為復制選項緩沖區的長度。如果選項的實際長度大於此值,則選項會被截斷;如果實際長度小於此值,那么返回時將此值更新為實際長度。

 

帶外數據

與普通數據相比,帶外數據擁有更高的傳輸優先級,即使傳輸隊列中已經有數據了,帶外數據也可先行傳輸。TCP支持帶外數據,UDP不支持。TCP將帶外數據稱為緊急數據(urgent data,TCP僅支持一個字節的緊急數據。可以在send函數中指定MSG_OOB標志來產生緊急數據(指定MSG_OOB標志的send發送的字節數如果超過一個時,則最后一個字節將被視為緊急數據)。

如果通過套接字安排了信號的產生,那么緊急數據被接收時,會發送SIGURG信號。

TCP還支持緊急標記(urgent mark的概念,即在普通數據流中標記緊急數據所在的位置。如果采用套接字選項SO_OOBINLINE,那么可以在普通數據中接受緊急數據,可以使用函數sockatmark判斷是否到達了緊急標記處。

#include <sys/socket.h>

int sockatmark(int sockfd);

返回值:到達標記處,返回1;沒到達,返回0;出錯,返回-1

說明:

可以在普通數據流中接收緊急數據,也可以在recv函數中采用MSG_OOB標志在其他隊列數據之前接收緊急數據。

TCP隊列僅用一個字節接收緊急數據,如果在接收當前緊急數據前又有新的緊急數據到來,那么已有的字節會被丟棄。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM