《unix高级环境编程》套接字——套接字基本描述

来源:互联网 发布:网络打印机 ip地址设置 编辑:程序博客网 时间:2024/06/05 15:33

 在前面部分介绍的:管道、FIFO、消息队列、信号量和共享内存都是同一台计算机上的进程间通信,本节介绍的套接字是可以实现不同计算机之间的远程进程间通信。套接口是网络进程的 ID,在网络中每一个节点都有一个网络地址,也就是 IP 地址,两个进程间通信时,首先要确定各自所在网络节点的网络地址。但是,网络地址只要确定进程所在的计算机,由于一台计算机上同时可能有多个网络进程,所以仅凭网络地址还不能确定是网络中的哪一个进程,因此套接口中还需要其他信息,也就是端口。在一台计算机中,一个端口号只能分配给一个进程,所以,进程和端口之间是一一对应的关系。因此,使用端口号和网络地址的组合就能唯一地确定整个网络中的一个网络进程。

        把网络应用程序中所用到的网络地址和端口号信息放在一个结构体中,也就是套接口地址结构。大多数的套接口函数都需要一个指向套接口地址结构的指针作为参数,并以此来传递地址信息。每个协议族都定义它自己的套接口地址结构,套接口地址结构都是以 sockaddr_ 开头,并以每个协议中名中的两个字母作为结尾。

套接字描述符

        套接字是通信端点的抽象,实现端对端之间的通信。与应用程序要使用文件描述符访问文件一样,访问套接字需要套接字描述符。因此,很多能够操作文件描述符的函数也能够操作套接字描述符。要对套接字进行操作必须获取该套接字的描述符,可以调用函数 socket 实现:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 套接字 */  
  2.   
  3. /* 
  4.  * 函数功能:创建套接字描述符; 
  5.  * 返回值:若成功则返回套接字描述符,若出错返回-1; 
  6.  * 函数原型: 
  7.  */  
  8. #include <sys/socket.h>  
  9.   
  10. int socket(int domain, int type, int protocol);  
  11. /* 
  12.  * 说明: 
  13.  * socket类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符; 
  14.  * domain表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下: 
  15.  * (1)AF_INET         IPv4因特网域 
  16.  * (2)AF_INET6        IPv6因特网域 
  17.  * (3)AF_UNIX         Unix域 
  18.  * (4)AF_UNSPEC       未指定 
  19.  * 
  20.  * type确定socket的类型,常用类型如下: 
  21.  * (1)SOCK_STREAM     有序、可靠、双向的面向连接字节流 
  22.  * (2)SOCK_DGRAM      长度固定的、无连接的不可靠报文传递 
  23.  * (3)SOCK_RAW        IP协议的数据报接口 
  24.  * (4)SOCK_SEQPACKET  长度固定、有序、可靠的面向连接的报文传递 
  25.  * 
  26.  * protocol指定协议,常用取值如下: 
  27.  * (1)0               选择type类型对应的默认协议 
  28.  * (2)IPPROTO_TCP     TCP传输协议 
  29.  * (3)IPPROTO_UDP     UDP传输协议 
  30.  * (4)IPPROTO_SCTP    SCTP传输协议 
  31.  * (5)IPPROTO_TIPC    TIPC传输协议 
  32.  * 
  33.  */  
        当我们不再使用套接字描述符时,可以使用 close 函数关闭该套接字,并且释放该套接字描述符。如果我们不想完全关闭该套接字描述符,而只是想关闭读、写端其中一个端时,可以使用函数 shutdown 实现:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 函数功能:关闭套接字上的输入或输出; 
  3.  * 返回值:若成功则返回0,若出错返回-1; 
  4.  * 函数原型: 
  5.  */  
  6. #include <sys/socket.h>  
  7. int shutdown(int sockfd, int how);  
  8. /* 
  9.  * 说明: 
  10.  * sockfd表示待操作的套接字描述符; 
  11.  * how表示具体操作,取值如下: 
  12.  * (1)SHUT_RD     关闭读端,即不能接收数据 
  13.  * (2)SHUT_WR     关闭写端,即不能发送数据 
  14.  * (3)SHUT_RDWR   关闭读、写端,即不能发送和接收数据 
  15.  * 
  16.  */  

寻址

字节序

        计算机在内存中的数据存储有两种方式:一种是小端字节序,即内存低地址存储数据低字节,内存高地址存储数据高字节;另一种是大端字节序,即内存低地址存储数据高字节,内存高地址存储数据低字节;具体如下图所示:


        网络字节序采用的是大端字节序。某个系统所采用的字节序称为主机字节序(也称处理器字节序),主机字节序可能是小端字节序,也有可能是大端字节序。在网络协议中处理多字节数据时采用的都是网络字节序,即大端字节序,而不是主机字节序。要把主机字节序和网络字节序相对应,则必须采用字节序转换函数,以下是主机字节序和网络字节序之间的转换函数:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 函数功能:主机字节序和网络字节序之间的转换; 
  3.  * 返回值:返回对应类型表示的字节序; 
  4.  * 函数原型: 
  5.  */  
  6. #include <arpa/inet.h>  
  7. uint32_t htonl(uint32_t hostint32);//返回值:以网络字节序表示的32位整型数;  
  8. uint16_t htons(uint16_t hostint16);//返回值:以网络字节序表示的16位整型数;  
  9.   
  10. uint32_t ntohl(uint32_t netint32);//返回值:以主机字节序表示的32位整型数;  
  11. uint16_t ntohs(uint16_t netint16);//返回值:以主机字节序表示的16位整型数;  
  12. /* 
  13.  * 说明: 
  14.  * 从以上函数我们可以发现: 
  15.  * h表示“主机(host)”字节序,n表示“网络(network)”字节序; 
  16.  * l表示“长(long)”整型,s表示“短(short)”整型; 
  17.  * 
  18.  */  

字节操作函数

        在套接字编程中,经常会使用到字节操作函数,下面是一些经常用到的字节操作函数:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 函数功能:字节操作; 
  3.  * 函数原型: 
  4.  */  
  5. #include <strings.h>  
  6. void bzero(void *dest, size_t nbytes);//将dest所存储的数据前nbytes字节初始化为0;  
  7. void bcopy(const void *str, void *dest, size_t nbytes);//将str所存储的数据前nbytes字节复制到dest中;  
  8. int bcmp(const void *ptr1, const void *ptr2, size_t nbytes);//比较两个字符的前nbytes字节的大小;  
  9.   
  10. void *memset(void *dest, int c, size_t len);//将dest所存储的数据前len字节初始化为c;  
  11. void *memcopy(void *dest,const void *src, size_t nbytes);//将src所存储的数据前nbytes字节复制到dest中;  
  12. int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);//比较两个字符的前nbytes字节的大小;  

套接字的地址数据结构

        套接字的地址标识了特定通信域中的套接字端点,套接字数据结构与使用它的网络(通信域)有关。在 Linux 中,每一种协议都有自己的网络地址数据结构,这些结构以 sockaddr_ 开头,不同的后缀表示不同的协议,例如 IPv4 对应的是 sockaddr_in 。为了使不同格式的地址数据结构能够传入到套接字函数中,则地址会被强制转换成通用的地址数据结构 sockaddr 表示,通用地址数据结构定义如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 地址数据结构 */  
  2.   
  3. /* 通用的地址数据结构 */  
  4. struct sockaddr  
  5. {  
  6.     uint8_t         sa_len;     /* total length */  
  7.     sa_family_t     sa_family;  /* address family */  
  8.     char            sa_data[];  /* variable-length address */  
  9. };  
  10. /* Linux 下的地址数据结构 */  
  11. struct sockaddr  
  12. {  
  13.     uint8_t         sa_len;     /* total length */  
  14.     sa_family_t     sa_family;  /* address family */  
  15.     char            sa_data[14];  /* length address */  
  16. };  
        若要把网络字节序的地址打印成我们能够理解的格式,则需要转换函数把网络字节序转换成我们可读的地址格式,inet_ntop 和 inet_pton 这两个函数把对 IP 地址格式进行转换,其定义如下:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include<arpa/inet.h>  
  2. const char *inet_ntop(int domain, const void *restrict addr, char *restrict str, socklen_t size);  
  3. //若成功则返回地址字符串指针,出错则返回NULL;  
  4.   
  5. int inet_pton(int domain, const char *restrict str, void *restrict addr);  
  6. //若成功则返回1,格式无效则返回0,出错则返回-1;  
  7. /* 
  8.  * 说明: 
  9.  * inet_ntop 是将网络字节序的二进制地址转换成文本字符串格式; 
  10.  * inet_pton 是将文本字符串格式转换成网络字节序的二进制地址; 
  11.  * 参数domain只能取值:AF_INET 或 AF_INET6; 
  12.  */  

地址查询

        为了获取主机的信息,我们可以调用以下函数进行实现,也可实现名字地址和数字地址之间的转换;

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 地址查询 */  
  2.   
  3. /* 
  4.  * 函数功能:获取给定计算机的主机信息; 
  5.  * 返回值:若成功则返回主机结构指针,若出错则返回NULL; 
  6.  * 函数原型: 
  7.  */  
  8. #include <netdb.h>  
  9.   
  10. struct hostent *gethostbyname(const char *hostname);//将主机名地址转换为数字地址;  
  11. struct hostent *gethostaddr(const char *addr, size_t len, int family);//将主机数字地址转换为名字地址;  
  12. struct hostent *gethostent(void);  
  13.   
  14. void sethostent(int stayopen);  
  15. void endhostent(void);  
  16. /* 
  17.  * 说明: 
  18.  * 若主机数据文件没有打开,gethostent会打开它,该函数返回文件的下一条目; 
  19.  * 函数sethostent会打开文件,若文件已打开,那么将其回绕; 
  20.  * 函数endhostent将关闭文件; 
  21.  * 其中hostent结构至少包含如下成员数据: 
  22.  */  
  23. struct hostent  
  24. {  
  25.     char    *h_name;        /* name of host */  
  26.     char    **h_aliases;    /* pointer to alternate host name array */  
  27.     int     h_addrtype;     /* address type */  
  28.     int     h_length;       /* length in bytes of address */  
  29.     char    **h_addr_list;  /* pointer to array of network address */  
  30. };  

       上面的函数若成功调用,则会返回一个指向 hostent 结构的指针,若出错则返回 NULL,且设置全局变量 h_error 为相应值。一般的 socket 系统调用都将错误信息存储在全局变量 error 中,但是和主机 host 有关的系统调用,则将错误信息存储在 h_error 中,它的取值如下:

  1. HOST_NOT_FOUND:找不到主机;
  2. TRY_AGAIN:重试;
  3. NO_RECOVERY:不可修复性错误;
  4. NO_DATA:指定的名字有效,但是没有记录;

获取网络名字和网络号

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 函数功能:获取网络名和网络号; 
  3.  * 返回值:若成功则返回指针,出错返回NULL; 
  4.  * 函数原型: 
  5.  */  
  6. #include <netdb.h>  
  7. struct netent *getnetbyaddr(uint32_t net, int type);  
  8. struct netent *getnetbyname(const char *name);  
  9. struct netent *getnetent(void);  
  10.   
  11. void setnetent(int stayopen);  
  12. void endnetent(void);  
  13. /* 
  14.  * netent 结构至少包含以下成员: 
  15.  */  
  16. struct netent  
  17. {  
  18.     char    *n_name;        /* network name */  
  19.     char    *n_aliases;     /* alternate network name array pointer */  
  20.     int     n_addrtype;     /* address type */  
  21.     uint32_t n_net;         /* network number */  
  22. };  

将协议名字和协议号采用以下函数映射:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include <netdb.h>  
  2. struct protoent *getprotobyname(const char *name);  
  3. struct protoent *getprotobynumber(int proto);  
  4. struct protoent *getprotoent(void);  
  5. //以上三个函数返回值:若成功则返回指针,出错返回NULL;  
  6.   
  7. void setprotornt(int stayopen);  
  8. void endprotornt(void);  
  9. /* 
  10.  * protoent 结构至少包含以下成员: 
  11.  */  
  12. struct protoent  
  13. {  
  14.     char    *p_name;    /* protocol name */  
  15.     char    **p_aliases;/* pointer to alternate protocol name array */  
  16.     int     p_proto;    /* protocol number */  
  17. };  

端口号与服务名之间的映射:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 函数功能:服务名与端口号之间的映射; 
  3.  * 返回值:若成功则返回指针,若出错则返回NULL; 
  4.  * 函数原型: 
  5.  */  
  6. #include <netdb.h>  
  7. struct servent *getservbyname(const char *name, const char *proto);  
  8. struct servent *getservbyport(int port, const char *proto);  
  9. struct servent *getservent(void);  
  10.   
  11. void setservent(int stayopen);  
  12. void endservent(void);  
  13. /* 
  14.  * 其中servent 结构至少包含以下成员: 
  15.  */  
  16. struct servent  
  17. {  
  18.     char    *s_name;        /* service name */  
  19.     char    **s_aliases;    /* pointer to alternate service name array */  
  20.     int     s_port;         /* port number */  
  21.     char    *s_proto;       /* name of protocol */  
  22. };  

地址与主机名和服务名之间的转换:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /* 
  2.  * 函数功能:将服务名和主机名映射到一个地址; 
  3.  * 返回值:若成功则返回0,若出错则返回非0错误编码; 
  4.  * 函数原型: 
  5.  */  
  6. #include <netdb.h>  
  7. #include <sys/socket.h>  
  8. int getaddrinfo(const char *host, const char *service, const struct addrinfo *hint, struct addrinfo **res);  
  9.   
  10. void freeaddrinfo(struct addrinfo *ai);  
  11.   
  12. const char *gai_strerror(int error);//若getaddrinfo出错时,错误消息只能由该函数输出;  
  13.   
  14. /* 
  15.  * 说明: 
  16.  * 该函数需要提供主机名或服务名,若只提供其中一个,则另一个必须指定为NULL; 
  17.  * addrinfo是一个结构链表,其定义如下: 
  18.  */  
  19. struct addrinfo  
  20. {  
  21.     int         ai_flags;       /* customize behavior */  
  22.     int         ai_family;      /* address family */  
  23.     int         ai_socktype;    /* socket type */  
  24.     int         ai_protocol;    /* protocol */  
  25.     socklen_t   ai_addrlen;     /* length in bytes of address */  
  26.     struct sockaddr *ai_addr;   /* address */  
  27.     char        *ai_canonname;  /* canonical name of host */  
  28.     struct addrinfo *ai_next;   /* next in list */  
  29. };  
  30. /* 
  31.  * 函数功能:将地址转换成服务名或主机名; 
  32.  * 返回值:若成功则返回0,若出错则返回非0值; 
  33.  * 函数原型: 
  34.  */  
  35. #include <netdb.h>  
  36. #include <sys/socket.h>  
  37. int getnameinfo(const struct sockadd *addr, socklen_t alen, char * host, socklen_t hostlen,  
  38.         char * service, socklen_t servlen, unsigned int flags);  

测试程序:打印主机和服务信息;

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #include "apue.h"  
  2. #include <netdb.h>  
  3. #include <arpa/inet.h>  
  4. #include <sys/socket.h>  
  5.   
  6. void print_family(struct addrinfo *aip);  
  7. void print_type(struct addrinfo *aip);  
  8. void print_flags(struct addrinfo *aip);  
  9. void print_protocol(struct addrinfo *aip);  
  10.   
  11. int main(int argc, char **argv)  
  12. {  
  13.     struct addrinfo *ailist, *aip;  
  14.     struct addrinfo hint;  
  15.   
  16.     /* 定义IPv4的套接字地址结构 */  
  17.     struct sockaddr_in *sinp;  
  18.   
  19.     const char *addr;  
  20.     int err;  
  21.     char abuf[INET_ADDRSTRLEN];  
  22.   
  23.     if(argc != 3)  
  24.         err_quit("usage: %s nodename service", argv[0]);  
  25.     /* 初始化addrinfo结构变量hint*/  
  26.     hint.ai_family = 0;  
  27.     hint.ai_socktype = 0;  
  28.     hint.ai_protocol = 0;  
  29.     hint.ai_addrlen = 0;  
  30.     hint.ai_flags = AI_CANONNAME;//需要一个规范名,而不是别名;  
  31.     hint.ai_addr = NULL;  
  32.     hint.ai_next = NULL;//表示只有一个addrinfo链表结构;  
  33.     hint.ai_canonname = NULL;  
  34.   
  35.     /* 将主机名和服务名映射到一个地址 */  
  36.     if((err = getaddrinfo(argv[1], argv[2], &hint, &ailist)) != 0)  
  37.         err_quit("getaddrinfo error: %s\n", gai_strerror(err));  
  38.   
  39.     /* 打印主机和服务信息 */  
  40.     for(aip = ailist; aip != NULL; aip = aip->ai_next)  
  41.     {  
  42.         print_family(aip);  
  43.         print_type(aip);  
  44.         print_protocol(aip);  
  45.         print_flags(aip);  
  46.   
  47.         printf("\n\thost %s", aip->ai_canonname ?aip->ai_canonname:"-");  
  48.         if(aip->ai_family == AF_INET)  
  49.         {  
  50.             /* 获取IP地址,并把网络字节序的二进制地址转换为文本字符串地址 */  
  51.             sinp = (struct sockaddr_in *)aip->ai_addr;  
  52.             addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf, INET_ADDRSTRLEN);  
  53.             printf(" address %s", addr ? addr:"unknown");  
  54.             printf(" port %d", ntohs(sinp->sin_port));  
  55.         }  
  56.         printf("\n");  
  57.     }  
  58.     exit(0);  
  59. }  
  60. void print_family(struct addrinfo *aip)  
  61. {  
  62.     printf(" family-- ");  
  63.     switch(aip->ai_family)  
  64.     {  
  65.         case AF_INET://IPv4  
  66.             printf("inet");  
  67.             break;  
  68.         case AF_INET6://IPv6  
  69.             printf("inet6");  
  70.             break;  
  71.         case AF_UNIX://UNIX域  
  72.             printf("Unix");  
  73.             break;  
  74.         case AF_UNSPEC://未指定  
  75.             printf("unspecified");  
  76.             break;  
  77.         default:  
  78.             printf("unknown");  
  79.     }  
  80. }  
  81. void print_type(struct addrinfo *aip)  
  82. {  
  83.     printf(" type.. ");  
  84.     switch(aip->ai_socktype)  
  85.     {  
  86.         case SOCK_STREAM:  
  87.             printf("stream");  
  88.             break;  
  89.         case SOCK_DGRAM:  
  90.             printf("datagram");  
  91.             break;  
  92.         case SOCK_RAW:  
  93.             printf("raw");  
  94.             break;  
  95.         case SOCK_SEQPACKET:  
  96.             printf("seqpacket");  
  97.             break;  
  98.         default:  
  99.             printf("unknown (%d)", aip->ai_socktype);  
  100.     }  
  101. }  
  102.   
  103. void print_protocol(struct addrinfo *aip)  
  104. {  
  105.     printf(" protocol++ ");  
  106.     switch(aip->ai_protocol)  
  107.     {  
  108.         case IPPROTO_TCP:  
  109.             printf("TCP");  
  110.             break;  
  111.         case IPPROTO_UDP:  
  112.             printf("UDP");  
  113.             break;  
  114.         case IPPROTO_SCTP:  
  115.             printf("SCTP");  
  116.             break;  
  117.         case 0:  
  118.             printf("default");  
  119.             break;  
  120.         default:  
  121.             printf("unknown (%d)", aip->ai_protocol);  
  122.     }  
  123. }  
  124. void print_flags(struct addrinfo *aip)  
  125.  {  
  126.      printf(" flags ");  
  127.      if(aip->ai_flags == 0)  
  128.          printf("0");  
  129.      else  
  130.      {  
  131.          if(aip->ai_flags & AI_PASSIVE)  
  132.              printf(" passive ");  
  133.          if(aip->ai_flags & AI_CANONNAME)  
  134.              printf(" canon ");  
  135.          if(aip->ai_flags & AI_NUMERICHOST)  
  136.              printf(" numhost ");  
  137.      }  
  138.  }  

输出结果:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. ./socket localhost nfs  
  2.  family-- inet type.. stream protocol++ TCP flags  canon   
  3.     host localhost address 127.0.0.1 port 2049  
  4.  family-- inet type.. datagram protocol++ UDP flags  canon   
  5.     host - address 127.0.0.1 port 2049  
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 遛狗碰到碰瓷老太太怎么办 碰到保安碰瓷的人怎么办 碰到碰瓷的车怎么办 商品房楼上邻居违规装修怎么办 遇到不讲道理的邻居怎么办 邻居告我我该怎么办 丈夫判刑十年回来妻子怎么办 有人发色情信息给妻子怎么办 小孩拉蛋花样便怎么办 鸡下的蛋壳是软怎么办 幼升小错过了现场审核怎么办 老人户口迁移到北京医保怎么办 过山洞经常堵耳朵怎么办 门有了一个洞该怎么办 公司要求补税没钱补怎么办 公司补税补不起了怎么办 脚被石头砸肿了怎么办 砸到脚背肿了怎么办 小猫吃完饭抓地怎么办 耳机链接处断了怎么办 表链从表盘断了怎么办 龟头和皮分开了怎么办 微信买票被骗了怎么办 撞车了我的全责怎么办 蒙田包包里面不耐脏怎么办? 摩托车转向灯不会打怎么办 浓硫酸弄到脸上怎么办 钥匙被锁在家里怎么办 有奶宝宝吸不出来怎么办 奶涨宝宝吸不出来怎么办 高铁票未取误点怎么办 飞猪上12306占座失败怎么办? 新生儿肚脐还没有脱落发炎怎么办 蹲坑被纸巾堵了怎么办 老公有外遇老婆不想离婚怎么办 结婚十年妻子出轨该怎么办 初生儿眼睛多眼屎怎么办 被丝袜脚摩擦过瘾了怎么办 老公在卧室装摄像头怎么办 听了鬼故事害怕怎么办 看完鬼片害怕睡不着怎么办