socket通信

寫在前面的話

關(guān)于socket的通信基本知識(shí)這里不多贅述,有興趣自行百度。本文重點(diǎn)講解socket中使用到的結(jié)構(gòu)體及其參數(shù)意義。
  本文也不講解具體使用教程,網(wǎng)上一搜一堆,提供筆者參考的一篇實(shí)現(xiàn)iOS socket通信
  本人是一名iOS工程師,參考的均為OC語言和C語言中及l(fā)inux系統(tǒng)中的相關(guān)定義,在其他語言或操作平臺(tái)中或有出入。

socket中用到的頭文件

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

??iOS審核要求必須支持ipv6,而ipv6的頭文件是<netinet6/in6.h>,在<netinet/in.h>的最后有相關(guān)定義如下

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

其中核心<sys/socket.h>提供了創(chuàng)建,綁定,連接,監(jiān)聽,斷開,發(fā)消息等常用函數(shù)及基本數(shù)據(jù)結(jié)構(gòu),之后將一一介紹。
  <netinet/in.h>提供socket地址數(shù)據(jù)結(jié)構(gòu)sockaddr_in的相關(guān)定義。
  <arpa/inet.h>提供IP地址轉(zhuǎn)換函數(shù)

常用數(shù)據(jù)結(jié)構(gòu)解析

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) */
};

該結(jié)構(gòu)體用于存儲(chǔ)地址結(jié)構(gòu)。來自<sys/socket.h>。
  sa_len表示地址的長度。
  sa_family表示地址族常用的族有ipv4的AF_INET和ipv6的AF_INET6
  sa_data[14]表示地址數(shù)據(jù)。

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];
};

該結(jié)構(gòu)體是對sockaddr的擴(kuò)展。來自<netinet/in.h>。
  sin_len表示長度。
  sin_family表示地址族,sin_port表示端口號(hào),sin_addr表示ip地址。
  sin_zero[8]沒有實(shí)際意義,只是為了跟sockaddr結(jié)構(gòu)在內(nèi)存中對齊。

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

該結(jié)構(gòu)體用來表示一個(gè)32位的IPv4地址。來自<netinet/in.h>。

常用函數(shù)解析

1、初始化函數(shù)socket()
int socket( int af, int type, int protocol);

該函數(shù)來自<sys/socket.h>。
  af:一個(gè)地址描述。支持AF_INET、AF_INET6等格式。
  type:指定socket類型。新套接口的類型描述類型,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。常用的socket類型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
  protocol:顧名思義,就是指定協(xié)議。套接口所用的協(xié)議。如調(diào)用者不想指定,可用0。常用的協(xié)議有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCPIPPROTO_TIPC等,它們分別對應(yīng)TCP傳輸協(xié)議、UDP傳輸協(xié)議、STCP傳輸協(xié)議、TIPC傳輸協(xié)議。
當(dāng)返回結(jié)果為-1時(shí)表示創(chuàng)建失敗。

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

該函數(shù)來自<sys/_endian.h>。
  該函數(shù)將一個(gè)16位數(shù)從主機(jī)字節(jié)順序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)順序。將高低位互換位置。參數(shù)為端口號(hào)。

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

該函數(shù)來自<arpa/inet.h>。
  inet_addr()的功能是將一個(gè)點(diǎn)分十進(jìn)制的IP轉(zhuǎn)換成一個(gè)長整數(shù)型數(shù)。參數(shù)為IP地址。

4、連接函數(shù)connect()
int connect(int s, const struct sockaddr * name, int namelen);

該函數(shù)來自<sys/socket.h>。
  s:標(biāo)識(shí)一個(gè)未連接socket。
  name:指向要連接套接字的sockaddr結(jié)構(gòu)體的指針。
  namelen:sockaddr結(jié)構(gòu)體的字節(jié)長度。
  返回值為-1表示連接失敗。

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

該函數(shù)來自<sys/socket.h>。
  s:標(biāo)識(shí)一個(gè)已連接socket。
  buf:接收到的消息的存儲(chǔ)位置。注意大小。
  len:接收消息的長度。
  當(dāng)返回值小于0時(shí)表示錯(cuò)誤,當(dāng)等于0時(shí)表示對端的socket已正常關(guān)閉。

6、發(fā)送函數(shù)send()
ssize_t send(int s, const void * buf, size_t len, int √)

該函數(shù)來自<sys/socket.h>。
  s:標(biāo)識(shí)一個(gè)已連接socket。
  buf:發(fā)送的消息的存儲(chǔ)位置。注意大小。
  len:發(fā)送消息的長度。[1]

當(dāng)返回值為-1時(shí)表示錯(cuò)誤。

7、綁定函數(shù)bind()
int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen)

該函數(shù)來自<sys/socket.h>。
  sockfd:標(biāo)識(shí)一未捆綁套接口的描述符。
  my_addr:賦予套接口的地址。
  addrlen:my_addr結(jié)構(gòu)的長度。
  當(dāng)返回值為-1時(shí)表示錯(cuò)誤。

8、監(jiān)聽函數(shù)listen()
int listen( int sockfd, int backlog)

該函數(shù)來自<sys/socket.h>
  sockfd:用于標(biāo)識(shí)一個(gè)已捆綁未連接套接口的描述符。
  backlog:等待連接隊(duì)列的最大長度。

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

該函數(shù)來自<sys/socket.h>。
  sockfd:套接字描述符,該套接口在listen()后監(jiān)聽連接。
  addr:(可選)指針,指向一緩沖區(qū),其中接收為通訊層所知的連接實(shí)體的地址。Addr參數(shù)的實(shí)際格式由套接口創(chuàng)建時(shí)所產(chǎn)生的地址族確定。
  addrlen:(可選)指針,輸入?yún)?shù),配合addr一起使用,指向存有addr地址長度的整型數(shù)。

10、關(guān)閉連接close()
int  close(int sockfd)

該函數(shù)來自unistd.h
  sockfd:套接字描述符。

ipv4與ipv6

由于iOS在審核時(shí)必須通過ipv6環(huán)境的測試,而通常我們使用的都是ipv4的地址,同時(shí)sockaddr_in只能表示ipv4環(huán)境的結(jié)構(gòu)。因此引入了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 */
};

基本結(jié)構(gòu)和sockaddr_in類似,只是參數(shù)名上多了個(gè)6。
  這里存在一個(gè)地址轉(zhuǎn)換,研究了GCDAsyncSocket的轉(zhuǎn)換方法。這里也建議想省事的同學(xué)直接使用該第三方庫。

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結(jié)構(gòu)和getaddrinfo()函數(shù)。

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 協(xié)議無關(guān)

ai_socktype:指定我套接字的類型

名稱 意義
SOCK_STREAM 1
SOCK_DGRAM 2 數(shù)據(jù)報(bào)

在AF_INET通信域中套接字類型SOCK_STREAM的默認(rèn)協(xié)議是TCP(傳輸控制協(xié)議)
  在AF_INET通信域中套接字類型SOCK_DGRAM的默認(rèn)協(xié)議是UDP(用戶數(shù)據(jù)報(bào)協(xié)議)

ai_protocol:指定協(xié)議類型??扇〉闹等Q于ai_address和ai_socktype的值
  ai_flags指定了如何來處理地址和名字。

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

getaddrinfo函數(shù)能夠處理名字到地址以及服務(wù)到端口這兩種轉(zhuǎn)換,返回的是一個(gè)sockaddr 結(jié)構(gòu)的鏈而 不是一個(gè)地址清單。它具有協(xié)議無關(guān)性。
  hostname:一個(gè)主機(jī)名或者地址串(IPv4的點(diǎn)分十進(jìn)制串或者IPv6的16進(jìn)制串)
  service:一個(gè)服務(wù)名或者10進(jìn)制端口號(hào)數(shù)串。
  hints:可以是一個(gè)空指針,也可以是一個(gè)指向某個(gè)addrinfo結(jié)構(gòu)的指針,調(diào)用者在這個(gè)結(jié)構(gòu)中填入關(guān)于期望返回的信息類型的暗示。舉例來說:如果指定的服務(wù)既支持TCP也支持UDP,那么調(diào)用者可以把hints結(jié)構(gòu)中的ai_socktype成員設(shè)置成SOCK_DGRAM使得返回的僅僅是適用于數(shù)據(jù)報(bào)套接口的信息。
  返回0: 成功,返回非0: 出錯(cuò)。


  1. 這里的長度不能多也不能少,筆者在這里預(yù)留多余長度后在后臺(tái)解析錯(cuò)誤,特此批注。 ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • socket通信原理 socket又被叫做套接字,它就像連接到兩端的插座孔一樣,通過建立管道,將兩個(gè)不同的進(jìn)程之間...
    jiodg45閱讀 1,243評(píng)論 0 1
  • 前言 我們深諳信息交流的價(jià)值,那網(wǎng)絡(luò)中進(jìn)程之間如何通信,如我們每天打開瀏覽器瀏覽網(wǎng)頁時(shí),瀏覽器的進(jìn)程怎么與web服...
    Chars閱讀 3,156評(píng)論 2 124
  • 近期在做的項(xiàng)目中,涉及到了進(jìn)程間數(shù)據(jù)傳輸,系統(tǒng)的原本實(shí)現(xiàn)是通過管道,但是原有的實(shí)現(xiàn)中兩個(gè)進(jìn)程是在同一臺(tái)機(jī)器,而且兩...
    Jensen95閱讀 3,199評(píng)論 0 8
  • 研究IPv6 socket編程原因: Supporting IPv6 in iOS 9 WWDC2015蘋果宣布在...
    li大鵬閱讀 7,643評(píng)論 7 15
  • 1三個(gè)相關(guān)數(shù)據(jù)結(jié)構(gòu). 關(guān)于socket的創(chuàng)建,首先需要分析socket這個(gè)結(jié)構(gòu)體,這是整個(gè)的核心。 104 str...
    ice_camel閱讀 2,967評(píng)論 1 8

友情鏈接更多精彩內(nèi)容