• 《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

  • 相关阅读:
    ADO.NET 六(DataRow DataColumn)
    ADO.NET 五(DataAdapter 与 DataSet)
    ADO.NET 四(DataReader)
    ADO.NET 三(Command)
    ADO.NET 二(Connection)
    面试知识点笔记-1
    【新弹性盒】
    设置弹性盒要用到的属性
    Vue项目模板--和--webpack自动化构建工具的---项目打包压缩使用
    Vue中使用【watch检测路由】的方法
  • 原文地址:https://www.cnblogs.com/Anker/p/2830352.html
Copyright © 2020-2023  润新知