寫在前面的話
關(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_STCP、IPPROTO_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ò)。
-
這里的長度不能多也不能少,筆者在這里預(yù)留多余長度后在后臺(tái)解析錯(cuò)誤,特此批注。 ?