iOS 用原生代码写一个简单的socket连接

socket简介

描述

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
建立网络通信连接至少要一对端口号(socket)。

socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。

套接字是同一台主机内应用层与传输层之间的接口。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。

连接过程

根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。

  1. 服务器监听:服务器端套接字创建后,处于等待连接的状态,实时监控网络状态
  2. 客户端请求:是指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器端的套接字,指出服务器端套接字的IP地址和端口号,然后向服务器端套接字发出连接请求
  3. 连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求后,会响应客户端套接字的请求,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了,(三次握手)。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
image.png

编码原生客户端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")

  1. 创建socket

    /*
     函数原型:
     int socket(int domain, int type, int protocol);
     
     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)。套接字描述符是一个整数类型的值。
     */
    // 1. 创建socket
    _clientID = socket(AF_INET, SOCK_STREAM, 0);
    
    if (_clientID == -1) {
        [self logMessage:@"创建socket失败"];
        return;
    } else {
        [self logMessage:@"创建socket成功"];
    }
    
  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 connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
     
     参数说明:
     sockfd:标识一个已连接套接口的描述字,就是我们刚刚创建的那个_clinenId。
     addr:指针,指向目的套接字的地址。
     addrlen:接收返回地址的缓冲区长度。
     返回值:成功则返回0,失败返回非0,错误码GetLastError()。
     */
    // 2. 连接socket
    int result = connect(_clientID, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
    if (result != 0) {
        [self logMessage:@"连接socket失败"];
        return;
    } else {
        [self logMessage:@"连接socket成功"];
    }
    
  3. 接收消息

- (void)recvMessage {
    // 4. 接收数据
    while (1) {
        uint8_t buffer[1024];
        ssize_t recvLen = recv(self.clientID, buffer, sizeof(buffer), 0);
        [self logMessage:[NSString stringWithFormat:@"接收了%@字节",@(recvLen)]];
        
        if (recvLen == 0) {
            continue;
        }
        
        //buffer -> data -> string
        NSData *data = [NSData dataWithBytes:buffer length:recvLen];
        NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        
        [self logMessage:[NSString stringWithFormat:@"接收到的字符串:%@",str]];
    }
}
  1. 发送消息
- (void)sendBtnClick {
    // 3. 发送消息
    if (self.sendTextField.text.length == 0) {
        return;
    }
    
    const char *msg = [self.sendTextField.text stringByAppendingString:@"\n"].UTF8String;
    
    ssize_t sendLen = send(self.clientID, msg, strlen(msg), 0);
    [self logMessage:[NSString stringWithFormat:@"发送了%@字节",@(sendLen)]];
    [self logMessage:[NSString stringWithFormat:@"发送到的字符串:%@",[NSString stringWithUTF8String:msg]]];
}

编码原生服务端socket

相比客户端socket,服务端socket多了三步
bind(),listen(),accept()

  1. 头文件导入
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>

/*
 服务器可以使用终端命令:nc -lk SocketPort
 也可以自己自己写一个本地socket服务端
 */
//htons : 将一个无符号短整型的主机数值转换为网络字节顺序,不同cpu 是不同的顺序 (big-endian大尾顺序 , little-endian小尾顺序)
#define SocketPort htons(8040)
//inet_addr是一个计算机函数,功能是将一个点分十进制的IP转换成一个长整数型数
#define SocketIP inet_addr("127.0.0.1")

#define maxConnectCount 5

  1. 创建socket
    // 1. 创建socket
    self.serverID = socket(AF_INET, SOCK_STREAM, 0);
    
    if (self.serverID == -1) {
        NSLog(@"创建socket失败");
        return;
    } else {
        NSLog(@"创建socket成功");
    }
  1. 绑定socket
    // 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;
    
    if (bind(self.serverID, (const struct sockaddr *)&socketAddr, sizeof(socketAddr)) == -1) {
        NSLog(@"绑定Socket失败");
        return;
    } else {
        NSLog(@"绑定socket成功");
    }
  1. 添加socket监听
    // 3. 添加socket监听,让服务器监听客户端的请求
    if (listen(self.serverID, maxConnectCount) == -1) {
        NSLog(@"监听失败");
        return;
    } else {
        NSLog(@"监听成功");
    }
    
  1. 接收客户端请求
    // 4. accept , 当客户端发送请求时,程序为serverSocket创建一个新套接字 ConnectionSocket,用于clientSocket和serverSocket之间创建一个TCP连接
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self acceptSocket];
    });
    
- (void)acceptSocket {
    struct sockaddr_in client_socketAddress;
    socklen_t address_len;
    
    //accept函数
    int client_socketID = accept(self.serverID, (struct sockaddr *restrict)&client_socketAddress, &address_len);
    self.client_socketID = client_socketID;
    
    if (client_socketID == -1) {
        NSLog(@"接收%@客户端错误",@(address_len));
        return;
    } else {
        NSString *acceptInfo = [NSString stringWithFormat:@"客户端 in, socket:%@",@(client_socketID)];
        
        NSLog(@"%@",acceptInfo);
        
        [self receiveMsgWithClietnSocket:client_socketID];
    }
}

  1. 接收消息
// 5. 接收消息
- (void)receiveMsgWithClietnSocket:(int)clientSocketID {
    while (1) {
        char buf[1024] = {0};
        long bufLen = recv(clientSocketID, buf, 1024, 0);
        
        if (bufLen > 0) {
            NSData *recvData = [[NSData alloc] initWithBytes:buf length:bufLen];
            NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
            
            NSLog(@"收到客户端消息:%@",recvStr);
        } else if (bufLen == -1) {
            NSLog(@"客户端消息读取失败");
            break;
        } else if (bufLen == 0) {
            NSLog(@"客户端已关闭");
            close(clientSocketID);
            break;
        }
    }
}

  1. 发送消息
// 6. 发送消息
- (void)sendBtnClick {
    //注意发送时的套接字是连接套接字,而不是服务器的套接字
    const char* msg = self.sendTextField.text.UTF8String;
    ssize_t sendLen = send(self.client_socketID, msg, strlen(msg), 0);
    NSLog(@"socket发送了%@字节",@(sendLen));
}

demo地址:
SocketTest
参考链接:
https://www.jianshu.com/p/a019b582204a

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343