socket通信

写在前面的话

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • socket通信原理 socket又被叫做套接字,它就像连接到两端的插座孔一样,通过建立管道,将两个不同的进程之间...
    jiodg45阅读 1,126评论 0 1
  • 前言 我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服...
    Chars阅读 2,981评论 2 117
  • 近期在做的项目中,涉及到了进程间数据传输,系统的原本实现是通过管道,但是原有的实现中两个进程是在同一台机器,而且两...
    Jensen95阅读 3,137评论 0 8
  • 研究IPv6 socket编程原因: Supporting IPv6 in iOS 9 WWDC2015苹果宣布在...
    li大鹏阅读 7,306评论 7 15
  • 1三个相关数据结构. 关于socket的创建,首先需要分析socket这个结构体,这是整个的核心。 104 str...
    ice_camel阅读 2,808评论 1 8