上一章的IPC仅限于本机的进程间通讯,这一章讲述的是网络间的进程间通讯(network IPC)
套接字描述符 Socket描述符在UNIX系统中被当做是一种文件描述符,有很多文件I/O相关函数可以直接用,但跟偏移相关等的函数不行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <sys/socket.h> int socket (int domain, int type.int protocal) ;int shutdown (int sockfd, int how) ;
shutdown函数的应用和close的区别
close函数只是减少引用计数1,只有引用计数为0了内核才会发送FIN包;而shutdown会影响所有持有这个socket的进程(如通过fork函数得到的)
shutdown可以只关闭读端或者写端,close不可设置选项
shutdown后的影响 来自于Stack Overflow :
Shutting down the read side of a socket will cause any blocked recv (or similar) calls to return 0 (indicating graceful shutdown). I don’t know what will happen to data currently traveling up the IP stack. It will most certainly ignore data that is in-flight from the other side. It will not affect writes to that socket at all.
socket中遇到SIGPIPE信号
当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。
又或者当一个进程向某个已经收到RST的socket执行写操作是,内核向该进程发送一个SIGPIPE信号。该信号的缺省行为是终止进程,因此进程必须捕获它以免不情愿的被终止。
检测方式:先设置忽略该信号,然后read或者write失败的话检测errno是不是EPIPE。
另外,如果使用多进程模型且父进程不想关心子进程的话,为防止子进程崩溃产生了僵死进程,应该设置忽略SIGCHLD信号。
数据链路层的包监听 sock_raw(注意一定要在root下使用)原始套接字编程可以接收到本机网卡上的数据帧或者数据包,对于监听网络的流量和分析是很有作用的。可用下列方式创建这种socket
SOCK_RAW, IPPROTO_TCP|IPPROTO_UDP|IPPROTO_ICMP)```发送接收ip数据包,不能用IPPROTO_IP,因为如果是用了IPPROTO_IP,系统根本就不知道该用什么协议。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 2. ```socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP|ETH_P_ARP|ETH_P_ALL))```发送接收以太网数据帧 3. 如socket的第一个参数使用PF_INET,第二个参数使用SOCK_RAW,则可以得到原始的IP包。 ## 寻址 ### 字节序 1. 大端:高字节地址放低位,低字节地址放高位 2. 小端:高字节地址放高位,低字节地址放低位 ```cpp #include <arpa/inet.h> //host to network in long uint32_t htonl(uint32_t hostint32); //host to network in short uint16_t htons(uint16_t hostint16); //network to host in long uint32_t ntohl(uint32_t netint32); //network to host in short uint16_t ntohs(uint16_t netint16);
地址格式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 struct sockaddr { sa_family_t sa_family; char sa_data[]; }; struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { sa_family_t sin_family; in_port_t sin_port; struct in_addr sin_addr; }; struct in6_addr { uint8_t s6_addr[16 ]; }; struct sockaddr_in6 { sa_family_t sin6_family; in_port_t sin6_port; unit32_t sin6_flowinfo; struct in6_addr sin6_addr; uint32_t sin6_scope_id; }; in_addr_t inet_addr (const char *cp) ;char *inet_ntoa (struct in_addr) ;int inet_aton (const char *string, struct in_addr* addr) ;const char *inet_ntop (int domain, const void *restrict addr, char *restrict str, coklen_t size) ;int inet_pton (int domain, const char *restrict str, void *restrict addr) ;
地址查询 1 2 3 4 5 6 7 #include <netdb.h> struct hostent *gethostent (void );void sethostent (int stayopen) ;void endhostent (void ) ;
bind 用于关联套接字和地址
1 2 3 4 5 6 7 8 #include <sys/socket.h> int bind (int sockfd, const struct sockaddr *addr, socklen_t len) ;int getsockname (int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp) ;int getpeername (int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp) ;
连接 1 2 3 #include <sys/socket.h> int connect (int sockfd, const struct sockaddr *addr, socklen_t len) ;
如果connect的时候,sockfd没有绑定到一个地址,connect会给调用者默认绑定一个地址。
对于UDP,也可调用connect,这样每次发送的时候就不需要一直填地址。
监听 1 2 3 4 5 #include <sys/socket.h> int listen (int sockfd, int backlog) ;int accept (int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len) ;
数据传输
send
sendto
sendmsg
recv
recvfrom
recvmsg
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <sys/socket.h> ssize_t send (int sockfd, const void *buf, size_t nbytes, int flags) ;ssize_t sendto (int sockfd, const void *buf, size_t nbytes, int flags, conststruct sockaddr *destaddr, socklen_t destlen) ;ssize_t sendmsg (int sockfd,const struct msghdr *msg, int flags) ;struct msghdr { void *msg_name; socklen_t msg_namelen; struct iovec *msg_iov; int msg_iovlen; void *msg_control; socklen_t msg_controllen; int msg_flags; }; ssize_t recv (int sockfd, void *buf, size_t nbytes, int flags) ;ssize_t recvfrom (int sockfd, void *restrict buf, size_t len, int flags, struct sockaddr *restict addr, socklen_t *restrict addrlen) ;ssize_t recvmsg (int sockfd, struct msghdr *msg, int flags) ;
套接字选项 1 2 3 4 #include <sys/socket.h> int setsockopt (int sockfd, int level, int option, const void *val, socklen_t len) ;int getsockopt (int sockfd, int level, int option, void *restrict val, socklen_t *restrict lenp) ;
level的详细请见《APUE》P503,另外这个函数通常作用是来设置SO_REUSEADDR和SO_REUSEPORT等选项。两者的区别和相关的影响见0517日报中的相关内容。
带外数据 带外数据一般是针对TCP而言的,相当于紧急数据(urgent data),这些数据会优先发送,哪怕传输队列已经有数据。TCP仅支持一个字节的紧急数据,只需在发送函数中指定MSG_OOB的标识,当发送内容超过一个字节,只有最后一个字节被视为紧急数据。上一个紧急数据未处理又收到一个,会被覆盖。详见《APUE》16.7
1 2 3 #include <sys/socket.h> int sockatmark (int sockfd) ;
查了下应用场景,比较特殊,如远程传输文件突然想取消,则使用这种方式
非阻塞I/O 设置接受和发送为异步操作的顺序
建立套接字的所有权
通知套接字当I/O操作不会阻塞时发信号
详细方式
fcntl中使用F_SETOWN
fcntl中使用F_SETFL并启用O_ASYNC
其他方式见《APUE》P505