iOS 用原生代碼寫一個(gè)簡(jiǎn)單的socket連接

socket簡(jiǎn)介(摘取自百度百科)

描述

網(wǎng)絡(luò)上的兩個(gè)程序通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換症歇,這個(gè)連接的一端稱為一個(gè)socket。
建立網(wǎng)絡(luò)通信連接至少要一對(duì)端口號(hào)(socket)哗总。

socket本質(zhì)是編程接口(API)砚亭,對(duì)TCP/IP的封裝,TCP/IP也要提供可供程序員做網(wǎng)絡(luò)開(kāi)發(fā)所用的接口炫刷,這就是Socket編程接口橘原;HTTP是轎車籍铁,提供了封裝或者顯示數(shù)據(jù)的具體形式涡上;Socket是發(fā)動(dòng)機(jī),提供了網(wǎng)絡(luò)通信的能力拒名。

在連接成功時(shí)吩愧,應(yīng)用程序兩端都會(huì)產(chǎn)生一個(gè)Socket實(shí)例,操作這個(gè)實(shí)例增显,完成所需的會(huì)話雁佳。對(duì)于一個(gè)網(wǎng)絡(luò)連接來(lái)說(shuō),套接字是平等的同云,并沒(méi)有差別糖权,不因?yàn)樵诜?wù)器端或在客戶端而產(chǎn)生不同級(jí)別。不管是Socket還是ServerSocket它們的工作都是通過(guò)SocketImpl類及其子類完成的炸站。


image.png
連接過(guò)程

根據(jù)連接啟動(dòng)的方式以及本地套接字要連接的目標(biāo)星澳,套接字之間的連接過(guò)程可以分為三個(gè)步驟:服務(wù)器監(jiān)聽(tīng),客戶端請(qǐng)求武契,連接確認(rèn)。

(1)服務(wù)器監(jiān)聽(tīng):是服務(wù)器端套接字并不定位具體的客戶端套接字荡含,而是處于等待連接的狀態(tài)咒唆,實(shí)時(shí)監(jiān)控網(wǎng)絡(luò)狀態(tài)此熬。

(2)客戶端請(qǐng)求:是指由客戶端的套接字提出連接請(qǐng)求抄囚,要連接的目標(biāo)是服務(wù)器端的套接字。為此闭翩,客戶端的套接字必須首先描述它要連接的服務(wù)器的套接字误债,指出服務(wù)器端套接字的地址和端口號(hào)浸船,然后就向服務(wù)器端套接字提出連接請(qǐng)求。

(3)連接確認(rèn):是指當(dāng)服務(wù)器端套接字監(jiān)聽(tīng)到或者說(shuō)接收到客戶端套接字的連接請(qǐng)求寝蹈,它就響應(yīng)客戶端套接字的請(qǐng)求李命,建立一個(gè)新的線程,把服務(wù)器端套接字的描述發(fā)給客戶端箫老,一旦客戶端確認(rèn)了此描述封字,連接就建立好了。而服務(wù)器端套接字繼續(xù)處于監(jiān)聽(tīng)狀態(tài)耍鬓,繼續(xù)接收其他客戶端套接字的連接請(qǐng)求阔籽。

image.png

iOS寫一個(gè)原生socket

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

//htons : 將一個(gè)無(wú)符號(hào)短整型的主機(jī)數(shù)值轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序,不同cpu 是不同的順序 (big-endian大尾順序 , little-endian小尾順序)
#define SocketPort htons(8040) //端口
//inet_addr是一個(gè)計(jì)算機(jī)函數(shù)牲蜀,功能是將一個(gè)點(diǎn)分十進(jìn)制的IP轉(zhuǎn)換成一個(gè)長(zhǎng)整數(shù)型數(shù)
#define SocketIP   inet_addr("127.0.0.1") // ip
2. 創(chuàng)建socket

函數(shù)原型:

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

函數(shù)使用:

//屬性笆制,用于接收socket創(chuàng)建成功后的返回值
@property (nonatomic, assign) int clinenId;

_clinenId = socket(AF_INET, SOCK_STREAM, 0);
    
if (_clinenId == -1) {
    NSLog(@"創(chuàng)建socket 失敗");
    return;
}

參數(shù)說(shuō)明:
domain:協(xié)議域,又稱協(xié)議族(family)涣达。常用的協(xié)議族有AF_INET(ipv4)在辆、AF_INET6(ipv6)证薇、AF_LOCAL(或稱AF_UNIX,Unix域Socket)开缎、AF_ROUTE等棕叫。協(xié)議族決定了socket的地址類型,在通信中必須采用對(duì)應(yīng)的地址奕删,如AF_INET決定了要用ipv4地址(32位的)與端口號(hào)(16位的)的組合俺泣、AF_UNIX決定了要用一個(gè)絕對(duì)路徑名作為地址。

type:指定Socket類型完残。常用的socket類型有SOCK_STREAM伏钠、SOCK_DGRAM、SOCK_RAW谨设、SOCK_PACKET熟掂、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一種面向連接的Socket扎拣,針對(duì)于面向連接的TCP服務(wù)應(yīng)用赴肚。數(shù)據(jù)報(bào)式Socket(SOCK_DGRAM)是一種無(wú)連接的Socket,對(duì)應(yīng)于無(wú)連接的UDP服務(wù)應(yīng)用二蓝。

protocol:指定協(xié)議誉券。常用協(xié)議有IPPROTO_TCP、IPPROTO_UDP刊愚、IPPROTO_STCP踊跟、IPPROTO_TIPC等,分別對(duì)應(yīng)TCP傳輸協(xié)議鸥诽、UDP傳輸協(xié)議商玫、STCP傳輸協(xié)議、TIPC傳輸協(xié)議牡借。

注意:type和protocol不可以隨意組合拳昌,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當(dāng)?shù)谌齻€(gè)參數(shù)為0時(shí)钠龙,會(huì)自動(dòng)選擇第二個(gè)參數(shù)類型對(duì)應(yīng)的默認(rèn)協(xié)議地回。

返回值:如果調(diào)用成功就返回新創(chuàng)建的套接字的描述符,如果失敗就返回INVALID_SOCKET(Linux下失敗返回-1)俊鱼。套接字描述符是一個(gè)整數(shù)類型的值刻像。

3. 創(chuàng)建連接

函數(shù)原型:

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

函數(shù)使用:
3.1 創(chuàng)建socketAddr

    /**
     __uint8_t    sin_len;          假如沒(méi)有這個(gè)成員,其所占的一個(gè)字節(jié)被并入到sin_family成員中
     sa_family_t    sin_family;     一般來(lái)說(shuō)AF_INET(地址族)PF_INET(協(xié)議族)
     in_port_t    sin_port;         // 端口
     struct    in_addr sin_addr;    // ip
     char        sin_zero[8];       沒(méi)有實(shí)際意義,只是為了 跟SOCKADDR結(jié)構(gòu)在內(nèi)存中對(duì)齊
     */
    struct sockaddr_in socketAddr;
    socketAddr.sin_family   = AF_INET;//當(dāng)前這個(gè)是ipv4
    socketAddr.sin_port     = SocketPort; //這里定義了一個(gè)宏
    
    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(@"連接成功");

參數(shù)說(shuō)明:
sockfd:標(biāo)識(shí)一個(gè)已連接套接口的描述字并闲,就是我們剛剛創(chuàng)建的那個(gè)_clinenId细睡。
addr指針,指向目的套接字的地址帝火。
addrlen:接收返回地址的緩沖區(qū)長(zhǎng)度溜徙。
返回值:成功則返回0湃缎,失敗返回非0,錯(cuò)誤碼GetLastError()蠢壹。

4. 發(fā)送消息

函數(shù)原型:

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

函數(shù)使用:

    const char *msg = @"消息內(nèi)容".UTF8String;
    //send() 等同于 write() 多提供了一個(gè)參數(shù)來(lái)控制讀寫操作
    ssize_t sendLen = send(self.clinenId, msg, strlen(msg), 0);
    NSLog(@"發(fā)送了:%ld字節(jié)",sendLen);

參數(shù)說(shuō)明:
sockfd:一個(gè)用于標(biāo)識(shí)已連接套接口的描述字嗓违。
buff:包含待發(fā)送數(shù)據(jù)的緩沖區(qū)。
nbytes:緩沖區(qū)中數(shù)據(jù)的長(zhǎng)度图贸。
flags:調(diào)用執(zhí)行方式蹂季。
返回值:如果成功,則返回發(fā)送的字節(jié)數(shù)疏日,失敗則返回SOCKET_ERROR偿洁,一個(gè)中文UTF8 編碼對(duì)應(yīng) 3 個(gè)字節(jié)。所以上面發(fā)送了3*4字節(jié)沟优。

5. 接收數(shù)據(jù)

函數(shù)原型:

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

函數(shù)使用:

    uint8_t buffer[1024];
    //recv() 等同于 read() 多提供了一個(gè)參數(shù)來(lái)控制讀寫操作
    ssize_t recvLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
    NSLog(@"接收到了:%ld字節(jié)",recvLen);
    if (recvLen==0) {
        NSLog(@"此次傳輸長(zhǎng)度為0 如果下次還為0 請(qǐng)檢查連接");
    }
    // 接收到的數(shù)據(jù)轉(zhuǎn)換
    NSData *recvData  = [NSData dataWithBytes:buffer length:recvLen];
    NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
    NSLog(@"%@",recvStr);

參數(shù)說(shuō)明:
sockfd:一個(gè)用于標(biāo)識(shí)已連接套接口的描述字涕滋。
buff:包含待發(fā)送數(shù)據(jù)的緩沖區(qū)。
nbytes:緩沖區(qū)中數(shù)據(jù)的長(zhǎng)度挠阁。
flags:調(diào)用執(zhí)行方式宾肺。
返回值:如果成功,則返回讀入的字節(jié)數(shù)侵俗,失敗則返回SOCKET_ERROR锨用。

完整代碼


#pragma mark - 創(chuàng)建socket建立連接
- (IBAction)socketConnetAction:(UIButton *)sender {
    
    // 1: 創(chuàng)建socket
    int socketID = socket(AF_INET, SOCK_STREAM, 0);
    self.clinenId= socketID;
    if (socketID == -1) {
        NSLog(@"創(chuàng)建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(@"連接成功");
    
    // 調(diào)用開(kāi)始接受信息的方法
    // while 如果主線程會(huì)造成堵塞
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self recvMsg];
    });
    
}


#pragma mark - 發(fā)送消息

- (IBAction)sendMsgAction:(id)sender {
    //3: 發(fā)送消息
    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(@"發(fā)送 %ld 字節(jié)",sendLen);
}

#pragma mark - 接受數(shù)據(jù)
- (void)recvMsg{
    // 4. 接收數(shù)據(jù)
    while (1) {
        uint8_t buffer[1024];
        ssize_t recvLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
        NSLog(@"接收到了:%ld字節(jié)",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);
    }
}

調(diào)試

首先 運(yùn)行效果


image.png
方式一,簡(jiǎn)單快捷
  1. 打開(kāi)命令行工具輸入 nc -lk 8040
  2. 點(diǎn)擊連接socket


    image.png
  3. 命令行工具隨便輸入字符 回車


    image.png

    4.模擬器隨便輸入字符 發(fā)送


    image.png
方式二 自己寫一個(gè)本地socket服務(wù)端

聽(tīng)起來(lái)好想很牛逼坡慌,其實(shí)跟上面寫客戶端差不多黔酥。
多了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(@"創(chuàng)建socket 失敗");
        return;
    }
    NSLog(@"創(chuàng)建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: 監(jiān)聽(tīng)socket
    int listen_result = listen(self.serverId, kMaxConnectCount);
    if (listen_result == -1) {
        NSLog(@"監(jiān)聽(tīng)失敗");
        return;
    }
    
    NSLog(@"監(jiān)聽(tīng)成功");
  1. accept()
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        struct sockaddr_in client_address;
        socklen_t address_len;
        // accept函數(shù)
        int client_socket = accept(self.serverId, (struct sockaddr *)&client_address, &address_len);
        self.client_socket = client_socket;
        
        if (client_socket == -1) {
            NSLog(@"接受 %u 客戶端錯(cuò)誤",address_len);           
        }else{
            NSString *acceptInfo = [NSString stringWithFormat:@"客戶端 in,socket:%d",client_socket];
            NSLog(@"%@",acceptInfo);
           //開(kāi)始接受消息
            [self receiveMsgWithClietnSocket:client_socket];
        }
    });
  1. recv()
while (1) {
        // 5: 接受客戶端傳來(lái)的數(shù)據(jù)
        char buf[1024] = {0};
        long iReturn = recv(clientSocket, buf, 1024, 0);
        if (iReturn>0) {
            NSLog(@"客戶端來(lái)消息了");
            // 接收到的數(shù)據(jù)轉(zhuǎn)換
            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 = @"給客戶端發(fā)消息".UTF8String;
    ssize_t sendLen = send(self.client_socket, msg, strlen(msg), 0);
    NSLog(@"發(fā)送了:%ld字節(jié)",sendLen);
  1. close()
    int close_result = close(self.client_socket);
    
    if (close_result == -1) {
        NSLog(@"socket 關(guān)閉失敗");
        return;
    }else{
        NSLog(@"socket 關(guān)閉成功");
    }

那么這篇文章就到這里,寫這篇文章的主要目的是為了讓后面學(xué)習(xí)GCDAsyncSocket時(shí)逗概,加深印象弟晚、深入理解其實(shí)現(xiàn)原理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末逾苫,一起剝皮案震驚了整個(gè)濱河市卿城,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铅搓,老刑警劉巖瑟押,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異星掰,居然都是意外死亡多望,警方通過(guò)查閱死者的電腦和手機(jī)嫩舟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)怀偷,“玉大人家厌,你說(shuō)我怎么就攤上這事∽倒ぃ” “怎么了饭于?”我有些...
    開(kāi)封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)晋渺。 經(jīng)常有香客問(wèn)我镰绎,道長(zhǎng),這世上最難降的妖魔是什么木西? 我笑而不...
    開(kāi)封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任畴栖,我火速辦了婚禮,結(jié)果婚禮上八千,老公的妹妹穿的比我還像新娘吗讶。我一直安慰自己,他們只是感情好恋捆,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布照皆。 她就那樣靜靜地躺著,像睡著了一般沸停。 火紅的嫁衣襯著肌膚如雪膜毁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天愤钾,我揣著相機(jī)與錄音瘟滨,去河邊找鬼。 笑死能颁,一個(gè)胖子當(dāng)著我的面吹牛杂瘸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伙菊,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼败玉,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了镜硕?” 一聲冷哼從身側(cè)響起运翼,我...
    開(kāi)封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎兴枯,沒(méi)想到半個(gè)月后血淌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡念恍,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年六剥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晚顷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疗疟,死狀恐怖该默,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情策彤,我是刑警寧澤栓袖,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站店诗,受9級(jí)特大地震影響裹刮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庞瘸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一捧弃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧擦囊,春花似錦违霞、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至贯被,卻和暖如春眼五,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彤灶。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工看幼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枢希。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓桌吃,卻偏偏與公主長(zhǎng)得像朱沃,于是被迫代替她去往敵國(guó)和親苞轿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348