iOS —— Socket

Socket其实就是在传输层对TCP/UDP封装的一层API,俗称套接字。 就是为网络服务提供的一种机制,通信的两端都是Socket(用户端和服务端)两个Socket IO传输,Socket是纯C语言的,是跨平台的。下图:

UDP :(用户数据报文协议) 只管发送,不确认对方是否接收,将数据及源和目的的封装成数据包中,不需要建立连接。每个数据报的大小限制在64K之内。因为无需连接,因此是不可靠协议,不需要建立连接,特点就是:速度快。

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的通信协议,数据在传输前要建立连接,传输完毕后还要断开连接,客户端在收发数据前要使用 connect() 函数和服务器建立连接。建立连接的目的是保证IP地址、端口、物理链路等正确无误,为数据的传输开辟通道。
TCP建立连接时要传输三个数据包,俗称三次握手(Three-way Handshaking),上一篇文章中有讲到。

关于客户端和服务端,服务端只多了bind、listen、accept 这三个函数。

客户端代码:

创建:

    /**
     1: 创建socket
     参数
     domain:协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、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传输协议。
     注意:1.type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。
     返回值:
     如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET(Linux下失败返回-1)
     */
    
 
    int socketId = socket(AF_INET, SOCK_STREAM, 0);
    self.clinenId = socketId;
    if (socketId == -1) {
        NSLog(@"创建失败....");
        return;
    }
    
    /**
     __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];       没有实际意义,只是为了 跟SOCKADDR结构在内存中对齐
     */
    
    struct sockaddr_in socketAdd;
    socketAdd.sin_family = AF_INET;
    socketAdd.sin_port   = SocketPort;
    struct in_addr socket_idAdd;
    socket_idAdd.s_addr  = SocketIP;
    socketAdd.sin_addr   = socket_idAdd;
    
    //
    /**
     参数
     参数一:套接字描述符
     参数二:指向数据结构sockaddr的指针,其中包括目的端口和IP地址
     参数三:参数二sockaddr的长度,可以通过sizeof(struct sockaddr)获得
     返回值
     成功则返回0,失败返回非0,错误码GetLastError()。
     */
    
    // 连接通道
    int result = connect(socketId,(const struct sockaddr *)&socketAdd, sizeof(socketAdd));
    if (result != 0) {
        NSLog(@"连接失败....");
        return;
    }
    NSLog(@"连接成功");
    // 接受消息
    self.index = 0;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        // 这里写接收消息的代码, 如下:
    });

接收消息 :

 // 4. 接收数据
    /**
     参数
     1> 客户端socket
     2> 接收内容缓冲区地址
     3> 接收内容缓存区长度
     4> 接收方式,0表示阻塞,必须等待服务器返回数据
     
     返回值
     如果成功,则返回读入的字节数,失败则返回SOCKET_ERROR
     */
 
    while (1) {
        
        uint8_t buffer[1024];
        ssize_t recLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
        NSLog(@"接受到了%ld 字节",recLen);
        
        if (recLen == 0) {
            self.index ++;
            if (self.index > 5) {
                return ;
            }
        }
        
        NSData *data = [NSData dataWithBytes:buffer length:recLen];
        NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@",str);
        self.index = 0;
        dispatch_async(dispatch_get_main_queue(), ^{
            
            // 在主线程更新UI, 消息:str
        });
        
    }

发送:

 /**
     3: 发送消息
     s:一个用于标识已连接套接口的描述字。
     buf:包含待发送数据的缓冲区。
     len:缓冲区中数据的长度。
     flags:调用执行方式。
     
     返回值
     如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
     一个中文对应 3 个字节!UTF8 编码!
     */
    if (self.sendMsgContent_tf.text.length==0) {
        NSLog(@"消息为空,无法发送");
        return;
    }
    
    const char *msg = self.sendMsgContent_tf.text.UTF8String;
    // 监控发送数据长度
    ssize_t sendLen = send(self.clinenId, msg, strlen(msg), 0);

服务端代码:

#pragma mark - 创建socket建立连接
#define SocketPort htons(8041)
#define SocketIP   inet_addr("192.168.0.102")

static int const kMaxConnectCount = 5;
- (void)socketConnetAction {
    
    // 1: 创建socket
    self.serverId = socket(AF_INET, SOCK_STREAM, 0);
    
    if (self.serverId == -1) {
        NSLog(@"创建socket 失败");
        return;
    }
    NSLog(@"创建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;
    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成功");
    
    // 3: 监听socket
    int listen_result = listen(self.serverId, kMaxConnectCount);
    if (listen_result == -1) {
        NSLog(@"监听失败");
        return;
    }
    NSLog(@"监听成功");
    // 4: 接受客户端的链接
    for (int i = 0; i < kMaxConnectCount; i++) {
        [self acceptClientConnet];
    }
    
     // 发送消息同客服端一样
}

#pragma mark - 接受客户端的链接

- (void)acceptClientConnet{
    
    // 阻塞线程
    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);
        }

    });
    
}

 

 

 


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
iOS 中使用 BSD Socket 进行网络编程时,可以使用 select 函数来实现多路复用。 select 函数可以监视多个文件描述符的状态,当其中有文件描述符发生变化时,select 函数就会返回,并且可以通过返回的结果来判断是哪些文件描述符发生了变化。 具体使用步骤如下: 1. 创建 socket。 2. 绑定 socket。 3. 监听 socket。 4. 创建 fd_set 集合,将需要监视的文件描述符加入集合中。 5. 调用 select 函数,等待文件描述符状态变化。 6. 根据 select 函数的返回结果,判断是哪些文件描述符发生了变化。 7. 处理变化的文件描述符。 下面是一个简单的示例代码: ``` #include <sys/select.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main() { int listenSocket = socket(AF_INET, SOCK_STREAM, 0); if (listenSocket < 0) { perror("socket"); return 1; } struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(12345); serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(listenSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) != 0) { perror("bind"); close(listenSocket); return 1; } if (listen(listenSocket, 5) != 0) { perror("listen"); close(listenSocket); return 1; } fd_set readFds; FD_ZERO(&readFds); FD_SET(listenSocket, &readFds); while (true) { fd_set tmpFds = readFds; int maxFd = listenSocket; if (select(maxFd + 1, &tmpFds, NULL, NULL, NULL) < 0) { perror("select"); close(listenSocket); return 1; } for (int i = 0; i <= maxFd; ++i) { if (FD_ISSET(i, &tmpFds)) { if (i == listenSocket) { // 有新的连接请求 struct sockaddr_in clientAddr; socklen_t addrLen = sizeof(clientAddr); int clientSocket = accept(listenSocket, (struct sockaddr *)&clientAddr, &addrLen); if (clientSocket < 0) { perror("accept"); close(listenSocket); return 1; } printf("New connection from %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port)); FD_SET(clientSocket, &readFds); if (clientSocket > maxFd) { maxFd = clientSocket; } } else { // 有数据可读 char buf[1024]; int len = recv(i, buf, sizeof(buf), 0); if (len < 0) { perror("recv"); close(listenSocket); return 1; } else if (len == 0) { printf("Connection closed\n"); close(i); FD_CLR(i, &readFds); } else { buf[len] = '\0'; printf("Received: %s\n", buf); } } } } } return 0; } ``` 这个示例代码实现了一个简单的 TCP 服务器,可以接受客户端的连接,并且当有数据可读时,会打印出来。在主循环中,我们使用 select 函数等待文件描述符状态变化,当有新的连接请求或者有数据可读时,就会通过 if 语句来判断是哪些文件描述符发生了变化,并且进行相应的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值