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 [[email protected] benxin]# ./addr01
18 78_56_34_12
19 12_34_56_78

由于地址格式与特定的通信域相关,因此,为了使得不同格式的地址都能够传入套接字函数,所有的地址必须先被强制转换为一个通用的地址结构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 [[email protected] benxin]# ./addr02
22 addr = c0a80880
23 addr = 192.168.8.128

地址查询


#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 [[email protected] benxin]# ./addr04
17 host: benxintuzi
18 addr: 192.168.8.128

如下函数用来获得网络名字和网络编号:


#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 [[email protected] 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

如下函数用于在协议名和协议编号之间进行映射:


#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 [[email protected] 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

如下函数用于操作服务(每个服务由一个唯一的端口号来支持):


#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 [[email protected] 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

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 [[email protected] 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

 

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 }

服务器调用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 }

数据传输

因为一个套接字表示为一个文件描述符,因此只要建立连接,就可以使用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 recv_from(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队列仅用一个字节接收紧急数据,如果在接收当前紧急数据前又有新的紧急数据到来,那么已有的字节会被丢弃。

时间: 2024-10-24 22:36:34

Linux 进程间通信(二)(网络IPC:套接字)的相关文章

Linux 网络编程——套接字的介绍

套接字是一种通信机制(通信的两方的一种约定),凭借这种机制,不同主机之间的进程可以进行通信.我们可以用套接字中的相关函数来完成通信过程. 套接字的特性有三个属性确定,它们是:域(domain),类型(type),和协议(protocol). 套接字的域 域指定套接字通信中使用的网络介质.最常见的套接字域是 AF_INET,它是指 Internet 网络,许多 Linux 局域网使用的都是该网络,当然,因特网自身用的也是它. 套接字类型 流套接字(SOCK_STREAM): 流套接字用于提供面向连

linux网络编程——套接字(socket)入门

1.套接字的基本结构 struct sockaddr 这个结构用来存储套接字地址. 数据定义: struct sockaddr { unsigned short sa_family; /* address族, AF_xxx */ char sa_data[14]; /* 14 bytes的协议地址 */ }; sa_family 一般来说,都是"AFINET". sa_data 包含了一些远程电脑的地址.端口和套接字的数目,它里面的数据是杂溶在一切的. 为了处理struct socka

网络编程 套接字socket 及 粘包

网络编程 套接字socket 及 粘包 sockt 初识 五层协议 : 从传输层包括传输层以下 , 都是操作系统帮我们封装的各种head socket套接字充当的就是内置模块的角色 socket 套接字,它存在于传输层与应用层之间的抽象层 避免你学习各层的接口以及协议的使用, socket已经封装好了所有的接口 . 直接使用这些接口或者方法即可 , 使用起来方便,提升开发效率 socket 就是一个模块 , 通过使用学习模块提供的功能 , 建立客户端与服务端的通信 套接字的工作流程(基于TCP和

网络---中断套接字Socket

1 package socketpack_2; 2 import java.awt.BorderLayout; 3 import java.awt.EventQueue; 4 import java.awt.event.ActionEvent; 5 import java.awt.event.ActionListener; 6 import java.io.IOException; 7 import java.io.OutputStream; 8 import java.io.PrintWrit

图解IT---1网络与套接字

记几张清晰简洁的原理图---1网络与套接字 1. TCP有限状态机   2. 数据包接收 3. 套接字的层次 4. netfilter/iptable原理

网络编程(二):套接字Socket

socket是基于C/S架构的,也就是说进行socket网络编程,通常需要编写两个py文件,一个服务端,一个客户端. 首先,导入Python中的socket模块: import socket 其通信逻辑如下图所示: 这张图片是整个socket编程的基础,必须牢牢记住. 通过导入模块import socket后用socket.socket()创建套接字,格式如下: sk = socket.socket([family[, type[, proto]]]) 参数说明: family: 套接字家族,可

进程-IPC 套接字 (四)

详见:https://github.com/ZhangzheBJUT/linux/blob/master/IPC(%E5%9B%9B).md 七 套接字 7.1. 套接字简介 之前所讨论的IPC机制都是依靠一台计算机共享系统资源实现的,这些资源可以是文件系统(命名管道).共享的物理内存(共享内存)和消息队列,这些只有运行在同一台机器上的进程可以使用. 伯克利版本的UNIX系统引入了一种新的通信工具-套接字接口(socket interface).一台机器上的进程可以通过使用套接字和另外一台机器上

网络编程套接字,osi七层架构各层协议最全讲解

目录 Socket原理 1.什么是Socket 2.网络中进程如何通信 2.1.本地进程间通信 2.2.网络中进程如何通信 3.Socket怎么通信 4.TCP/IP协议 4.1.概念 4.2.TCP的粘包问题以及数据的无边界性: 4.3.TCP数据报结构: 4.4.连接的建立(三次握手): 4.5.TCP四次握手断开连接 4.6.关于 TIME_WAIT 状态的说明 4.7优雅的断开连接–shutdown() 5.OSI模型 6.Socket常用函数接口及其原理 6.1.使用socket()函

UNIX网络编程——套接字选项(心跳检测、绑定地址复用)(转)

/* 设置套接字选项周期性消息检测连通性 心跳包. 心博.主要用于长连接. * 参数:套接字, 1或0开启, 首次间隔时间, 两次间隔时间, 断开次数 */ void setKeepAlive( int iSockfd , int iSockAttrOn, socklen_t iIdleTime , socklen_t iInterval , socklen_t iCount ){ setsockopt( iSockfd , SOL_SOCKET , SO_KEEPALIVE , (const

网络编程-套接字(socket)

一.什么是套接字 套接字(socket)是计算机之前数据传输的工具,是有计算机系统提供的一个组件,是网络数据传输的软件设备. 二.TCP/IP协议 TCP/IP协议栈共分为4层(OSI规范分7层),tcp.udp就是基于socket的一种协议 三.套接字的分类 1.流式套接字(TCP) 它提供了一种可靠的.面向连接的双向通讯方式.适用于传输数据量大的场景. TCP的具有以下三项特征: 传输过程中数据不会丢失,面向连接的套接字会根据接收端的状态传输数据,如果传输出错还会提供重传服务 按序传输数据,