本章将考察不同计算机(通过网络连接)上的进程相互通信的机制:网络进程间通信。
套接字描述符
正如使用文件描述符访问文件,应用程序用套接字描述符访问套接字。
许多处理文件描述符函数(如read和write)可以用于处理套接字描述符。调用socket函数创建一个套接字
#include <sys/socket.h> int socket(int domain,int type,int protocol);
参数domain(域)确定通信的特性,包括地址格式。下图总结了POSIX.1指定的各个域,每个域都有自己表示地址的格式
参数type确定套接字的类型,下图总结了POSIX.1定义的套接字类型。
参数protocol通常是0,表示为给定的域和套接字类型选择默认协议。
调用socket与调用open相类似。可以调用close来关闭对文件或套接字的访问,并且释放该描述符以便重新使用。
下图总结了到目前为止所讨论的大多数以文件描述符为参数的函数使用套接字描述符时的行为。
套接字通信是双向的。可以采用shutdown函数来禁止一个套接字的I/O
#include <sys/socket.h> int shutdown(int sockfd,int how);
如果how是SHUT_RD(关闭读端),那么无法从套接字读取数据。
如果how是SHUT_WR(关闭写端),那么无法使用套接字发送数据。
如果how是SHUT_RDWR,则既无法读取数据,又无法发送数据。
使用close函数直到关闭了最后一个引用它的文件描述符才会释放这个套接字,而shutdown允许使用一个套接字处于不活动状态,和引用它的文件描述符数目无关。
字节序
有4个用来处理处理器字节序和网络字节序之间实施转换的函数
#include <arpa/inet.h> uint32_t htonl(unit32_t hostint32); //返回值:以网络字节序表示的32位整数 uint16_t htons(unit16_t hostint16); //返回值:以网络字节序表示的16位整数 uint32_t ntohl(unit32_t netint32); //返回值:以主机字节序表示的32位整数 uint32_t ntons(unit16_t netint16); //返回值:以主机字节序表示的16位整数
地址格式
一个地址标识一个特定通信域的套接字端点,地址格式与这个特定的通信域相关。
为使不同的格式地址能够传入到套接字函数,地址会被强制转换成一个通用的地址结构sockaddr(Linux中的定义):
struct sockaddr{ sa_family_t sa_family; char sa_data[14]; };
因特网地址定义在<netinet/in.h>头文件中。在IPv4因特网域中,套接字地址用结构sockaddr_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; };
IPv6因特网域套接字地址用结构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; };
下面两个函数用于二进制地址格式与十进制字符表示(a.b.c.d)之间的相互转换:
#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);
地址查询
通过调用gethostend,可以找到给定计算机系统的主机信息
#include <netdb.h> struct hostent *gethostent(void); void sethostent(int stayopen); void endhostent(void);
如果主机数据库文件没有打开,gethostent会打开它。函数gethostent返回文件中的下一个条目。
函数sethostent会打开文件,如果文件已经被打开,那么将其回绕。
hostent结构如下至少包含以下成员:
struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ };
我们可以用以下函数在协议名字和协议编号之间进行映射:
#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);
POSIX.1定义的protoent结构至少包含以下成员:
struct protoent { char *p_name; /* official protocol name */ char **p_aliases; /* alias list */ int p_proto; /* protocol number */ };
可以使用函数getservbyname将一个服务名映射到一个端口号,使用函数getservbyport将一个端口号映射到一个服务名,使用函数getservent顺序扫描服务数据库
#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);
servent结构至少包含以下成员:
struct servent { char *s_name; /* official service name */ char **s_aliases; /* alias list */ int s_port; /* port number */ char *s_proto; /* protocol to use */ };
POSIX.1定义了若干新的函数,允许一个应用程序将一个主机名和一个服务器名映射到一个地址
#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);
需要提供主机名、服务名,或者两者都提供。函数返回一个链表结构addrinfo,可以用freeaddrinfo来释放一个或多个这种结构。
addrinfo结构的定义至少包含以下成员:
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; };
可以提供一个可选的hint来选择符合特定条件的地址。
如果getaddrinfo失败,需要调用gai_strerror将返回的错误码转换成错误消息
#include <netdb.h> const char *gai_strerror(int error);
getnameinfo函数将一个地址转换成一个主机名和一个服务名
#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,int flags);
套接字地址被翻译为一个主机名和一个服务名。flags参数提供了一些控制翻译的方式,下图总结了支持的标志
下面程序说明了getaddrinfo函数的使用方法
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 }