《APUE》读书笔记-第十六章网络IPC:套接字
通过网络套接字可以使得不同计算机上运行的进程相互通信。
1、创建套接字
#include <sys/socket.h>
Int socket( int domain, int type, int protocol);
注意:AF_LOCAL域是AF_UNIX的别名,AF_UNSPEC域可以代表任何域。
2、套接字通信是双向的,禁止套接字上的输入/输出
#include < sys/socket.h>
Int shutdown ( int sockfd, int how);
3、处理字节序和网络字节序之间的轮换:
#include< arpa/inet.h>
Uint32_t htonl (uint32_t hostint32);
Uint16_t htons(uint16_t hostint16);
Uint32_t ntohl (uint32_t netint32);
Uint16_t ntohs (uint16_t netint16);
4、地址格式:
Struct sockaddr{
Sa_family_t sa_family;
Char sa_data[];}
在Linux中,该结构定义如下:
Struct sockaddr{
Sa_family_t sa_family;
Char sa_data[14];};
而在FreeBSD中,该结构定义如下:
Struct sockaddr{
Unsigned char sa_len;
Sa_family_t sa_family;
Char sa_data[14];};
因特网地址定义在<netinet/in.h>中。在IPV4因特网域(AF_INET)中,套接字地址用如下结构sokaddr_in表示:
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;
};
数据类型in_port_t 定义成uint16_t。数据类型in_addr_t 定义成uint32_t。这些整数类型在<sdint.h>中定义并指定了相应的位数。
与IPV4因特网域(AF_INET)相比较,IPV6因特网域(AF_INET6)套接字地址用如下结构sockaddr_in6表示:
Struct in6_addr{
Uint8_t s6_addr[16];};
Struct sockaddr_in6{
Sa_family_t sin6_family;
In_port_t sin6_port;
Uint32_t sin6_flowinfo;
Struct in6_addr sin6_addr;
Uint32_t sin6_scope_id;}
在Linux中,sockaddr_in 定义如下:
Struct sockaddr_in{
Sa_family_t sin_family;
In_port_t sin_port;
Struct in_addr sin_addr;
Unsigned char sin_zero[8];};
5、打印出能被人理解的地址格式函数
#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);
6、找给定计算机的主机信息
#include<netdb.h>
Struct hostent * gethostent (void);
Void sethostent (int stayopen);
Void endhostent (void);
Struct hostent{
Char * h_name;
Char ** h_aliases;
Int h_addrtype;
Int h_length;
Char ** h_addr_list;};
7、从接口获得网络名字和网络号:
#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);
Struct netent{
Char * n_name;
Char ** n_aliases;
Int n_addrtype;
Uint32_t n_net;};
8、将协议名字和协议号采用以下函数映射
#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;
Char ** p_aliases;
Int p_proto;};
9、从一个服务名映射到一个端口号,服务名
#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 setervent (int stayopen);
Void endservent (void);
Struct servent{
Char * s_name;
Char ** s_aliases;
Int s_port;
Char * s_proto;};
10、从一个主机名字和服务名字映射到一个地址
#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);
Void freeaddrinfo (struct addrinfo * ai);
Struct addrinfo{
Int ai_flags;
int ai_family;
Int ai_socktype;
Int ai_protocol;
Socklen_t ai_addrlen;
Struct sockaddr * ai_addr;
Char * ai_canonname;
Struct addrinfo * ai_next;};
11、gai_strerror将返回的错误码转换成错误消息
#include<netdb.h>
Const char * gai_strerror (int error);
12、将地址转换成主机或者服务名
#include<sys/socket.h>
#include <netdb.h>
Int getnameinfo (const struct sockaddr * restrict addr, socklen_t alen, char * restrict host,socklen_t hostlen, char * restrict service, socklen_t servlen, unsigned int flags);
写一个程序获取本机的网络信息,程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <arpa/inet.h> 5 #include <netdb.h> 6 #include <sys/socket.h> 7 8 void print_family(struct addrinfo *aip) 9 { 10 printf("family "); 11 switch(aip->ai_family) 12 { 13 case AF_INET: 14 printf("inet "); 15 break; 16 case AF_INET6: 17 printf("inet6 "); 18 break; 19 case AF_UNIX: 20 printf("unix "); 21 break; 22 case AF_UNSPEC: 23 printf("unspecified "); 24 break; 25 default: 26 printf("unkown "); 27 } 28 } 29 30 void print_type(struct addrinfo *aip) 31 { 32 printf("type "); 33 switch(aip->ai_socktype) 34 { 35 case SOCK_STREAM: 36 printf("stream "); 37 break; 38 case SOCK_DGRAM: 39 printf("datagram"); 40 break; 41 case SOCK_SEQPACKET: 42 printf("seqpacket "); 43 break; 44 case SOCK_RAW: 45 printf("raw "); 46 break; 47 default: 48 printf("unknown (%d)",aip->ai_socktype); 49 } 50 } 51 52 void print_protocol(struct addrinfo *aip) 53 { 54 printf(" protocol "); 55 switch(aip->ai_protocol) 56 { 57 case 0: 58 printf("default "); 59 break; 60 case IPPROTO_TCP: 61 printf("TCP "); 62 break; 63 case IPPROTO_UDP: 64 printf("UDP "); 65 break; 66 case IPPROTO_RAW: 67 printf("raw "); 68 break; 69 default: 70 printf("unknown (%d)",aip->ai_protocol); 71 } 72 } 73 74 void print_flags(struct addrinfo *aip) 75 { 76 printf("flags"); 77 if(aip->ai_flags == 0) 78 printf("0"); 79 else 80 { 81 if(aip->ai_flags & AI_PASSIVE) 82 printf(" passive "); 83 if(aip->ai_flags & AI_CANONNAME) 84 printf(" canon "); 85 if(aip->ai_flags & AI_NUMERICHOST) 86 printf(" numhost "); 87 } 88 } 89 90 int main() 91 { 92 struct addrinfo *ailist,*aip; 93 struct addrinfo hint; 94 struct sockaddr_in *sinp; 95 const char *addr; 96 int err; 97 char abuf[INET_ADDRSTRLEN]; 98 hint.ai_flags = AI_CANONNAME; 99 hint.ai_family = 0; 100 hint.ai_socktype = 0; 101 hint.ai_protocol = 0; 102 hint.ai_addrlen = 0; 103 hint.ai_canonname = NULL; 104 hint.ai_addr = NULL; 105 hint.ai_next = NULL; 106 if(getaddrinfo("localhost",NULL,&hint,&ailist) != 0) 107 { 108 printf("getaddrinfo error: %s",gai_strerror(err)); 109 exit(-1); 110 } 111 for(aip = ailist;aip != NULL;aip = aip->ai_next) 112 { 113 print_flags(aip); 114 print_family(aip); 115 print_type(aip); 116 print_protocol(aip); 117 printf("\n\thost %s",aip->ai_canonname ?aip->ai_canonname : "-"); 118 if(aip->ai_family == AF_INET) 119 { 120 sinp = (struct sockaddr_in *)aip->ai_addr; 121 addr = inet_ntop(AF_INET,&sinp->sin_addr,abuf,INET_ADDRSTRLEN); 122 printf(" address %s ",addr?addr:"unknown"); 123 printf(" port %d ",ntohs(sinp->sin_port)); 124 } 125 printf("\n"); 126 } 127 exit(0); 128 }
程序执行结果如下:
13、将套接字与地址绑定
#include <sys/socket.h>
Int bind (int sockfd, const struct sockaddr * addr, socklen_t len );
对于使用的地址有一些限制:
A、 在进程所运行的机器上,指定的地址必须有效,不能指定一个其他机器的地址。
B、 地址必须和创建套接字时的地址族支持的格式相匹配。
C、 端口号必须不小于1024,除非该进程具有相应的特权(即为超级用户)。
D、一般只有套接字端点能够与地址绑定,尽管有些协议允许多重绑定。
14、获取绑定到一个套接字的地址:
#include <sys/socket.h>
Int getsockname ( int sockfd, struct sockaddr * restrict addr, socklen_t * restrict alenp);
注意:在调用getsockname之前,设置alenp为一个指向整数的指针,该整数指定缓冲区sockaddr的大小。返回时,该整数会被设置成返回地址的大小。如果该地址和提供的缓冲区长度不匹配,则将其截断而不报错。如果当前没有绑定到该套接字的地址,其结果没有定义。
15、获得对方地址:
#include <sys/socket.h>
Int getpeername ( int sockfd, struct sockaddr * restrict addr, socklen_t * restrict alenp);
注意:如果套接字已经和对方连接,调用getpeername来找到对方的地址。除了还会返回对方的地址之外,函数getpeername和getsockname一样。
16、建立连接
#include <sys/socket.h>
Int connect ( int sockfd, const struct sockaddr * addr, socklen_t len);
17、listen函数
#include <sys/socket.h>
Int listen ( int sockfd,int backlog);
18、accept函数
#include <sys/socket.h>
Int accept ( int sockfd, struct sockaddr * restrict addr, socklen_t * restrict len);
19、send、sendto以及sendmsg信息发送函数
#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, const struct 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;
};
19、recv、recvfrom 与recvmsg接收数据函数
#include <sys/socket.h>
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 * restrict addr, socklen_t * restrict addrlen);
Ssize_t recvmsg ( int sockfd, struct msghdr * msg, int flags);
写个程序练习网络IPC,客户端请求服务器获取当前时间,服务器调用uptime命令将时间发送给客户端。程序如下:
客户端程序:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <arpa/inet.h> 5 #include <netdb.h> 6 #include <sys/socket.h> 7 #include <errno.h> 8 #include <string.h> 9 #define MAXADDRLEN 256 10 #define BUFLEN 128 11 #define MAXSLEEP 128 12 #define HOST_NAME_MAX 256 13 //尝试重试的连接 14 int connect_retry(int sockfd,const struct sockaddr *addr,socklen_t alen) 15 { 16 int nsec; 17 for(nsec =1;nsec <= MAXSLEEP;nsec<<=1) 18 { 19 if(connect(sockfd,addr,alen) == 0) 20 return 0; 21 if(nsec <= MAXSLEEP/2) 22 sleep(nsec); 23 } 24 return -1; 25 } 26 void print_uptime(int sockfd) 27 { 28 int n; 29 char buf[BUFLEN]; 30 while((n=recv(sockfd,buf,BUFLEN,0)) > 0) 31 write(STDOUT_FILENO,buf,n); 32 if(n<0) 33 { 34 perror("recv error"); 35 exit(-1); 36 } 37 } 38 int main() 39 { 40 struct addrinfo *ailist,*aip; 41 struct addrinfo hint; 42 int sockfd; 43 int err,n; 44 char *host; 45 #ifdef _SC_HOST_NAME_MAX 46 n = sysconf(_SC_HOST_NAME_MAX); 47 if(n<0) 48 #endif 49 n = HOST_NAME_MAX; 50 host = (char*)malloc(n); 51 if(host == NULL) 52 { 53 perror("malloc() error"); 54 exit(-1); 55 } 56 if(gethostname(host,n) < 0) 57 { 58 perror("gethostname() error"); 59 exit(-1); 60 } 61 hint.ai_flags = 0; 62 hint.ai_family = 0; 63 hint.ai_socktype = SOCK_STREAM; 64 hint.ai_protocol = 0; 65 hint.ai_addrlen = 0; 66 hint.ai_canonname = NULL; 67 hint.ai_addr = NULL; 68 hint.ai_next = NULL; 69 if(getaddrinfo(host,"ruptime",&hint,&ailist) != 0) 70 { 71 printf("getaddrinfo error: %s",gai_strerror(err)); 72 exit(-1); 73 } 74 for(aip = ailist;aip != NULL;aip = aip->ai_next) 75 { 76 if((sockfd = socket(aip->ai_family,SOCK_STREAM,0))<0) 77 err = errno; 78 else 79 printf("socket successfully.\n"); 80 if(connect_retry(sockfd,aip->ai_addr,aip->ai_addrlen) < 0) 81 {err = errno; 82 printf("connect error\n"); 83 } 84 else 85 { 86 printf("connect server successfully.\n"); 87 printf("Receing time from server is: \n"); 88 print_uptime(sockfd); 89 exit(0); 90 } 91 } 92 fprintf(stderr,"can't connect to %s: %s\n",host,strerror(err)); 93 strerror(err); 94 exit(0); 95 }
服务器程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <arpa/inet.h> 5 #include <netdb.h> 6 #include <sys/socket.h> 7 #include <errno.h> 8 #include <syslog.h> 9 #include <string.h> 10 11 #define BUFLEN 128 12 #define QLEN 10 13 #ifndef Host_NAME_MAX 14 #define HOST_NAME_MAX 256 15 #endif 16 17 int initserver(int type,const struct sockaddr *addr,socklen_t alen,int qlen) 18 { 19 int fd; 20 int err = 0; 21 if((fd = socket(addr->sa_family,type,0)) < 0) 22 { 23 return -1; 24 } 25 printf("socket is created successfully.\n"); 26 if(bind(fd,addr,alen) < 0) 27 { 28 err = errno; 29 goto errout; 30 } 31 printf("bind successfully.\n"); 32 if(type == SOCK_STREAM || type == SOCK_SEQPACKET) 33 { 34 if(listen(fd,qlen) < 0) 35 { 36 err = errno; 37 goto errout; 38 } 39 printf("listen successfully.\n"); 40 } 41 return fd; 42 errout: 43 close(fd); 44 errno = err; 45 return -1; 46 } 47 48 void serve(int sockfd) 49 { 50 int clfd; 51 FILE *fp; 52 char buf[BUFLEN]; 53 printf("Prepareing for accpet client :\n"); 54 for(;;) 55 { 56 clfd = accept(sockfd,NULL,NULL); 57 if(clfd < 0) 58 { 59 syslog(LOG_ERR,"ruptime: accpet error: %s",gai_strerror(errno)); 60 exit(1); 61 } 62 printf("received a client request.\n"); 63 //通过管道调用uptime 64 if((fp =popen("/usr/bin/uptime","r")) == NULL) 65 { 66 sprintf(buf,"error: %s\n",strerror(errno)); 67 send(clfd,buf,strlen(buf),0); 68 } 69 else 70 { 71 while(fgets(buf,BUFLEN,fp) != NULL) 72 send(clfd,buf,strlen(buf),0); 73 pclose(fp); 74 } 75 close(clfd); 76 } 77 } 78 int main() 79 { 80 struct addrinfo *ailist,*aip; 81 struct addrinfo hint; 82 int sockfd; 83 int err,n; 84 char *host; 85 #ifdef _SC_HOST_NAME_MAX 86 n = sysconf(_SC_HOST_NAME_MAX); 87 if(n<0) 88 #endif 89 n = HOST_NAME_MAX; 90 host = (char*)malloc(n); 91 if(host == NULL) 92 { 93 perror("malloc() error"); 94 exit(-1); 95 } 96 if(gethostname(host,n) < 0) 97 { 98 perror("gethostname() error"); 99 exit(-1); 100 } 101 puts(host); 102 hint.ai_flags = AI_CANONNAME; 103 hint.ai_family = 0; 104 hint.ai_socktype = SOCK_STREAM; 105 hint.ai_protocol = 0; 106 hint.ai_addrlen = 0; 107 hint.ai_canonname = NULL; 108 hint.ai_addr = NULL; 109 hint.ai_next = NULL; 110 if(getaddrinfo(host,"ruptime",&hint,&ailist) != 0) 111 { 112 syslog(LOG_ERR,"getaddrinfo error: %s",gai_strerror(err)); 113 exit(-1); 114 } 115 for(aip = ailist;aip != NULL;aip = aip->ai_next) 116 if((sockfd = initserver(SOCK_STREAM,aip->ai_addr,aip->ai_addrlen,QLEN)) >= 0) 117 { 118 serve(sockfd); 119 exit(0); 120 } 121 exit(-1); 122 }
为了获取ruptime的网络信息,需要编辑/etc/services文件,追加ruptime 4000/tcp 。
程序执行结果如下:
客户端获取服务器时间:
服务器端响应客户请求:
20、带外数据:TCP支持带外数据,但是UDP不支持。TCP仅支持一个字节的紧急数据,但是允许紧急数据在普通数据传递机制数据流之外传输。为了产生紧急数据,在三个send函数中任何一个指定MSG_OOB。如果带MSG_OOB标志传输字节超过一个时,最后一个字节被看作紧急数据字节。当接收到紧急数据时,那么改善信号SIGURG。
TCP支持紧急标记的概念:在普通数据流中紧急数据所在的位置。如果采用套接字选项SO_OOBINLINE,那么可以在普通数据中接收紧急数据。为帮助判断是否接收到紧急标记,可以使用函数sockatmark
#include <sys/socket.h>
Int sockatmark ( int sockfd);
当下一个要读的字节在紧急标志所标识的位置时,sockatmark返回1。当带外数据出现在套接字读取队列时,select函数会返回一个文件描述符并且拥有一个异常状态挂起。可以在普通数据流上接受紧急数据,或者在某个recv函数中MSG_OOB标志在其他队列数据之前接收紧急数据。TCP队列仅有一字节的紧急数据,如果在接收当前的紧急数据字节之前又有新的紧急数据到来,那么当前的字节会被丢弃。
21、在基于套接字异步I/O中,当能够从套接字中读取数据,或者套接字写队列中的空间变得可用时,可以安排发送信号SIGIO。通过两个步骤来使用异步I/O:
1) 建立套接字拥有者关系,信号可以被传送到合适的进程。
2) 通知套接字当I/O操作不会阻塞时发信号告知。
可以使用三种方式来完成第一个步骤:
A、 在fcntl使用F_SETOWN命令
B、 在ioctl中作用FIOSETOWN命令
C、 在ioctl中使用SIOCSPGRP命令。
要完成第二个步骤,有两个选择:
A、 在fcntl中使用F_SETFL命令并且启用文件标志O_ASYNC。
B、 在ioctl中使用FIOASYNC