《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命令将时间发送给客户端。程序如下:

客户端程序:

View Code
 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 }

服务器程序如下:

View Code
  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

posted @ 2012-12-23 22:16  Rabbit_Dale  阅读(1499)  评论(0编辑  收藏  举报