美文网首页
iOS 用原生代码写一个简单的socket连接

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

作者: Mrfang1 | 来源:发表于2020-10-05 19:16 被阅读0次

    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

    相关文章

      网友评论

          本文标题:iOS 用原生代码写一个简单的socket连接

          本文链接:https://www.haomeiwen.com/subject/kowauktx.html