一、概念理解
1.什么是Socket?
Socket又稱為“套接字”,是系統(tǒng)提供的用于網(wǎng)絡(luò)通信的方法,本質(zhì)并不是一個協(xié)議,沒有規(guī)定計(jì)算機(jī)怎么樣傳遞消息,只是給程序員提供一個接口,使用這個接口提供的方法,發(fā)送和接收消息。
Socket簡化了程序員操作,知道對方的IP和端口號的情況下,就可以給對方發(fā)送消息,再有服務(wù)端來處理,因此需要服務(wù)端和客戶端。
2.Socket的通信過程
每一個應(yīng)用或者服務(wù)都有一個端口,因此需要包含以下的步驟:
服務(wù)端利用Socket監(jiān)聽端口;
客戶端發(fā)起連接;
服務(wù)端返回信息,建立連接,開始通信;
客戶端,服務(wù)端斷開連接;
二、各協(xié)議的區(qū)別
OSI模型把網(wǎng)絡(luò)通信分成7層,由低向高分別是:物理層,數(shù)據(jù)鏈路層,網(wǎng)絡(luò)層,傳輸層,會話層,表示層和應(yīng)用層。
我們常用的HTTP協(xié)議是對應(yīng)應(yīng)用層,TCP協(xié)議對應(yīng)傳輸層,IP協(xié)議對應(yīng)網(wǎng)絡(luò)層,HTTP協(xié)議是基于TCP連接。
TCP/IP是傳輸層協(xié)議,主要解決數(shù)據(jù)如何在網(wǎng)絡(luò)中傳輸;而HTTP是應(yīng)用層協(xié)議,主要解決如何包裝數(shù)據(jù)。
在傳輸數(shù)據(jù)時候可以只是用TCP/IP,但是這樣沒有應(yīng)用層,無法識別傳輸?shù)臄?shù)據(jù)類容,這樣是沒有意義的,如果想使傳輸?shù)臄?shù)據(jù)有意義,則必須使用應(yīng)用層協(xié)議,HTTP就是一種,WEB使用它,封裝HTTP文本信息,然后使用TCP/IP協(xié)議傳輸?shù)骄W(wǎng)絡(luò)上
Socket實(shí)際上就是對TCP/IP協(xié)議的封裝,本身并不是協(xié)議,而是調(diào)用一個接口(API),通過Socket,我們才能使用TCP/IP協(xié)議;
HTTP和Socket連接的區(qū)別
1.TCP連接
Socket本身就是對TCP的封裝,就要先明白TCP連接:
建立一次TCP連接需要進(jìn)行"三次握手":

首先了解一下幾個標(biāo)志,SYN(synchronous),同步標(biāo)志,ACK (Acknowledgement),即確認(rèn)標(biāo)志,seq應(yīng)該是Sequence Number,序列號的意思,另外還有四次握手的fin,應(yīng)該是final,表示結(jié)束標(biāo)志。
簡單的用英語來表示就是:
客戶端:hi,how are you?
服務(wù)端:fine,thank you,and you?
客戶端:i am fine too.
- 客戶端發(fā)送一個TCP的SYN標(biāo)志位置1的包指明客戶打算連接的服務(wù)器的端口,以及初始序號X,保存在包頭的序列號(Sequence Number)字段里。
- 服務(wù)器發(fā)回確認(rèn)包(ACK)應(yīng)答。即SYN標(biāo)志位和ACK標(biāo)志位均為1同時,將確認(rèn)序號(Acknowledgement Number)設(shè)置為客戶的序列號加1以,即X+1。
- 客戶端再次發(fā)送確認(rèn)包(ACK) SYN標(biāo)志位為0,ACK標(biāo)志位為1。并且把服務(wù)器發(fā)來ACK的序號字段+1,放在確定字段中發(fā)送給對方.并且在數(shù)據(jù)段放寫序列號的+1。
只有進(jìn)行完三次握手后,才能正式傳輸數(shù)據(jù),理想狀態(tài)下只要建立起連接,在通信雙方主動關(guān)閉連接之前,TCP連接將會一直保持下去。三次握手能夠確保對面已經(jīng)收到自己的同步序列號,這樣就可以保證后續(xù)數(shù)據(jù)包的丟失可以被察覺,這也是TCP流式傳輸?shù)幕A(chǔ)。
斷開TCP連接需要發(fā)送4個包,客戶端和服務(wù)端都可以發(fā)起這個請求,在Socket編程中任何一方執(zhí)行close()操作就會產(chǎn)生"四次握手":

關(guān)閉為什么是4次,而連接是3次,是因?yàn)楫?dāng)服務(wù)端收到客戶端的SYN連接請求報(bào)文后,可以直接發(fā)送SYN+ACK報(bào)文,ACK用來回應(yīng),SYN用來同步。但是當(dāng)關(guān)閉連接的情況下,接收端收到FIN報(bào)文時候,很可能不會立即關(guān)閉,所以先發(fā)送一個ACK報(bào)文告訴發(fā)送端我收到了,只有等接收端報(bào)文全部發(fā)送完了,才能發(fā)送FIN報(bào)文。
2.HTTP連接
HTPP協(xié)議即超文本傳送協(xié)議,是建立在TCP協(xié)議之上的一種??蛻舳嗣看伟l(fā)送的請求都要服務(wù)端回送響應(yīng),請求結(jié)束后,會自動釋放連接。
3.Socekt連接
概念:Socket是通信的基石,是支持TCP/IP協(xié)議的基本操作單元,包含5種信息:連接使用的協(xié)議,本機(jī)主機(jī)IP地址,本地進(jìn)程的端口號,遠(yuǎn)程主機(jī)IP地址,遠(yuǎn)程進(jìn)程的協(xié)議端口。
應(yīng)用層通過傳輸層進(jìn)行數(shù)據(jù)傳輸時候,可能會遇到同一個TCP協(xié)議端口傳輸好幾種數(shù)據(jù),可以通過socket來區(qū)分不同應(yīng)用程序或者網(wǎng)絡(luò)連接。
建立Socket連接的步驟
- 至少需要1對,一個作用于客戶端,一個在服務(wù)端;
- 連接分為三個步驟:服務(wù)器監(jiān)聽,客戶端請求,連接確認(rèn);
- 服務(wù)器監(jiān)聽:并不對應(yīng)具體的客戶端socket,而是處于等待連接狀態(tài),實(shí)時監(jiān)聽網(wǎng)絡(luò)狀態(tài),等待客戶端連接;
- 客戶端請求:客戶端的套接字向服務(wù)端套接字發(fā)起連接請求,因此需要知道服務(wù)端的套接字的地址和端口號,而且需要描述他要連接的服務(wù)器的套接字;
- 連接確認(rèn):當(dāng)服務(wù)端套接字監(jiān)聽到或者接收到客戶端的套接字的連接請求,就響應(yīng)客戶端的套接字,建立一個新的連接,把服務(wù)端的套接字的描述發(fā)給客戶端,一旦確認(rèn),雙方就正式建立連接。而且服務(wù)端的套接字仍在監(jiān)聽狀態(tài),繼續(xù)接受其他客戶端的套接字。
Socket HTTP TCP區(qū)別
Socket連接可以指定傳輸層協(xié)議,可以是TCP或者UDP,當(dāng)時TCP協(xié)議時候就是一個TCP連接。而HTTP連接是請求->響應(yīng)的方式,在請求時候需要先建立連接,然后客戶端向服務(wù)器發(fā)出請求之后,服務(wù)器才能回復(fù)數(shù)據(jù)。而Socket一旦建立連接,服務(wù)器可以主動將數(shù)據(jù)傳輸給客戶端;而HTTP則需要客戶端先向服務(wù)器發(fā)送請求之后才能將數(shù)據(jù)返回給客戶端。但實(shí)際上Socket建立之后因?yàn)榉N種原因,會導(dǎo)致斷開連接,其中一個原因就是防火墻會斷開長時間處于非活躍狀態(tài)的連接,因此需要輪詢高速網(wǎng)絡(luò),這個連接是活躍的。
三、在iOS里面的使用
iOS提供了Socket網(wǎng)絡(luò)編程接口CFSocket,tcp和udp的socket是有區(qū)別的。
基于TCP的Socket:

基于UDP的Socket

常用的Socket類型分為兩種,流式Socket(SOCKET_STREAM)和數(shù)據(jù)報(bào)式(SOCKET_DGRAM),流式針對于面向TCP連接的應(yīng)用,而數(shù)據(jù)報(bào)式是一種無連接的Socket,對應(yīng)于無連接的UDP服務(wù)應(yīng)用。
iOS官方給出的使用時CFSocket,它是基于BSD Socket進(jìn)行抽象和封裝,CFSocket 中包含了少數(shù)開銷,它幾乎可以提供 BSD sockets 所具有的一切功能,并且把 socket 集成進(jìn)一個“運(yùn)行循環(huán)”當(dāng)中。CFSocket 并不僅僅限于基于流的 sockets (比如 TCP),它可以處理任何類型的 socket。
你可以利用 CFSocketCreate 功能從頭開始創(chuàng)建一個 CFSocket 對象,或者利用 CFSocketCreateWithNative 函數(shù)從 BSD socket 創(chuàng)建。然后,需要利用函數(shù) CFSocketCreateRunLoopSource 創(chuàng)建一個“運(yùn)行循環(huán)”源,并利用函數(shù)CFRunLoopAddSource 把它加入一個“運(yùn)行循環(huán)”。這樣不論 CFSocket 對象是否接收到信息, CFSocket 回調(diào)函數(shù)都可以運(yùn)行。
好了,廢話少說,進(jìn)入正題。
客戶端
客戶端創(chuàng)建Socket相對簡單不少,步驟如下:
0.(可選)創(chuàng)建CFSocketContext->用來關(guān)聯(lián)Socket上下文信息
/*
struct CFSocketContext
{
CFIndex version; 版本號,必須為0
void *info; 一個指向任意程序定義數(shù)據(jù)的指針,可以在CFScocket對象剛創(chuàng)建的時候與之關(guān)聯(lián),被傳遞給所有在上下文中回調(diào);
CFAllocatorRetainCallBack retain; info指針中的retain回調(diào),可以為NULL
CFAllocatorReleaseCallBack release; info指針中的release的回調(diào),可以為NULL
CFAllocatorCopyDescriptionCallBack copyDescription; info指針中的回調(diào)描述,可以為NULL
};
typedef struct CFSocketContext CFSocketContext;
*/
實(shí)現(xiàn)代碼:
//這里把self作為數(shù)據(jù)指針傳過去,這樣在回調(diào)的時候就能拿到當(dāng)前的VC
CFSocketContext sockContext = {0,(__bridge void *)(self),NULL,NULL,NULL};
1.創(chuàng)建CFSocket對象
CFSocketRef SocketRef = CFSocketCreate
(
//內(nèi)存分配類型,一般為默認(rèn)的Allocator->kCFAllocatorDefault
<#CFAllocatorRef allocator#>,
//協(xié)議族,一般為Ipv4:PF_INET,(Ipv6,PF_INET6)
<#SInt32 protocolFamily#>,
//套接字類型,TCP用流式—>SOCK_STREAM,UDP用報(bào)文式->SOCK_DGRAM
<#SInt32 socketType#>,
//套接字協(xié)議,如果之前用的是流式套接字類型:PPROTO_TCP,如果是報(bào)文式:IPPROTO_UDP
<#SInt32 protocol#>,
//回調(diào)事件觸發(fā)類型 *1
<#CFOptionFlags callBackTypes#>,
//觸發(fā)時候調(diào)用的方法 *2
<#CFSocketCallBack callout#>,
//用戶定義的數(shù)據(jù)指針,用于對CFSocket對象的額外定義或者申明,可以為NULL
<#const CFSocketContext *context#>
);
*1 具體的回調(diào)事件觸發(fā)類型
enum CFSocketCallBackType {
kCFSocketNoCallBack = 0,
kCFSocketReadCallBack = 1,
kCFSocketAcceptCallBack = 2,(常用)
kCFSocketDataCallBack = 3,
kCFSocketConnectCallBack = 4,
kCFSocketWriteCallBack = 8
};
typedef enum CFSocketCallBackType CFSocketCallBackType;
*2具體的觸發(fā)調(diào)用的方法
CFSocketCallBack 在CFsocket對象中某個活躍類型被觸發(fā)時候調(diào)用的觸發(fā)函數(shù)
官方的申明是:typedef void (*CFSocketCallBack) ( CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info );也就是新建的這個方法要包含這些參數(shù)
/*!
* @brief socket回調(diào)函數(shù)
*
* @param s socket對象;
* @param callbackType 這個socket對象的活動類型;
* @param address socket對象連接的遠(yuǎn)程地址,CFData對象對應(yīng)的是socket對象中的protocol family(struct sockaddr_in 或者 struct sockaddr_in6), 除了type類型為kCFSocketAcceptCallBack和kCFSocketDataCallBack,否則這個值通常是NULL;
* @param data 跟回調(diào)類型相關(guān)的數(shù)據(jù)指針
kCFSocketConnectCallBack:如果失敗了,它指向的就是SINT32的錯誤代碼;
kCFSocketAcceptCallBack: 它指向的就是CFSocketNativeHandle
kCFSocketDataCallBack: 它指向的就是將要進(jìn)來的Data;
其他情況都是NULL
* @param info 與Socket相關(guān)的自定義的任意數(shù)據(jù)
*/
實(shí)現(xiàn)代碼:
_socketRef = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketConnectCallBack,ServerConnectCallBack, &sockContext);
2.創(chuàng)建Socket需要連接的地址,這是一個結(jié)構(gòu)體,需要包含幾個參數(shù),同事IPV4和IPV6不一樣
// ----創(chuàng)建sockadd_in的結(jié)構(gòu)體,該結(jié)構(gòu)體作為socket的地址,IPV6需要改參數(shù)
struct sockaddr_in addr;
//創(chuàng)建完結(jié)構(gòu)體先把這個結(jié)構(gòu)體進(jìn)行清零操作
//memset:將addr中所有字節(jié)用0替換并返回addr,作用是一段內(nèi)存塊中填充某個給定的值,它是對較大的結(jié)構(gòu)體或數(shù)組進(jìn)行清零操作的一種最快方法
memset(&addr, 0, sizeof(addr));
/* 設(shè)置addr的具體內(nèi)容
struct sockaddr_in {
__uint8_t sin_len; 長度
sa_family_t sin_family; 協(xié)議族,用AF_INET->互聯(lián)網(wǎng)絡(luò),TCP,UDP等等
in_port_t sin_port; 端口號(使用網(wǎng)絡(luò)字節(jié)順序) htons:將主機(jī)的無符號短整形數(shù)轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)順序
struct in_addr sin_addr; 存儲IP地址,使用inet_addr()這個函數(shù),用來將一個點(diǎn)分十進(jìn)制的IP轉(zhuǎn)換成一個長整數(shù)型數(shù)(u_long類型),若字符串有效則將字符串轉(zhuǎn)換為32位二進(jìn)制網(wǎng)絡(luò)字節(jié)序的IPV4地址,否則為INADDR_NONE
char sin_zero[8]; 讓sockaddr與sockaddr_in兩個數(shù)據(jù)結(jié)構(gòu)保持大小相同而保留的空字節(jié),無需處理
};*/
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(19992);
addr.sin_addr.s_addr = inet_addr(192.168.1.333);
3.把地址轉(zhuǎn)換成CFDataRef
CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault,(UInt8 *)&addr, sizeof(addr));
4.連接
這里連接有2種方案:
- 方案一:
如果上面SocketRef創(chuàng)建愛你時候選擇回調(diào)類型為kCFSocketNoCallBack,然后沒有設(shè)置回調(diào)函數(shù),那就直接進(jìn)行連接
/*!
* @brief 連接socket
*
* @param s 連接的socket
* @param address 連接的socket的包含的地址參數(shù)
* @param timeout 連接超時時間,如果為負(fù),則不嘗試連接,而是把連接放在后臺進(jìn)行,如果_socket消息類型為kCFSocketConnectCallBack,將會在連接成功或失敗的時候在后臺觸發(fā)回調(diào)函數(shù)
*
* @return 返回CFSocketError類型
CFSocketConnectToAddress(CFSocketRef s, CFDataRef address, CFTimeInterval timeout)
*/
CFSocketError result = CFSocketConnectToAddress(_socketRef, dataRef, 5);
/*
typedef CF_ENUM(CFIndex, CFSocketError) {
kCFSocketSuccess = 0,成功
kCFSocketError = -1L,失敗
kCFSocketTimeout = -2L 超時
};
*/
方案二:
如果設(shè)置回調(diào)參數(shù)為kCFSocketConnectCallBack,并且設(shè)置了回調(diào)函數(shù)
// ----連接
CFSocketConnectToAddress(_socketRef, dataRef, -1);
// ----加入循環(huán)中
// ----獲取當(dāng)前線程的RunLoop
CFRunLoopRef runLoopRef = CFRunLoopGetCurrent();
// ----把Socket包裝成CFRunLoopSource,最后一個參數(shù)是指有多個runloopsource通過同一個runloop時候順序,如果只有一個source通常為0
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socketRef, 0);
// ----加入運(yùn)行循環(huán),第三個參數(shù)表示
CFRunLoopAddSource(runLoopRef, //運(yùn)行循環(huán)管
sourceRef, // 增加的運(yùn)行循環(huán)源, 它會被retain一次
kCFRunLoopCommonModes //用什么模式把source加入到run loop里面,使用kCFRunLoopCommonModes可以監(jiān)視所有通常模式添加source
);
CFRelease(sourceRef);
5.連接成功和失敗的判斷
如果方案一:
if (result == kCFSocketSuccess) {
// ----另外一個線程讀取數(shù)據(jù)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self readStreamData];
});
}
如果方案二:
void ServerConnectCallBack ( CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef address, const void *data, void *info )
{
//這里是當(dāng)前控制器名字,從info中取得之前存儲的控制器,然后在后臺執(zhí)行刷新數(shù)據(jù)方法
ViewController *vc = (__bridge ViewController *)(info);
ViewController *vc = (__bridge ViewController *)(info);
// ----判斷是不是NULL
if (data != NULL) {
printf("連接失敗\n");
[vc performSelector:@selector(releaseSocket) withObject:nil];
}else {
printf("連接成功\n");
[vc performSelectorInBackground:@selector(readStreamData) withObject:nil];
}
}
6.讀取數(shù)據(jù)
- (void)readStreamData
{
// ----定義一個字符型變量
char buffer[512];
/**
int recv( SOCKET s, char FAR *buf, int len, int flags );
不論是客戶還是服務(wù)器應(yīng)用程序都用recv函數(shù)從TCP連接的另一端接收數(shù)據(jù)。
(1)第一個參數(shù)指定接收端套接字描述符;
(2)第二個參數(shù)指明一個緩沖區(qū),該緩沖區(qū)用來存放recv函數(shù)接收到的數(shù)據(jù);
(3)第三個參數(shù)指明buf的長度;
(4)第四個參數(shù)一般置0。
*/
long readData;
//若無錯誤發(fā)生,recv()返回讀入的字節(jié)數(shù)。如果連接已中止,返回0。如果發(fā)生錯誤,返回-1,應(yīng)用程序可通過perror()獲取相應(yīng)錯誤信息
while((readData = recv(CFSocketGetNative(_socketRef), buffer, sizeof(buffer), 0))) {
NSString *content = [[NSString alloc] initWithBytes:buffer length:readData encoding:NSUTF8StringEncoding];
dispatch_async(dispatch_get_main_queue(), ^{
self.infoLabel.text = [NSString stringWithFormat:@"%@\n%@",content,self.infoLabel.text];
});
}
perror("recv");
}
7.向服務(wù)端上傳數(shù)據(jù)
NSString *stringTosend = [NSString stringWithFormat:@"%@說:%@",self.nameText.text,self.messageText.text];
const char* data = [stringTosend UTF8String];
/** 成功則返回實(shí)際傳送出去的字符數(shù), 失敗返回-1. 錯誤原因存于errno*/
int sendData = send(CFSocketGetNative(_socketRef), data, strlen(data) + 1, 0);
if (sendData < 0) {
perror("send");
}
服務(wù)端
服務(wù)端創(chuàng)建Socket相對復(fù)雜一下;
- 創(chuàng)建Socket對象
//同客戶端,注釋就不寫了
CFSocketRef _socket = CFSocketCreate(kCFAllocatorDefault,
PF_INET,
SOCK_STREAM,
IPPROTO_TCP ,
kCFSocketAcceptCallBack,
TCPServerAcceptCallBack,
NULL);
if (_socket == NULL) {
NSLog(@"創(chuàng)建Socket失?。?);
return;
}
- 設(shè)置允許重用本地地址和端口
這里首先介紹下setsockopt()這個函數(shù),它是用來設(shè)置Socket關(guān)聯(lián)的選項(xiàng),選項(xiàng)可能存在于多層協(xié)議中,它們總會出現(xiàn)在最上面的套接字層。當(dāng)操作套接字選項(xiàng)時,選項(xiàng)位于的層和選項(xiàng)的名稱必須給出。為了操作套接字層的選項(xiàng),應(yīng)該 將層的值指定為SOL_SOCKET。為了操作其它層的選項(xiàng),控制選項(xiàng)的合適協(xié)議號必須給出。
int setsockopt(int sock, //需要設(shè)置選項(xiàng)的套接字
int level, //選項(xiàng)所在的協(xié)議層
int optname, //需要訪問的選項(xiàng)名
const void *optval, //新選項(xiàng)值的緩沖
socklen_t optlen //現(xiàn)選項(xiàng)的長度
);
/* 成功返回0,失敗返回-1。
失敗的errno:
EBADF:sock不是有效的文件描述詞
EFAULT:optval指向的內(nèi)存并非有效的進(jìn)程空間
EINVAL:在調(diào)用setsockopt()時,optlen無效
ENOPROTOOPT:指定的協(xié)議層不能識別選項(xiàng)
ENOTSOCK:sock描述的不是套接字
*/
/*
參數(shù)的詳細(xì)說明
1. level指定控制套接字的層次.可以取三種值:
1)SOL_SOCKET:通用套接字選項(xiàng).(常用)
2)IPPROTO_IP:IP選項(xiàng).
3)IPPROTO_TCP:TCP選項(xiàng).
2. optname指定控制的方式(選項(xiàng)的名稱)
SO_REUSERADDR- 允許重用本地地址和端口- int
3.optval設(shè)置套接字選項(xiàng).根據(jù)選項(xiàng)名稱的數(shù)據(jù)類型進(jìn)行轉(zhuǎn)換,這里是int類型,我把它用BOOL來代替,1表示YES,0表示NO;
4. optlen 是指上面optval長度
*/
具體的代碼:
BOOL reused = YES;
//設(shè)置允許重用本地地址和端口
setsockopt(CFSocketGetNative(_socket), SOL_SOCKET, SO_REUSEADDR, (const void *)&reused, sizeof(reused));
3.創(chuàng)建Socket需要連接的地址
//定義sockaddr_in類型的變量,該變量將作為CFSocket的地址
struct sockaddr_in Socketaddr;
memset(&Socketaddr, 0, sizeof(Socketaddr));
Socketaddr.sin_len = sizeof(Socketaddr);
Socketaddr.sin_family = AF_INET;
//設(shè)置該服務(wù)器監(jiān)聽本機(jī)任意可用的IP地址
// addr4.sin_addr.s_addr = htonl(INADDR_ANY);
//設(shè)置服務(wù)器監(jiān)聽地址
Socketaddr.sin_addr.s_addr = inet_addr(TEST_IP_ADDR);
//設(shè)置服務(wù)器監(jiān)聽端口
Socketaddr.sin_port = htons(TEST_IP_PROT);
4.轉(zhuǎn)換地址類型,連接
//將IPv4的地址轉(zhuǎn)換為CFDataRef
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (UInt8 *)&Socketaddr, sizeof(Socketaddr));
//將CFSocket綁定到指定IP地址
if (CFSocketSetAddress(_socket, address) != kCFSocketSuccess) {
//如果_socket不為NULL,則釋放_socket
if (_socket) {
CFRelease(_socket);
exit(1);
}
_socket = NULL;
}
5.加入RunLoop循環(huán)監(jiān)聽
NSLog(@"----啟動循環(huán)監(jiān)聽客戶端連接---");
//獲取當(dāng)前線程的CFRunLoop
CFRunLoopRef cfRunLoop = CFRunLoopGetCurrent();
//將_socket包裝成CFRunLoopSource
CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socket, 0);
//為CFRunLoop對象添加source
CFRunLoopAddSource(cfRunLoop, source, kCFRunLoopCommonModes);
CFRelease(source);
//運(yùn)行當(dāng)前線程的CFRunLoop
CFRunLoopRun();
6.回調(diào)函數(shù)
//有客戶端連接進(jìn)來的回調(diào)函數(shù)
void TCPServerAcceptCallBack(CFSocketRef socket,
CFSocketCallBackType type,
CFDataRef address,
const void *data,
void *info)
{
//如果有客戶端Socket連接進(jìn)來
if (kCFSocketAcceptCallBack == type) {
//獲取本地Socket的Handle,這個回調(diào)事件的類型是kCFSocketAcceptCallBack,這個data就是一個CFSocketNativeHandle類型指針
CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
//定義一個255數(shù)組接收這個新的data轉(zhuǎn)成的socket的地址,SOCK_MAXADDRLEN意思是最長的可能的地址
uint8_t name[SOCK_MAXADDRLEN];
//這個地址數(shù)組的長度
socklen_t namelen = sizeof(name);
/**
int getpeername(int,已經(jīng)連接的Socket
struct sockaddr * __restrict,用來接收地址信息
socklen_t * __restrict 地址長度
)
作用是從已經(jīng)連接的Socket中獲得地址信息,存到參數(shù)2中,地址長度放到參數(shù)3中
成功是返回0,如果失敗了則返回別的數(shù)字,對應(yīng)不同錯誤碼
*/
//獲取Socket信息
if (getpeername(nativeSocketHandle,
(struct sockaddr *)name,
&namelen) != 0 ) {
perror("getpeername:");
exit(1);
}
//獲取連接信息
struct sockaddr_in *addr_in = (struct sockaddr_in *)name;
// ----inet_ntoa將網(wǎng)絡(luò)地址轉(zhuǎn)換成“.”點(diǎn)隔的字符串格式
NSLog(@"%s:%d連接進(jìn)來了",inet_ntoa(addr_in->sin_addr),addr_in->sin_port);
//創(chuàng)建一組可讀/寫的CFStream
readStreamRef = NULL;
writeStreamRef = NULL;
// ----創(chuàng)建一個和Socket對象相關(guān)聯(lián)的讀取數(shù)據(jù)流
CFStreamCreatePairWithSocket(kCFAllocatorDefault, //內(nèi)存分配器
nativeSocketHandle, //準(zhǔn)備使用輸入輸出流的socket
&readStreamRef, //輸入流
&writeStreamRef);//輸出流
// ----CFStreamCreatePairWithSocket()操作成功后,readStreamRef和writeStreamRef都指向有效的地址,因此判斷是不是還是之前設(shè)置的NULL就可以了
if (readStreamRef && writeStreamRef) {
//打開輸入流和輸出流
CFReadStreamOpen(readStreamRef);
CFWriteStreamOpen(writeStreamRef);
// ----一個結(jié)構(gòu)體包含程序定義數(shù)據(jù)和回調(diào)用來配置客戶端數(shù)據(jù)流行為
NSString *aaa = @"earth,wind,fire,be my call";
CFStreamClientContext context = {0,(__bridge void *)(aaa),NULL,NULL};
/**
指定客戶端的數(shù)據(jù)流,當(dāng)特定事件發(fā)生的時候,接受回調(diào)
Boolean CFReadStreamSetClient ( CFReadStreamRef stream, 需要指定的數(shù)據(jù)流
CFOptionFlags streamEvents, 具體的事件,如果為NULL,當(dāng)前客戶端數(shù)據(jù)流就會被移除
CFReadStreamClientCallBack clientCB, 事件發(fā)生回調(diào)函數(shù),如果為NULL,同上
CFStreamClientContext *clientContext 一個為客戶端數(shù)據(jù)流保存上下文信息的結(jié)構(gòu)體,為NULL同上
);
返回值為TRUE就是數(shù)據(jù)流支持異步通知,F(xiàn)ALSE就是不支持
*/
if (!CFReadStreamSetClient(readStreamRef,
kCFStreamEventHasBytesAvailable,
readStream,
&context)) {
exit(1);
}
// ----將數(shù)據(jù)流加入循環(huán)
CFReadStreamScheduleWithRunLoop(readStreamRef,
CFRunLoopGetCurrent(),
kCFRunLoopCommonModes);
const char *str = "welcome!\n";
//向客戶端輸出數(shù)據(jù)
CFWriteStreamWrite(writeStreamRef, (UInt8 *)str, strlen(str) + 1);
}else {
// ----如果失敗就銷毀已經(jīng)連接的Socket
close(nativeSocketHandle);
}
}
}
7.讀取客戶端發(fā)來的數(shù)據(jù)
void readStream(CFReadStreamRef readStream,
CFStreamEventType evenType,
void *clientCallBackInfo)
{
UInt8 buff[2048];
NSString *aaa = (__bridge NSString *)(clientCallBackInfo);
NSLog(@"%@", aaa);
// ----從可讀的數(shù)據(jù)流中讀取數(shù)據(jù),返回值是多少字節(jié)讀到的,如果為0就是已經(jīng)全部結(jié)束完畢,如果是-1則是數(shù)據(jù)流沒有打開或者其他錯誤發(fā)生
CFIndex hasRead = CFReadStreamRead(readStream, buff, sizeof(buff));
if (hasRead > 0) {
printf("接收到數(shù)據(jù):%s\n",buff);
const char *str = "for the lich king?。n";
//向客戶端輸出數(shù)據(jù)
CFWriteStreamWrite(writeStreamRef, (UInt8 *)str, strlen(str) + 1);
}
}