- 进程通信(interProcess Communication ,IPC)
- 套接字描述符
套接字是通信端点的抽象。
参数domain(域)确定通信的特性,包括地址格式。#include<sys/socket.h> int socket(int domain, int type, int protocol); 返回值:若成功,返回文件(套接字)描述符;若出错,返回 -1
参数type确定套接字的类型,进一步确定通信特征。
参数protocol通常是0,表示为给定的域和套接字选择默认协议。
通过网络套接字可以使得不同计算机上运行的进程相互通信。
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命令将时间发送给客户端。程序如下:
客户端程序:
服务器程序如下:
为了获取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