美文网首页
Socket初探

Socket初探

作者: 纳兰沫 | 来源:发表于2020-05-07 16:22 被阅读0次

Socket 套接字层 插座

Socket 是为网络服务提供的一种机制,通信的两端都是Socket 网络通信其实就是Socket间的通信 数据在两个Socket间
通过IO传输 `Socket 是纯C语言的 是跨平台的`

socket能够主动发送请求 会有速度 带宽 及时性的优点 造就即时通讯

在终端输入nc -lk 端口号 可以用于拦截连接请求

htons(数值) 将一个无符号短整型的主机数值转换为网络字节顺序 不同cpu 是不同的顺序
inet_addr(IP地址) 是一个计算机函数 功能是将一个点分十进制的IP转换成一个长整数型数

Socket的底层方法

1.创建socket socket(AF_INET, SOCK_STREAM, 0)
2.建立连接 connect(socketID, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
3.发送消息 send(self.clinenId, msg, strlen(msg), 0);
4.接受数据 recv(self.clinenId, buffer, sizeof(buffer), 0);
5.关闭 close(self.clinenId);
2.1 绑定 bind(self.serverId, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
3.1 监听 listen(self.serverId, kMaxConnectCount)

  • 创建Socket建立连接
    /**
     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(@"创建socket 失败");
        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 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;
    /**
     参数
     参数一:套接字描述符
     参数二:指向数据结构sockaddr的指针,其中包括目的端口和IP地址
     参数三:参数二sockaddr的长度,可以通过sizeof(struct sockaddr)获得
     返回值
     成功则返回0,失败返回非0,错误码GetLastError()。
     */
    // ip
    int result = connect(socketID, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));

    if (result != 0) {
        NSLog(@"链接失败");
        return;
    }
    NSLog(@"链接成功");

    // 发送数据
    // while 主线程 -- while
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self recvMsg];
    });
  • 发送消息
     /**
     3: 发送消息
     s:一个用于标识已连接套接口的描述字。
     buf:包含待发送数据的缓冲区。
     len:缓冲区中数据的长度。
     flags:调用执行方式。
     
     返回值
     如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR
     一个中文对应 3 个字节!UTF8 编码!
     */
   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);
    [self showMsg:self.sendMsgContent_tf.text msgType:0];
    self.sendMsgContent_tf.text = @"";
  • 接受消息
   /**
     参数
     1> 客户端socket
     2> 接收内容缓冲区地址
     3> 接收内容缓存区长度
     4> 接收方式,0表示阻塞,必须等待服务器返回数据
     
     返回值
     如果成功,则返回读入的字节数,失败则返回SOCKET_ERROR
     */
    
    while (1) {
        uint8_t buffer[1024];
        ssize_t recvLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
        if (recvLen == 0) {
            NSLog(@"接收到了0个字节");
            continue;
        }
        // buffer -> data -> string
        NSData *data = [NSData dataWithBytes:buffer length:recvLen];
        NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"%@---%@",[NSThread currentThread],str);
        dispatch_async(dispatch_get_main_queue(), ^{
            [self showMsg:str msgType:1];
            self.sendMsgContent_tf.text = @"";
        });
    }

GCDAsyncSocket

1.创建 self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
2.建立连接 [self.socket connectToHost:@"127.0.0.1" onPort:8060 withTimeout:-1 error:&error];
3.发送消息 [self.socket writeData:mData withTimeout:-1 tag:10086];
4.读数据 [self.socket readDataWithTimeout:-1 tag:10086];
5.关闭 [self.socket disconnect];
6.代理
每一次连接成功 接受成功 在监听一次

简单使用
 // 创建socket
    if (self.socket == nil)
        self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
    // 连接socket
    if (!self.socket.isConnected){
        NSError *error;
        [self.socket connectToHost:@"127.0.0.1" onPort:8090 withTimeout:-1 error:&error];
        if (error) NSLog(@"%@",error);
    }
//已经连接到服务器
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(nonnull NSString *)host port:(uint16_t)port{
    
    NSLog(@"连接成功 : %@---%d",host,port);
    [self.socket readDataWithTimeout:-1 tag:10086];

}

// 连接断开
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
    NSLog(@"断开 socket连接 原因:%@",err);
}

//已经接收服务器返回来的数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSLog(@"接收到tag = %ld : %ld 长度的数据",tag,data.length);
    [self.socket readDataWithTimeout:-1 tag:10086];
}

//消息发送成功 代理函数 向服务器 发送消息
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
    NSLog(@"%ld 发送数据成功",tag);
}
数据很大的情况

处理办法
1.粘包(把一段两段等等组合成一段进行处理)
2.断包(把一整个数据分成很多段进行处理)处理 断包会出现顺序的问题
顺序的问题的解决办法

  • 分隔符(有可能出现读空)
数据段的前后加上分隔符进行区分是否是该处理的数据段 但是 可能出现读空的情况
  • 数据长度 + 数据类型 + 数据
数据长度是数据长度占用的字节数 + 数据类型占用的字节数 + 数据的字节数的总和
    NSMutableData *mData = [NSMutableData data];
    unsigned int dataLength = 4+4+(int)data.length;
    NSData *lengthData = [NSData dataWithBytes:&dataLength length:4];
    [mData appendData:lengthData];
    
    // 数据类型 data
    // 2.拼接指令类型(4~7:指令)
    NSData *typeData = [NSData dataWithBytes:&dataType length:4];
    [mData appendData:typeData];
    
    // 最后拼接数据
    [mData appendData:data];
    NSLog(@"发送数据的总字节大小:%ld",mData.length);
    
    // 发数据
    [self.socket writeData:mData withTimeout:-1 tag:10086];

接受数据

//已经接收服务器返回来的数据
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSLog(@"接收到tag = %ld : %ld 长度的数据",tag,data.length);
    
    /**
     *  解析服务器返回的数据
     */
    // 获取总的数据包大小
    // 整段数据长度
    NSData *totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)];
    unsigned int totalSize = 0;
    [totalSizeData getBytes:&totalSize length:4];
    NSLog(@"响应总数据的大小 %u",totalSize);
    
    // 获取指令类型
    NSData *commandIdData = [data subdataWithRange:NSMakeRange(4, 4)];
    unsigned int commandId = 0;
    [commandIdData getBytes:&commandId length:4];
    
    // 结果
    NSData *resultData = [data subdataWithRange:NSMakeRange(8, 4)];
    unsigned int result = 0;
    [resultData getBytes:&result length:4];
    
    NSMutableString *str = [NSMutableString string];
    if (commandId == kcImageDataType) {
        [str appendString:@"图片 "];
    }
    
    if(result == 1){
        [str appendString:@"上传成功"];
    }else{
        [str appendString:@"上传失败"];
    }
    NSLog(@"%@",str);
    
    [self.socket readDataWithTimeout:-1 tag:10086];
    
}

连接断开的时候进行重连

- (void)reconnectSocket{
    // 1:关闭socket
    [self disconnectSocket];
    // 2.1 超时判断 > 5  --- 恶意攻击
    if (self.reconnectTime>64) {
        NSLog(@"网络超时,不再重连");
        return;
    }
   // 2.2 延时等待重连
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reconnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 创建socket
        if (self.socket == nil)
            self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
        // 连接socket
        if (!self.socket.isConnected){
            NSError *error;
            [self.socket connectToHost:@"127.0.0.1" onPort:8060 withTimeout:-1 error:&error];
            if (error) NSLog(@"%@",error);
        }
    });
    
    // 3:发现问题,每次都是毫无处理的重连是不合理的,对那些经常出问题的肯定是有问题的
    // 需要做处理 重连次数 重连时间要处理  2^5 = 64
    // 超时时长处理
    if (self.reconnectTime == 0) {
        self.reconnectTime = 2;
    }else{
        self.reconnectTime *= 2;
    }
    
}

心跳包验证保活

- (void)setupHeartBeat{
    
    dispatch_async(dispatch_get_main_queue(), ^{

        [self destoryHeartBeat];
        __weak typeof(self) weakSelf = self;
        self.heartTimer = [NSTimer scheduledTimerWithTimeInterval:15 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSData *heartData = [@"heartBeat" dataUsingEncoding:NSUTF8StringEncoding];
            [strongSelf.socket writeData:heartData withTimeout:-1 tag:10086];
            NSLog(@"heartBeat");
        }];
    });
}

- (void)destoryHeartBeat{
    dispatch_main_async_safe(^{
        if (self.heartTimer && [self.heartTimer respondsToSelector:@selector(isValid)] && [self.heartTimer isValid]) {
            [self.heartTimer invalidate];
            self.heartTimer = nil;
        }
    });
}

相关文章

网友评论

      本文标题:Socket初探

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