socket通信

作者: 诸葛云纹 | 来源:发表于2017-06-28 15:03 被阅读107次
    写在前面的话

    关于socket的通信基本知识这里不多赘述,有兴趣自行百度。本文重点讲解socket中使用到的结构体及其参数意义。
      本文也不讲解具体使用教程,网上一搜一堆,提供笔者参考的一篇实现iOS socket通信
      本人是一名iOS工程师,参考的均为OC语言和C语言中及linux系统中的相关定义,在其他语言或操作平台中或有出入。

    socket中用到的头文件

    #include <netinet/in.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    

    ⚠️iOS审核要求必须支持ipv6,而ipv6的头文件是<netinet6/in6.h>,在<netinet/in.h>的最后有相关定义如下

    /* INET6 stuff */
    #define __KAME_NETINET_IN_H_INCLUDED_
    #include <netinet6/in6.h>
    #undef __KAME_NETINET_IN_H_INCLUDED_
    
    

    其中核心<sys/socket.h>提供了创建,绑定,连接,监听,断开,发消息等常用函数及基本数据结构,之后将一一介绍。
      <netinet/in.h>提供socket地址数据结构sockaddr_in的相关定义。
      <arpa/inet.h>提供IP地址转换函数

    常用数据结构解析

    1、sockaddr
    /*
     * [XSI] Structure used by kernel to store most addresses.
     */
    struct sockaddr {
        __uint8_t   sa_len;     /* total length */
        sa_family_t sa_family;  /* [XSI] address family */
        char        sa_data[14];    /* [XSI] addr value (actually larger) */
    };
    

    该结构体用于存储地址结构。来自<sys/socket.h>
      sa_len表示地址的长度。
      sa_family表示地址族常用的族有ipv4的AF_INET和ipv6的AF_INET6
      sa_data[14]表示地址数据。

    2、sockaddr_in
    /*
     * Socket address, internet style.
     */
    struct sockaddr_in {
        __uint8_t   sin_len;
        sa_family_t sin_family;
        in_port_t   sin_port;
        struct  in_addr sin_addr;
        char        sin_zero[8];
    };
    

    该结构体是对sockaddr的扩展。来自<netinet/in.h>
      sin_len表示长度。
      sin_family表示地址族,sin_port表示端口号,sin_addr表示ip地址。
      sin_zero[8]没有实际意义,只是为了跟sockaddr结构在内存中对齐。

    3、in_addr
    /*
     * Internet address (a structure for historical reasons)
     */
    struct in_addr {
        in_addr_t s_addr;
    };
    

    该结构体用来表示一个32位的IPv4地址。来自<netinet/in.h>

    常用函数解析

    1、初始化函数socket()
    int socket( int af, int type, int protocol);
    

    该函数来自<sys/socket.h>
      af:一个地址描述。支持AF_INETAF_INET6等格式。
      type:指定socket类型。新套接口的类型描述类型,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。常用的socket类型有,SOCK_STREAMSOCK_DGRAMSOCK_RAWSOCK_PACKETSOCK_SEQPACKET等等。
      protocol:顾名思义,就是指定协议。套接口所用的协议。如调用者不想指定,可用0。常用的协议有,IPPROTO_TCPIPPROTO_UDPIPPROTO_STCPIPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
    当返回结果为-1时表示创建失败。

    2、htons()
    #define htons(x)    __DARWIN_OSSwapInt16(x)
    
    #define __DARWIN_OSSwapInt16(x) _OSSwapInt16(x)
    
    /* Generic byte swapping functions. */
    OS_INLINE
    uint16_t
    _OSSwapInt16(
        uint16_t        data
    )
    {
      /* Reduces to 'rev16' with clang */
      return (uint16_t)(data << 8 | data >> 8);
    }
    

    该函数来自<sys/_endian.h>
      该函数将一个16位数从主机字节顺序转换成网络字节顺序。将高低位互换位置。参数为端口号。

    3、inet_addr()
    in_addr_t    inet_addr(const char *);
    

    该函数来自<arpa/inet.h>
      inet_addr()的功能是将一个点分十进制的IP转换成一个长整数型数。参数为IP地址。

    4、连接函数connect()
    int connect(int s, const struct sockaddr * name, int namelen);
    

    该函数来自<sys/socket.h>
      s:标识一个未连接socket。
      name:指向要连接套接字的sockaddr结构体的指针。
      namelen:sockaddr结构体的字节长度。
      返回值为-1表示连接失败。

    5、接收函数recv()
    ssize_t recv( int s, void *buf, _size_t len, int flags);
    

    该函数来自<sys/socket.h>
      s:标识一个已连接socket。
      buf:接收到的消息的存储位置。注意大小。
      len:接收消息的长度。
      当返回值小于0时表示错误,当等于0时表示对端的socket已正常关闭。

    6、发送函数send()
    ssize_t send(int s, const void * buf, size_t len, int √)
    

    该函数来自<sys/socket.h>
      s:标识一个已连接socket。
      buf:发送的消息的存储位置。注意大小。
      len:发送消息的长度。[1]

    当返回值为-1时表示错误。

    7、绑定函数bind()
    int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen)
    

    该函数来自<sys/socket.h>
      sockfd:标识一未捆绑套接口的描述符。
      my_addr:赋予套接口的地址。
      addrlen:my_addr结构的长度。
      当返回值为-1时表示错误。

    8、监听函数listen()
    int listen( int sockfd, int backlog)
    

    该函数来自<sys/socket.h>
      sockfd:用于标识一个已捆绑未连接套接口的描述符。
      backlog:等待连接队列的最大长度。

    9、接受连接accept()
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
    

    该函数来自<sys/socket.h>
      sockfd:套接字描述符,该套接口在listen()后监听连接。
      addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。
      addrlen:(可选)指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。

    10、关闭连接close()
    int  close(int sockfd)
    

    该函数来自unistd.h
      sockfd:套接字描述符。

    ipv4与ipv6

    由于iOS在审核时必须通过ipv6环境的测试,而通常我们使用的都是ipv4的地址,同时sockaddr_in只能表示ipv4环境的结构。因此引入了sockaddr_in6

    struct sockaddr_in6 {
        __uint8_t   sin6_len;   /* length of this struct(sa_family_t) */
        sa_family_t sin6_family;    /* AF_INET6 (sa_family_t) */
        in_port_t   sin6_port;  /* Transport layer port # (in_port_t) */
        __uint32_t  sin6_flowinfo;  /* IP6 flow information */
        struct in6_addr sin6_addr;  /* IP6 address */
        __uint32_t  sin6_scope_id;  /* scope zone index */
    };
    

    基本结构和sockaddr_in类似,只是参数名上多了个6
      这里存在一个地址转换,研究了GCDAsyncSocket的转换方法。这里也建议想省事的同学直接使用该第三方库。

    NSMutableArray *addresses = nil;
    NSError *error = nil;
    
    NSString *portStr = [NSString stringWithFormat:@"%hu", port];
    
    struct addrinfo hints, *res, *res0;
            
    memset(&hints, 0, sizeof(hints));
    hints.ai_family   = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
            
    int gai_error = getaddrinfo([host UTF8String], [portStr UTF8String], &hints, &res0);
            
    if (gai_error) {
        error = [self gaiError:gai_error];
    } else {
        NSUInteger capacity = 0;
        for (res = res0; res; res = res->ai_next) {
            if (res->ai_family == AF_INET || res->ai_family == AF_INET6) {
                capacity++;
            }
        }
                
        addresses = [NSMutableArray arrayWithCapacity:capacity];
                
        for (res = res0; res; res = res->ai_next) {
            if (res->ai_family == AF_INET) {
                // Found IPv4 address.
                // Wrap the native address structure, and add to results.
                        
                NSData *address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
                [addresses addObject:address4];
            } else if (res->ai_family == AF_INET6) {
                // Fixes connection issues with IPv6
                // https://github.com/robbiehanson/CocoaAsyncSocket/issues/429#issuecomment-222477158
                        
                // Found IPv6 address.
                // Wrap the native address structure, and add to results.
                        
                struct sockaddr_in6 *sockaddr = (struct sockaddr_in6 *)res->ai_addr;
                in_port_t *portPtr = &sockaddr->sin6_port;
                if ((portPtr != NULL) && (*portPtr == 0)) {
                        *portPtr = htons(port);
                }
    
                NSData *address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen];
                [addresses addObject:address6];
            }
        }
        freeaddrinfo(res0);
                
        if ([addresses count] == 0) {
            error = [self gaiError:EAI_FAIL];
        }
    }
    

    这里面主要是addrinfo结构和getaddrinfo()函数。

    addrinfo
    struct addrinfo {
        int ai_flags;   /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
        int ai_family;  /* PF_xxx */
        int ai_socktype;    /* SOCK_xxx */
        int ai_protocol;    /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
        socklen_t ai_addrlen;   /* length of ai_addr */
        char    *ai_canonname;  /* canonical name for hostname */
        struct  sockaddr *ai_addr;  /* binary address */
        struct  addrinfo *ai_next;  /* next structure in linked list */
    };
    

    ai_family:指定了地址族,可取值如下:

    名称 意义
    AF_INET 2 ipv4
    AF_INET6 23 ipv6
    AF_UNSPEC 0 协议无关

    ai_socktype:指定我套接字的类型

    名称 意义
    SOCK_STREAM 1
    SOCK_DGRAM 2 数据报

    在AF_INET通信域中套接字类型SOCK_STREAM的默认协议是TCP(传输控制协议)
      在AF_INET通信域中套接字类型SOCK_DGRAM的默认协议是UDP(用户数据报协议)

    ai_protocol:指定协议类型。可取的值取决于ai_address和ai_socktype的值
      ai_flags指定了如何来处理地址和名字。

    getaddrinfo()
    int  getaddrinfo(const char * __restrict, const char * __restrict, const struct addrinfo * __restrict, struct addrinfo ** __restrict);
    

    getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr 结构的链而 不是一个地址清单。它具有协议无关性。
      hostname:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
      service:一个服务名或者10进制端口号数串。
      hints:可以是一个空指针,也可以是一个指向某个addrinfo结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。
      返回0: 成功,返回非0: 出错。


    1. 这里的长度不能多也不能少,笔者在这里预留多余长度后在后台解析错误,特此批注。

    相关文章

      网友评论

        本文标题:socket通信

        本文链接:https://www.haomeiwen.com/subject/kruucxtx.html