【Linux】socket套接字编程 TCP(2)

调试经验:

  • 1.接收一包数据,客户端与服务端需要注意用同一个结构体去存收到的变量。。比如规定通信双方的数据如下面的结构:
struct body {
    uint32 length;
    char buffer[1024];
}__attribute((packed)); //不考虑字节对齐body_t;
struct body buffer;

那么我们在调用recv()函数时,应该这样去接收。。这样可以直接将数据处理。。

int recv_size = recv(connect_fd,&buffer,1028,0);
  • 2.大端,小端的问题:

网络字节序和主机字节序之间的转换接口(ntohs、htons、ntohl、htonl),调用这四个函数并打印返回值,很容易可以找到适合的函数去得到相应的参数。。

  • 3.粘包与组包。。

这个问题来源于:TCP的客户端与服务端属于可靠连接,可以把两端的消息通道想象成一条河流,由于TCP/IP本身为了保证系统网络的最优性能,默认使用Nagle算法(主要作用:减少网络中报文段的数量),于是乎,对于应用层感觉就是每次收取到的数据是不定的,有时候一帧会少几个字节,有时候收到的又与下一帧连在一起了。

解决这个问题,网上有许多办法,讲讲我自己用的并且实现的方法把。

前提:上述的body结构体的length用来表示buffer的数据长度。所以recv_size = length + 4;在实际的调试过程中,遇见了两种情况。

  • 解决方法1:

1.recv_length > length :这就是典型的粘包了。客户端发送的两帧数据被服务端同时收到了。于是便有了以下的处理逻辑代码。。

简单的说就是:根据length的长度依次往后去处理剩下的数据。直到处理完收到的所有数据。

//buffer_tmp即是本帧的数据
int offst = length;
int unrecv_len = length + 4 - recv_size;
struct body* buffer_tmp = (body *)malloc(sizeof( body));
while (unrecv_len < 0) {  
    memset(buffer_tmp,0,sizeof(body));
    memcpy(&buffer_tmp->length,&recv_buffer.buffer[offst],4);
    buffer_tmp->length  = htonl(buffer_tmp->length);
    if (buffer_tmp->length > 1024) {
          break;
    }
    memcpy(buffer_tmp->buffer,&recv_buffer.buffer[offst+4],buffer_tmp->length);
    unrecv_len = unrecv_len + buffer_tmp->length + 4;
    offst = offst + buffer_tmp->length + 4;

/*****************处理buffer_tmp这一帧数据***************/
}
    free(buffer_tmp);
    buffer_tmp = nullptr;

2.recv_length < length:这就需要组包,将收到的两帧数据拼起来才可以得到完整的一帧数据。即调用两次recv函数来接收两帧数据。

int unrecv_len = length + 4 - recv_size;
int offset = recv_size - 4;
while (unrecv_len > 0) {
    recv_size += recv(connect_fd,&recv_buffer.buffer[offset],unrecv_len,0);
    unrecv_len = length + 4 - recv_size;
}
  • 解决方法2:
int readn(int fd, void *buf, size_t count) {
    size_t nleft = count;
    int nread;
    char *bufp = static_cast<char*>(buf);

    while (nleft > 0) {
        if ((nread = read(fd, bufp, nleft)) < 0) {
            if (errno == EINTR)
                continue;
            return -1;
        } else if (nread == 0) {  // 对方关闭或者已经读到eof
            DBG("client close");
            return count - nleft;
        }
        DBG("bufp = %s", bufp);
        bufp += nread;
        nleft -= nread;
    }
    return count;
}

读每一帧的数据的时候,先读前4个字节得到buffer的大小,于是通过while读取特定的长度。这样如果字节长度小了,就是分包那我们就在读,直到读到需要的长度个数。如果粘包,我们也不多读。。

if (readn(connect_fd, head_buffer, 4) == 0) { //读出4字节的数据到head_buffer里面
                        close(connect_fd);
                        break;
                }
                memcpy(&body_length, head_buffer, 4);
                body_length = htonl(body_length);
                printf("body_length == %d", body_length);  // 读到payload长度
  •  bind() 返回错误值为3,目前是用10个端口作为预备连接防止某个端口异常导致连接不上。
    int ret = ::bind(socket_fd, (struct sockaddr*)(&servaddr), sizeof(servaddr));
    if (ret < 0) {
        printf("bind error: %s(errno: %d)\n", strerror(errno), errno);
    }
  •  bind() 返回错误值为2,这是由于这个端口在程序死后,但会进入timeout,大概在1-3min内不能重新bind这个端口。。需要在bind前加上以下的代码,使能端口复用。
int option = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &option, 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值