• 网络套接字与寻址


    1 套接字描述

    套接字是通信端点的抽象,创建一个套接字使用如下函数:

    #include <sys/socket.h>

    int socket(int domain, int type, int protocol);

    返回值:若成功,返回套接字描述符;若出错,返回-1.

     

    参数:

    domain: 指定通信的特征,包括地址格式,以AF_开头的常数表示地址族(address family):

    描述

    AF_INET

    IPv4因特网域

    AF_INET6

    IPv6因特网域

    AF_UNIX

    UNIX域

    AF_UPSPEC

    未指定(可以代表任何域)

    type: 指定套接字的类型。如下为POSIX.1的套接字类型,也可以增加其他类型的支持:

    类型

    描述

    SOCK_DGRAM

    固定长度、无连接、不可靠

    SOCK_RAM

    IP协议的数据报接口

    SOCK_SEQPACKET

    固定长度、面向连接、有序、可靠

    SOCK_STREAM

    有序、可靠、双向、面向连接

    protocol: 根据指定的domain和type所提供的默认协议,通常设为0即可。在AF_INET通信域中,SOCK_STREAM默认的协议为TCP;SOCK_DGRAM默认的协议为UDP.

     

    使用无连接的数据报通信类似于邮寄信件,你不能保证传递的次序,信件可能会丢失,每封信都包含收信人地址;相反地,使用面向连接的通信协议类似于打电话,首先需要建立连接,连接建立好以后,彼此可以双向地通信,对话中不需要包含地址信息,连接本身指定了通话的源和目的,就像是端与端之间有一条虚拟链路一样。

     

    SOCK_STREAM套接字提供了字节流服务,应用程序不能分辨出报文的界限;SOCK_SEQPACKET套接字提供了基于报文的服务,这意味着接受的数据量和发送的数据量完全一致;SOCK_RAW套接字提供了一个数据报接口,用于直接访问下面的网络层,使用该套接字时,必须有root用户权限,并且需要应用程序自己负责构造协议头。

     

    套接字通信是双向的,可以使用shutdown函数来禁止一个套接字I/O:

    #include <sys/socket.h>

    int shutdown(int sockfd, int how);

    返回值:若成功,返回0;若出错,返回-1.

     

    参数:

    how: SHUT_RD(关闭读),SHUT_WR(关闭写),SHUT_RDWR(关闭读写).

     

    套接字本质上是一个文件描述符,如下为适用于文件描述符的函数在套接字中的表现行为:

    文件描述符函数

    使用套接字时的行为

    close

    释放套接字

    dup / dup2

    复制套接字

    fchdir

    失败,并且将errno设置为ENOTDIR

    fchomod

    未指定

    fchown

    由实现定义

    fcntl

    支持某些命令

    fdatasync / fsync

    由实现定义

    fstat

    支持一些stat结构成员,如何支持由实现定义

    ftruncate

    未指定

    ioctl

    依赖于底层设备驱动

    lseek

    由实现定义,失败时将errno设置为ESPIPE

    mmap

    未指定

    poll

    正常工作

    pread / pwrite

    失败时,将errno设置为ESPIPE

    read / readv

    与没有任何标志位的recv等价

    select

    正常工作

    write / writev

    与没有任何标志位的send等价

     

    2 寻址

    :: 不同的处理器架构有着不同的字节序,如果需要实现异构通信,必须统一字节序方式。网络协议指定了字节序(网络字节序),TCP/IP协议栈使用大端方式,对于使用TCP/IP的应用程序,有4个函数可以用来在处理器字节序和网络字节序之间进行转换:

    #include <arpa/inet.h>

    uint32_t htonl(uint32_t hostint32);  // 32位主机-->32位网络

    uint16_t htons(uint16_t hostint16);  // 16位主机-->16位网络

    uint32_t ntohl(uint32_t netint32); // 32位网络-->32位主机

    uint16_t ntohs(uint16_t netint16); // 16位网络-->16位主机

     

    说明:

    h: 主机字节序

    n: 网络字节序

    l: 4字节的长整型

    s: 2字节的短整型

     1 #include <arpa/inet.h>
     2 #include <stdio.h>
     3 
     4 int main(void)
     5 {
     6         unsigned int bytes = 0x12345678;
     7         const char* p = (const char*)&bytes;
     8         printf("%x_%x_%x_%x
    ", p[0], p[1], p[2], p[3]);
     9 
    10         unsigned int netbytes = htonl(bytes);    /* 将主机字节序转换为网络字节序 */
    11         p = (const char*)&netbytes;
    12         printf("%x_%x_%x_%x
    ", p[0], p[1], p[2], p[3]);
    13         
    14         return 0;
    15 }
    
    

    由于地址格式与特定的通信域相关,因此,为了使得不同格式的地址都能够传入套接字函数,所有的地址必须先被强制转换为一个通用的地址结构sockaddr:

    struct sockaddr

    {

        unsigned char sa_len;    /* total length */

        sa_family_t sa_family;   /* address family */

        char sa_data[14];    /* variable-length address */

    };

     

    因特网地址定义在<netinet/in.h>中:

    struct in_addr

    {

        in_addr_t       s_addr;     /* IPv4 address */

    };

     

    struct sockaddr_in

    {

        sa_family_t     sin_family; /* address_family */

        in_port_t       sin_port;   /* port number */

        struct in_addr  sin_addr;   /* IPv4 address */

    };

    数据类型in_port_t为uint16_t,in_addr_t为uint32_t,在<stdint.h>中定义了这些类型。

     

    :: 如下函数可以在数值地址和文本格式字符串地址之间进行转换:

    #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);

     

    参数:

    domain: AF_INET或者AF_INET6.

    size: 指定保存文本字符串缓冲区str的大小,该参数为INET_ADDRSTREAM或者INET6_ADDRSTREAM时,表明使用足够大的空间来存放该地址。

     

    说明:

    inet_pton的输出为网络字节序,inet_ntop的输入为网络字节序,要注意转换。

     1 #include <arpa/inet.h>
     2 #include <stdio.h>
     3 
     4 int main(void)
     5 {
     6         char dotaddr[] = "192.168.8.128";
     7         struct in_addr ipaddr;
     8 
     9         inet_pton(AF_INET, dotaddr, (void*)&ipaddr);
    10 
    11         ipaddr.s_addr = ntohl(ipaddr.s_addr);    /* 将网络字节序转换为主机字节序 */
    12         printf("addr = %x
    ", ipaddr.s_addr);
    13 
    14         ipaddr.s_addr = htonl(ipaddr.s_addr);    /* 将主机字节序转换为网络字节序 */
    15         inet_ntop(AF_INET, (void*)&ipaddr, dotaddr, 16);
    16         printf("addr = %s
    ", dotaddr);
    17 
    18         return 0;
    19 }            
    
    

    地址查询:

    #include <netdb.h>

    struct hostent* gethostent(void);      /* 返回下一个文件 */

    void sethostent(int stayopen);       /* 打开文件 */

    void endhostent(void);                   /* 关闭文件 */

     

    :: gethostent返回一个指向hostent结构体的指针,hostent结构如下:

    struct hostent

    {

        char *h_name;                          /* 主机名 */

        char **h_aliases;                   /* 可选的别名列表 */

        int h_addrtype;                        /* 地址类型,一般为AF_INET */

        int h_length;                           /* 地址长度 */

        char **h_addr_list;                      /* 网络地址列表 */

     

        #define h_addr h_addr_list[0];  /* 第一个网络地址 */

    };

     

    说明:

    之所以主机的地址是一个列表的形式,其原因是一个主机可能有多个网络接口。

    返回的地址为网络字节序。

     1 #include <stdio.h>
     2 #include <sys/socket.h>
     3 #include <netdb.h>
     4 #include <netinet/in.h>
     5 
     6 int main()
     7 {
     8         struct hostent* h;
     9         h = gethostbyname("benxintuzi");
    10         printf("host: %s
    ", h->h_name);
    11         printf("addr: %s
    ", inet_ntoa(*((struct in_addr*)h->h_addr)));
    12 
    13         return 0;
    14 }
    
    

    :: 如下函数用来获得网络名字和网络编号:

    #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);

     

    netent结构体如下定义:

    struct netent

    {

        char* n_name;       /* network name */

        char** n_aliases;   /* alternate network name array pointer */

        int n_addrtype;     /* net address type */

        uint32_t n_net;     /* network number*/

    };

     1 #include <stdio.h>
     2 #include <netdb.h>
     3 
     4 void printnet(struct netent* net)
     5 {
     6         char** p = net->n_aliases;
     7         printf("net name: %s
    ", net->n_name);
     8         
     9         while(*p != NULL)
    10         {
    11                 printf("alias name: %s
    ", *p);
    12                 p++;
    13         }
    14         
    15         printf("address type: %d
    ", net->n_addrtype);
    16 
    17         printf("net number: %u
    ", net->n_net);
    18 }
    19 
    20 int main()
    21 {
    22         struct netent* net = NULL;
    23         setnetent(1);
    24         while((net = getnetent()) != NULL)
    25         {
    26                 printnet(net);
    27                 printf("
    ");
    28         }
    29         endnetent();
    30 
    31         return 0;
    32 }
    
    

    :: 如下函数用于操作协议名和协议编号:

    #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;                 /* Official protocol name.  */

      char **p_aliases;             /* Alias list.  */

      int p_proto;                  /* Protocol number.  */

    };

     1 #include <netdb.h>
     2 
     3 void printproto(struct protoent* proto)
     4 {
     5         char** p = proto->p_aliases;
     6         printf("proto name: %s
    ", proto->p_name);
     7 
     8         while(*p != NULL)
     9         {
    10                 printf("alias name: %s
    ", *p);
    11                 p++;
    12         }
    13 
    14 
    15         printf("proto number: %d
    ", proto->p_proto);
    16 }
    17 
    18 int main()
    19 {
    20         struct protoent* proto = NULL;
    21         setprotoent(1);
    22         while((proto = getprotoent()) != NULL)
    23         {
    24                 printproto(proto);
    25                 printf("
    ");
    26         }
    27         endprotoent();
    28 
    29         return 0;
    30 }
    
    

    :: 如下函数用于操作服务于端口号:

    #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);

     

    struct servent

    {

      char *s_name;                 /* Official service name.  */

      char **s_aliases;             /* Alias list.  */

      int s_port;                   /* Port number.  */

      char *s_proto;                /* Protocol to use.  */

    };

     1 #include <stdio.h>
     2 #include <netdb.h>
     3 
     4 void printservent(struct servent* serv)
     5 {
     6         char** p = serv->s_aliases;
     7         printf("servent name: %s
    ", serv->s_name);
     8 
     9         while(*p != NULL)
    10         {
    11                 printf("alias name: %s
    ", *p);
    12                 p++;
    13         }
    14 
    15         printf("port number: %d
    ", serv->s_port);
    16 
    17         printf("proto to use: %s
    ", serv->s_proto);
    18 }
    19 
    20 int main()
    21 {
    22         struct servent* serv = NULL;
    23         setservent(1);
    24         while((serv = getservent()) != NULL)
    25         {
    26                 printservent(serv);
    27                 printf("
    ");
    28         }
    29         endservent();
    30 
    31         return 0;
    32 }
    
    

    :: POSIX.1中定义的函数getaddrinfo用来代替过时的gethostbyname和gethostbyaddr

    #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(sruct addrinfo* ai);

     

    struct addrinfo

    {

      int ai_flags;                 /* Input flags.  */

      int ai_family;                /* Protocol family for socket.  */

      int ai_socktype;              /* Socket type.  */

      int ai_protocol;              /* Protocol for socket.  */

      socklen_t ai_addrlen;         /* Length of socket address.  */

      struct sockaddr *ai_addr;     /* Socket address for socket.  */

      char *ai_canonname;           /* Canonical name for service location.  */

      struct addrinfo *ai_next;     /* Pointer to next in list.  */

    };

     

    getaddrinfo函数根据提供的主机名或服务名返回一个addrinfo链表;freeaddrinfo用来释放一个addrinfo结构体。如果getaddrinfo失败了,必须调用gai_strerror将返回的错误码转换成错误消息:

    const char* gai_strerror(int error);

     

    如果host非空,则指向一个长度为hostlen字节的缓冲区,用于存放返回的主机名;同样,如果service非空,则指向一个长度为servlen字节的缓冲区,用于返回的服务名。

    具体的flags参数如下:

    标志

    描述

    NI_DGRAM

    服务基于数据报而非基于流

    NI_NAMEREQD

    如果找不到主机名,将其作为一个错误对待

    NI_NOFQDN

    对于本地主机,仅返回全限定域名的节点名部分

    NI_NUMERICHOST

    返回主机地址的数字形式,而非主机名

    NI_NUMERICSCOPE

    对于IPv6,返回数字形式

    NI_NUMERICSERV

    返回服务地址的数字形式(即端口号)

     1 #include <stdio.h>
     2 #include <arpa/inet.h>
     3 #include <netdb.h>
     4 #include <sys/socket.h>
     5 #include <netinet/in.h>
     6 
     7 int main(void)
     8 {
     9         struct addrinfo*        ailist, *aip;
    10         struct addrinfo         hint;
    11         struct sockaddr_in*     sinp;
    12         const char*             addr;
    13         int                     err;
    14         char                    abuf[INET_ADDRSTRLEN];
    15 
    16         hint.ai_flags   = AI_CANONNAME;
    17         hint.ai_family  = 0;
    18         hint.ai_socktype        = 0;
    19         hint.ai_protocol        = 0;
    20         hint.ai_addrlen         = 0;
    21         hint.ai_canonname       = NULL;
    22         hint.ai_addr            = NULL;
    23         hint.ai_next            = NULL;
    24 
    25         if((err = getaddrinfo("benxintuzi", "nfs", &hint, &ailist)) != 0)
    26                 printf("getaddrinfo error: %s", gai_strerror(err));
    27         for(aip = ailist; aip != NULL; aip = aip->ai_next)
    28         {
    29                 printf("host: %s
    ", aip->ai_canonname ? aip->ai_canonname : "-");
    30                 if(aip->ai_family == AF_INET)
    31                 {
    32                         sinp = (struct sockaddr_in*)aip->ai_addr;
    33                         addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf, INET_ADDRSTRLEN);
    34                         printf("address: %s
    ", addr ? addr : "Unknown");
    35 
    36                 }
    37         printf("
    ");
    38         }
    39 
    40         return 0;
    41 }
    
    

    :: getnameinfo将一个地址转换为一个主机名和一个服务名:

    #include <sys/socket.h>

    #include <netdb.h>

    int getnameinfo(const struct sockaddr* addr,

            socklen_t alen,

            char* restrict host,

            socklen_t hostlen,

            char* restrict service,

            socklen_t servlen,

            int flags);

     

    struct sockaddr

    {

    unsigned short sa_family;   /* address family, AF_xxx */

    char sa_data[14];           /* 14 bytes of protocol address */

    };

     

     套接字与地址的关联:

    给某个服务器关联一个众所周知的地址,使得客户端可以访问该服务器,最简单的一个办法是服务器保留一个地址并且将其注册在/etc/services中,使用bind函数:

    #include <sys/socket.h>

    int bind(int sockfd, const struct sockaddr* addr, socklen_t len);

    地址中的端口号一般不小于1024,并且一般只能将一个套接字端口绑定在一个给定的地址上。

     

    使用getsockname找到绑定到套接字上的地址:

    int getsockname(int sockfd,

            struct sockaddr* restrict addr,

            socklen_t* restrict alenp);

    alenp指向缓冲区addr的长度。返回时,该整数会被设置成返回地址的大小,如果地址和提供的缓冲区长度不匹配,则地址会被自动截断而不报错。

     

    如果套接字已经和连接上,则可以使用getpeername来找到对方的地址:

    int getpeername(int sockfd,

            struct sockaddr* restrict addr,

            socklen_t* restrict alenp);

  • 相关阅读:
    JVM 参数(转)
    redis 事务
    redis 命令
    Redis配置文件参数说明(转)
    zookeeper原理(转)
    数字证书原理 转载
    证书 签名 验签 实例
    SSL双向认证java实现 (转)
    详细介绍Java垃圾回收机制 转载
    Java Socket重要参数讲解 (转载)
  • 原文地址:https://www.cnblogs.com/benxintuzi/p/4589819.html
Copyright © 2020-2023  润新知