CFSocket學(xué)習(xí)

一、概念理解

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)行"三次握手":

tcp-三次握手.png

首先了解一下幾個標(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.

  1. 客戶端發(fā)送一個TCP的SYN標(biāo)志位置1的包指明客戶打算連接的服務(wù)器的端口,以及初始序號X,保存在包頭的序列號(Sequence Number)字段里。
  2. 服務(wù)器發(fā)回確認(rèn)包(ACK)應(yīng)答。即SYN標(biāo)志位和ACK標(biāo)志位均為1同時,將確認(rèn)序號(Acknowledgement Number)設(shè)置為客戶的序列號加1以,即X+1。
  3. 客戶端再次發(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)生"四次握手":

tcp-四次握手.png

關(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. 至少需要1對,一個作用于客戶端,一個在服務(wù)端;
  2. 連接分為三個步驟:服務(wù)器監(jiān)聽,客戶端請求,連接確認(rèn);
  3. 服務(wù)器監(jiān)聽:并不對應(yīng)具體的客戶端socket,而是處于等待連接狀態(tài),實(shí)時監(jiān)聽網(wǎng)絡(luò)狀態(tài),等待客戶端連接;
  4. 客戶端請求:客戶端的套接字向服務(wù)端套接字發(fā)起連接請求,因此需要知道服務(wù)端的套接字的地址和端口號,而且需要描述他要連接的服務(wù)器的套接字;
  5. 連接確認(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:

tcp_Socket.png

基于UDP的Socket

udp_Socket.png

常用的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ù)雜一下;

  1. 創(chuàng)建Socket對象
  //同客戶端,注釋就不寫了
  CFSocketRef _socket = CFSocketCreate(kCFAllocatorDefault,
                                           PF_INET,
                                           SOCK_STREAM,
                                           IPPROTO_TCP ,
                                           kCFSocketAcceptCallBack,
                                           TCPServerAcceptCallBack,
                                           NULL);

  if (_socket == NULL) {
    NSLog(@"創(chuàng)建Socket失?。?);
    return;
  }
  1. 設(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);
    }
}

源碼地址:https://github.com/medivh-xiong/CFSocket_Demo.git

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

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

  • 參考:http://www.2cto.com/net/201611/569006.html TCP HTTP UD...
    F麥子閱讀 3,065評論 0 14
  • 一: 網(wǎng)絡(luò)各個協(xié)議:TCP/IP、SOCKET、HTTP 網(wǎng)絡(luò)七層由下往上分別為物理層、數(shù)據(jù)鏈路層、網(wǎng)絡(luò)層、傳輸層...
    iYeso閱讀 1,503評論 0 13
  • 1.OkHttp源碼解析(一):OKHttp初階2 OkHttp源碼解析(二):OkHttp連接的"前戲"——HT...
    隔壁老李頭閱讀 21,545評論 24 176
  • 這篇文章的題目來自于一片TED演講,演講者做了一個一年的計(jì)劃,閱讀全世界,將聯(lián)合國認(rèn)可的190+個國家的書籍,每個...
    張小蔥閱讀 2,454評論 3 8
  • 眼前的這塊空地真是踢足球的好地方。放學(xué)后,孩子們每次都會聚集在這兒舉行小小足球賽。只見他們用書包和外套擺成了...
    笑丁媽閱讀 225評論 0 0

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