调试经验:
- 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,