iOS 用原生代碼寫一個簡單的socket鏈接

socket簡介(摘取自百度百科)

描述

網絡上的兩個程序經過一個雙向的通訊鏈接實現數據的交換,這個鏈接的一端稱爲一個socket。 創建網絡通訊鏈接至少要一對端口號(socket)。程序員

socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員作網絡開發所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通訊的能力。編程

在鏈接成功時,應用程序兩端都會產生一個Socket實例,操做這個實例,完成所需的會話。對於一個網絡鏈接來講,套接字是平等的,並無差異,不由於在服務器端或在客戶端而產生不一樣級別。無論是Socket仍是ServerSocket它們的工做都是經過SocketImpl類及其子類完成的。 bash

image.png

鏈接過程

根據鏈接啓動的方式以及本地套接字要鏈接的目標,套接字之間的鏈接過程能夠分爲三個步驟:服務器監聽,客戶端請求,鏈接確認。服務器

(1)服務器監聽:是服務器端套接字並不定位具體的客戶端套接字,而是處於等待鏈接的狀態,實時監控網絡狀態。網絡

(2)客戶端請求:是指由客戶端的套接字提出鏈接請求,要鏈接的目標是服務器端的套接字。爲此,客戶端的套接字必須首先描述它要鏈接的服務器的套接字,指出服務器端套接字的地址和端口號,而後就向服務器端套接字提出鏈接請求。dom

(3)鏈接確認:是指當服務器端套接字監聽到或者說接收到客戶端套接字的鏈接請求,它就響應客戶端套接字的請求,創建一個新的線程,把服務器端套接字的描述發給客戶端,一旦客戶端確認了此描述,鏈接就創建好了。而服務器端套接字繼續處於監聽狀態,繼續接收其餘客戶端套接字的鏈接請求。 socket

image.png

iOS寫一個原生socket

1. 導入頭文件以及宏定義
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>

//htons : 將一個無符號短整型的主機數值轉換爲網絡字節順序,不一樣cpu 是不一樣的順序 (big-endian大尾順序 , little-endian小尾順序)
#define SocketPort htons(8040) //端口
//inet_addr是一個計算機函數,功能是將一個點分十進制的IP轉換成一個長整數型數
#define SocketIP inet_addr("127.0.0.1") // ip
複製代碼
2. 建立socket

函數原型:async

int socket(int domain, int type, int protocol);函數

函數使用:工具

//屬性,用於接收socket建立成功後的返回值
@property (nonatomic, assign) int clinenId;

_clinenId = socket(AF_INET, SOCK_STREAM, 0);
    
if (_clinenId == -1) {
    NSLog(@"建立socket 失敗");
    return;
}
複製代碼

參數說明:
domain:協議域,又稱協議族(family)。經常使用的協議族有*AF_INET(ipv4)、AF_INET6(ipv6)、*AF_LOCAL(或稱AF_UNIX,Unix域Socket)、AF_ROUTE等。協議族決定了socket的地址類型,在通訊中必須採用對應的地址,如AF_INET決定了要用ipv4地址(32位的)與端口號(16位的)的組合、AF_UNIX決定了要用一個絕對路徑名做爲地址。

type:指定Socket類型。經常使用的socket類型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一種面向鏈接的Socket,針對於面向鏈接的TCP服務應用。數據報式Socket(SOCK_DGRAM)是一種無鏈接的Socket,對應於無鏈接的UDP服務應用。

protocol:指定協議。經常使用協議有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分別對應TCP傳輸協議、UDP傳輸協議、STCP傳輸協議、TIPC傳輸協議。

注意:type和protocol不能夠隨意組合,如SOCK_STREAM不能夠跟IPPROTO_UDP組合。當第三個參數爲0時,會自動選擇第二個參數類型對應的默認協議。

返回值:若是調用成功就返回新建立的套接字的描述符,若是失敗就返回INVALID_SOCKET(Linux下失敗返回-1)。套接字描述符是一個整數類型的值。

3. 建立鏈接

函數原型:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函數使用: 3.1 建立socketAddr

/**
     __uint8_t    sin_len;          假如沒有這個成員,其所佔的一個字節被併入到sin_family成員中
     sa_family_t    sin_family;     通常來講AF_INET(地址族)PF_INET(協議族)
     in_port_t    sin_port;         // 端口
     struct    in_addr sin_addr;    // ip
     char        sin_zero[8];       沒有實際意義,只是爲了&emsp;跟SOCKADDR結構在內存中對齊
     */
    struct sockaddr_in socketAddr;
    socketAddr.sin_family   = AF_INET;//當前這個是ipv4
    socketAddr.sin_port     = SocketPort; //這裏定義了一個宏
    
    struct in_addr  socketIn_addr;
    socketIn_addr.s_addr    = SocketIP; // 也是宏

    socketAddr.sin_addr     = socketIn_addr;
複製代碼

3.2 鏈接

int result = connect(_clinenId, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
    if (result != 0) {
        NSLog(@"鏈接socket 失敗");
        return;
    }
    NSLog(@"鏈接成功");
複製代碼

參數說明: sockfd:標識一個已鏈接套接口的描述字,就是咱們剛剛建立的那個_clinenId。

addr指針,指向目的套接字的地址。

addrlen:接收返回地址的緩衝區長度。

***返回值:***成功則返回0,失敗返回非0,錯誤碼GetLastError()。

4. 發送消息

函數原型:

ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);

函數使用:

const char *msg = @"消息內容".UTF8String;
    //send() 等同於 write() 多提供了一個參數來控制讀寫操做
    ssize_t sendLen = send(self.clinenId, msg, strlen(msg), 0);
    NSLog(@"發送了:%ld字節",sendLen);
複製代碼

參數說明:
sockfd:一個用於標識已鏈接套接口的描述字。
buff:包含待發送數據的緩衝區。
nbytes:緩衝區中數據的長度。
flags:調用執行方式。
返回值:若是成功,則返回發送的字節數,失敗則返回SOCKET_ERROR,一箇中文UTF8 編碼對應 3 個字節。因此上面發送了3*4字節。

5. 接收數據

函數原型:

ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);

函數使用:

uint8_t buffer[1024];
    //recv() 等同於 read() 多提供了一個參數來控制讀寫操做
    ssize_t recvLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
    NSLog(@"接收到了:%ld字節",recvLen);
    if (recvLen==0) {
        NSLog(@"這次傳輸長度爲0 若是下次還爲0 請檢查鏈接");
    }
    // 接收到的數據轉換
    NSData *recvData  = [NSData dataWithBytes:buffer length:recvLen];
    NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
    NSLog(@"%@",recvStr);
複製代碼

參數說明: sockfd:一個用於標識已鏈接套接口的描述字。
buff:包含待發送數據的緩衝區。
nbytes:緩衝區中數據的長度。
flags:調用執行方式。
返回值:若是成功,則返回讀入的字節數,失敗則返回SOCKET_ERROR。

完整代碼

#pragma mark - 建立socket創建鏈接
- (IBAction)socketConnetAction:(UIButton *)sender {
    
    // 1: 建立socket
    int socketID = socket(AF_INET, SOCK_STREAM, 0);
    self.clinenId= socketID;
    if (socketID == -1) {
        NSLog(@"建立socket 失敗");
        return;
    }
    
    // 2: 鏈接socket
    struct sockaddr_in socketAddr;
    socketAddr.sin_family = AF_INET;
    socketAddr.sin_port   = SocketPort;
    struct in_addr socketIn_addr;
    socketIn_addr.s_addr  = SocketIP;
    socketAddr.sin_addr   = socketIn_addr;
    
    int result = connect(socketID, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));

    if (result != 0) {
        NSLog(@"鏈接失敗");
        return;
    }
    NSLog(@"鏈接成功");
    
    // 調用開始接受信息的方法
    // while 若是主線程會形成堵塞
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self recvMsg];
    });
    
}


#pragma mark - 發送消息

- (IBAction)sendMsgAction:(id)sender {
    //3: 發送消息
    if (self.sendMsgContent_tf.text.length == 0) {
        return;
    }
    const char *msg = self.sendMsgContent_tf.text.UTF8String;
    ssize_t sendLen = send(self.clinenId, msg, strlen(msg), 0);
    NSLog(@"發送 %ld 字節",sendLen);
}

#pragma mark - 接受數據
- (void)recvMsg{
    // 4. 接收數據
    while (1) {
        uint8_t buffer[1024];
        ssize_t recvLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
        NSLog(@"接收到了:%ld字節",recvLen);
        if (recvLen == 0) {
            continue;
        }
        // buffer -> data -> string
        NSData *data = [NSData dataWithBytes:buffer length:recvLen];
        NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@---%@",[NSThread currentThread],str);
    }
}
複製代碼

調試

首先 運行效果

image.png

方式一,簡單快捷
  1. 打開命令行工具輸入 nc -lk 8040
  2. 點擊鏈接socket
    image.png
  3. 命令行工具隨便輸入字符 回車
    image.png
    4.模擬器隨便輸入字符 發送
    image.png
方式二 本身寫一個本地socket服務端

聽起來好想很牛逼,其實跟上面寫客戶端差很少。 多了bind(),listen(),accept()三步。

頭文件、宏、屬性

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

#define SocketPort htons(8040)
#define SocketIP inet_addr("127.0.0.1")

@property (nonatomic, assign) int serverId;
@property (nonatomic, assign) int client_socket;
複製代碼
  1. socket()
self.serverId = socket(AF_INET, SOCK_STREAM, 0);
    if (self.serverId == -1) {
        NSLog(@"建立socket 失敗");
        return;
    }
    NSLog(@"建立socket 成功");
複製代碼
  1. bind()
struct sockaddr_in socketAddr;
    socketAddr.sin_family   = AF_INET;
    socketAddr.sin_port     = SocketPort;
    struct in_addr  socketIn_addr;
    socketIn_addr.s_addr    = SocketIP;
    socketAddr.sin_addr     = socketIn_addr;
    bzero(&(socketAddr.sin_zero), 8);
    
    // 2: 綁定socket
    int bind_result = bind(self.serverId, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
    if (bind_result == -1) {
        NSLog(@"綁定socket 失敗");
        return;
    }

    NSLog(@"綁定socket成功");
複製代碼
  1. listen()
// 3: 監聽socket
    int listen_result = listen(self.serverId, kMaxConnectCount);
    if (listen_result == -1) {
        NSLog(@"監聽失敗");
        return;
    }
    
    NSLog(@"監聽成功");
複製代碼
  1. accept()
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        struct sockaddr_in client_address;
        socklen_t address_len;
        // accept函數
        int client_socket = accept(self.serverId, (struct sockaddr *)&client_address, &address_len);
        self.client_socket = client_socket;
        
        if (client_socket == -1) {
            NSLog(@"接受 %u 客戶端錯誤",address_len);           
        }else{
            NSString *acceptInfo = [NSString stringWithFormat:@"客戶端 in,socket:%d",client_socket];
            NSLog(@"%@",acceptInfo);
           //開始接受消息
            [self receiveMsgWithClietnSocket:client_socket];
        }
    });
複製代碼
  1. recv()
while (1) {
        // 5: 接受客戶端傳來的數據
        char buf[1024] = {0};
        long iReturn = recv(clientSocket, buf, 1024, 0);
        if (iReturn>0) {
            NSLog(@"客戶端來消息了");
            // 接收到的數據轉換
            NSData *recvData  = [NSData dataWithBytes:buf length:iReturn];
            NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
            NSLog(@"%@",recvStr);
            
        }else if (iReturn == -1){
            NSLog(@"讀取消息失敗");
            break;
        }else if (iReturn == 0){
            NSLog(@"客戶端走了");
            
            close(clientSocket);
            
            break;
        }
    }
複製代碼
  1. send()
const char *msg = @"給客戶端發消息".UTF8String;
    ssize_t sendLen = send(self.client_socket, msg, strlen(msg), 0);
    NSLog(@"發送了:%ld字節",sendLen);
複製代碼
  1. close()
int close_result = close(self.client_socket);
    
    if (close_result == -1) {
        NSLog(@"socket 關閉失敗");
        return;
    }else{
        NSLog(@"socket 關閉成功");
    }
複製代碼

那麼這篇文章就到這裏,寫這篇文章的主要目的是爲了讓後面學習GCDAsyncSocket時,加深印象、深刻理解其實現原理。

相關文章
相關標籤/搜索