寫在前面的話
關(guān)于socket的通信基本知識(shí)這里不多贅述概耻,有興趣自行百度封恰。本文重點(diǎn)講解socket中使用到的結(jié)構(gòu)體及其參數(shù)意義。
本文也不講解具體使用教程举塔,網(wǎng)上一搜一堆,提供筆者參考的一篇實(shí)現(xiàn)iOS socket通信求泰。
本人是一名iOS工程師央渣,參考的均為OC語(yǔ)言和C語(yǔ)言中及l(fā)inux系統(tǒng)中的相關(guān)定義,在其他語(yǔ)言或操作平臺(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)聽(tīng)志衍,斷開(kāi),發(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)春叫。來(lái)自<sys/socket.h>
。
sa_len
表示地址的長(zhǎng)度泣港。
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)體是對(duì)sockaddr的擴(kuò)展呛每。來(lái)自<netinet/in.h>
。
sin_len
表示長(zhǎng)度坡氯。
sin_family
表示地址族晨横,sin_port
表示端口號(hào)洋腮,sin_addr
表示ip地址。
sin_zero[8]
沒(méi)有實(shí)際意義,只是為了跟sockaddr
結(jié)構(gòu)在內(nèi)存中對(duì)齊手形。
3啥供、in_addr
/*
* Internet address (a structure for historical reasons)
*/
struct in_addr {
in_addr_t s_addr;
};
該結(jié)構(gòu)體用來(lái)表示一個(gè)32位的IPv4地址。來(lái)自<netinet/in.h>
库糠。
常用函數(shù)解析
1伙狐、初始化函數(shù)socket()
int socket( int af, int type, int protocol);
該函數(shù)來(lái)自<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_STCP
大莫、IPPROTO_TIPC
等蛉腌,它們分別對(duì)應(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ù)來(lái)自<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ù)來(lái)自<arpa/inet.h>
寒瓦。
inet_addr()的功能是將一個(gè)點(diǎn)分十進(jìn)制的IP轉(zhuǎn)換成一個(gè)長(zhǎng)整數(shù)型數(shù)情屹。參數(shù)為IP地址。
4杂腰、連接函數(shù)connect()
int connect(int s, const struct sockaddr * name, int namelen);
該函數(shù)來(lái)自<sys/socket.h>
垃你。
s:標(biāo)識(shí)一個(gè)未連接socket。
name:指向要連接套接字的sockaddr結(jié)構(gòu)體的指針喂很。
namelen:sockaddr結(jié)構(gòu)體的字節(jié)長(zhǎng)度惜颇。
返回值為-1表示連接失敗。
5少辣、接收函數(shù)recv()
ssize_t recv( int s, void *buf, _size_t len, int flags);
該函數(shù)來(lái)自<sys/socket.h>
凌摄。
s:標(biāo)識(shí)一個(gè)已連接socket。
buf:接收到的消息的存儲(chǔ)位置漓帅。注意大小锨亏。
len:接收消息的長(zhǎng)度。
當(dāng)返回值小于0時(shí)表示錯(cuò)誤忙干,當(dāng)等于0時(shí)表示對(duì)端的socket已正常關(guān)閉器予。
6、發(fā)送函數(shù)send()
ssize_t send(int s, const void * buf, size_t len, int √)
該函數(shù)來(lái)自<sys/socket.h>
捐迫。
s:標(biāo)識(shí)一個(gè)已連接socket乾翔。
buf:發(fā)送的消息的存儲(chǔ)位置。注意大小施戴。
len:發(fā)送消息的長(zhǎng)度反浓。[1]
當(dāng)返回值為-1時(shí)表示錯(cuò)誤。
7暇韧、綁定函數(shù)bind()
int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen)
該函數(shù)來(lái)自<sys/socket.h>
勾习。
sockfd:標(biāo)識(shí)一未捆綁套接口的描述符。
my_addr:賦予套接口的地址懈玻。
addrlen:my_addr結(jié)構(gòu)的長(zhǎng)度巧婶。
當(dāng)返回值為-1時(shí)表示錯(cuò)誤。
8涂乌、監(jiān)聽(tīng)函數(shù)listen()
int listen( int sockfd, int backlog)
該函數(shù)來(lái)自<sys/socket.h>
艺栈。
sockfd:用于標(biāo)識(shí)一個(gè)已捆綁未連接套接口的描述符。
backlog:等待連接隊(duì)列的最大長(zhǎng)度湾盒。
9湿右、接受連接accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
該函數(shù)來(lái)自<sys/socket.h>
。
sockfd:套接字描述符罚勾,該套接口在listen()后監(jiān)聽(tīng)連接毅人。
addr:(可選)指針吭狡,指向一緩沖區(qū),其中接收為通訊層所知的連接實(shí)體的地址丈莺。Addr參數(shù)的實(shí)際格式由套接口創(chuàng)建時(shí)所產(chǎn)生的地址族確定划煮。
addrlen:(可選)指針,輸入?yún)?shù)缔俄,配合addr一起使用弛秋,指向存有addr地址長(zhǎng)度的整型數(shù)。
10俐载、關(guān)閉連接close()
int close(int sockfd)
該函數(shù)來(lái)自unistd.h
sockfd:套接字描述符蟹略。
ipv4與ipv6
由于iOS在審核時(shí)必須通過(guò)ipv6環(huán)境的測(cè)試,而通常我們使用的都是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é)直接使用該第三方庫(kù)太抓。
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é)議無(wú)關(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é)議類型走敌。可取的值取決于ai_address和ai_socktype的值
ai_flags指定了如何來(lái)處理地址和名字逗噩。
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é)議無(wú)關(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)于期望返回的信息類型的暗示项炼。舉例來(lái)說(shuō):如果指定的服務(wù)既支持TCP也支持UDP,那么調(diào)用者可以把hints結(jié)構(gòu)中的ai_socktype成員設(shè)置成SOCK_DGRAM使得返回的僅僅是適用于數(shù)據(jù)報(bào)套接口的信息示绊。
返回0: 成功锭部,返回非0: 出錯(cuò)。
-
這里的長(zhǎng)度不能多也不能少面褐,筆者在這里預(yù)留多余長(zhǎng)度后在后臺(tái)解析錯(cuò)誤拌禾,特此批注。 ?