• socket通信


    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]:这里的长度不能多也不能少,笔者在这里预留多余长度后在后台解析错误,特此批注。 

  • 相关阅读:
    像计算机科学家一样思考python-第3章 函数
    jenkins打包ios 报错rror: No signing certificate "iOS Distribution" found: No "iOS Distribution...
    为什么 自动化打包可以打出包,但是包的内容与代码内容不符?
    ios 无法安装xxx,请稍后再试
    error: exportArchive: You don’t have permission to save the file “HelloWorld.ipa” in the folder “HelloWorld”.
    tool 'xcodebuild' requires Xcode, but active developer directory '/Library/Developer/CommandLineTools' is a command line tools instance
    xcode archive灰色,无法打包的解决办法
    Build Android-x86 ICS 4 Virtualbox from Google Virtualbox Target and Intel Kernel 编译体验
    Vim简明教程【CoolShell】(转)
    ubuntu10.04版本下android源码的编译
  • 原文地址:https://www.cnblogs.com/Ghosgt/p/7090281.html
Copyright © 2020-2023  润新知