在阻塞式的tcp连接中使用recv接收数据未达到指定长度返回问题

本文探讨了在阻塞的TCP Socket上使用read/recv函数的实际行为,并解释了为何读取的数据长度可能与指定长度不一致。文中引用了《UNIX网络编程卷一套接字联网API》的内容,提供了readn和writen函数来确保完整读写指定数量的字节,同时也讨论了recv函数的MSG_WAITALL标志。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 转自http://naso.iteye.com/blog/1927483

 一直以为在阻塞的tcp socket上使用read/recv读取的数据长度一定和指定的读取长度一致,但是实际测试时发现往往返回的长度都比指定长度短,查找资料发现其实是一直误解了这个函数。 

  引用《UNIX网络编程 卷一 套接字联网API》3.9中的说法: 
字节流套接口(如tcp套接口)上的read和write函数所表现的行为不同于通常的文件IO。字节流套接口上的读或写输入或输出的字节数可能比要求的数量少,但这不是错误状况,原因是内核中套接口的缓冲区可能已达到了极限。此时所需的是调用者再次调用read或write函数,以输入或输出剩余的字节。 

  书中给出了readn和writen函数解决这个问题。 
C代码  收藏代码
  1. ssize_t      /* Read "n" bytes from a descriptor. */  
  2. readn(int fd, void *vptr, size_t n)  
  3. {  
  4.  size_t nleft;  
  5.  ssize_t nread;  
  6.  char *ptr;  
  7.   
  8.  ptr = vptr;  
  9.  nleft = n;  
  10.  while (nleft > 0) {  
  11.   if ( (nread = read(fd, ptr, nleft)) < 0) {  
  12.    if (errno == EINTR)  
  13.     nread = 0;  /* and call read() again */  
  14.    else  
  15.     return(-1);  
  16.   } else if (nread == 0)  
  17.    break;    /* EOF */  
  18.   
  19.   nleft -= nread;  
  20.   ptr   += nread;  
  21.  }  
  22.  return(n - nleft);  /* return >= 0 */  
  23. }  

C代码  收藏代码
  1. ssize_t      /* Write "n" bytes to a descriptor. */  
  2. writen(int fd, const void *vptr, size_t n)  
  3. {  
  4.  size_t  nleft;  
  5.  ssize_t  nwritten;  
  6.  const char *ptr;  
  7.   
  8.  ptr = vptr;  
  9.  nleft = n;  
  10.  while (nleft > 0) {  
  11.   if ( (nwritten = write(fd, ptr, nleft)) <= 0) {  
  12.    if (nwritten < 0 && errno == EINTR)  
  13.     nwritten = 0;  /* and call write() again */  
  14.    else  
  15.     return(-1);   /* error */  
  16.   }  
  17.   
  18.   nleft -= nwritten;  
  19.   ptr   += nwritten;  
  20.  }  
  21.  return(n);  
  22. }  

  在13.3中提供了另外一个选择: 
  在recv中,可以使用MSG_WAITALL标志保证要求读取的字节数。即使使用了MSG_WAITALL标志,如果发生了下列情况:(a)捕获一个信号(b)连接被终止,或(c)在套接口上发生错误,这个函数返回的字节数仍会比请求的少。 
  而且要注意的是这个标志并不是每个版本的recv都会实现的。
### TCP Socket 阻塞模式的操作方法 在 Linux 的网络编程中,默认情况下,创建的套接字(socket)是以 **阻塞模式** 运行的。这意味着当调用诸如 `connect` 或 `recv` 等函数时,如果操作尚完成,则会一直等待直到操作成功或者发生错误。 #### 创建并配置阻塞模式下的 TCP 套接字 要实现一个基于阻塞模式的 TCP 客户端或服务器程序,可以按照以下方式编写代码: 1. 使用标准库中的 `socket()` 函数来创建一个新的套接字。 2. 调用 `bind()` 和 `listen()` 来设置监听地址和队列长度(仅适用于服务器端)。 3. 对于客户端,可以直接通过 `connect()` 将其连接到远程主机。 4. 数据传输可以通过 `send()` 发送数据以及通过 `recv()` 接收数据。 这些 API 默认处于阻塞状态,在指定非阻塞标志的情况下无需额外处理即可正常工作[^1]。 以下是具体的 Python 实现例子: ```python import socket def create_blocking_tcp_server(host, port): server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置 SO_REUSEADDR 选项以便快速重用绑定的本地地址 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: server_socket.bind((host, port)) server_socket.listen(5) # 开始监听传入连接请求 print(f"Server listening on {host}:{port}") while True: client_conn, addr = server_socket.accept() # 此处为阻塞点 with client_conn as conn: data = conn.recv(1024) # 另一阻塞点 if not data: break print(f"Received from {addr}: {data.decode('utf-8')}") response_message = f"ECHO: {data.decode('utf-8')}" conn.sendall(response_message.encode('utf-8')) # 向客户发送回显消息 finally: server_socket.close() if __name__ == "__main__": HOST = 'localhost' PORT = 9999 create_blocking_tcp_server(HOST, PORT) ``` 上述脚本展示了如何构建一个简单的阻塞型 TCP 服务器。其中的关键部分在于 `accept()` 方法用于接受新的连接尝试;而 `recv()` 则负责读取来自已建立连接的数据流。这两个过程均会在没有可用事件之前暂停执行线程直至条件满足为止。 对于客户端而言,只需简单地发起一次成功的 `connect()` 请求即进入同步交互阶段: ```python client_socket = socket.create_connection(('localhost', 9999)) try: message_to_send = "Hello Server!" client_socket.sendall(message_to_send.encode('utf-8')) received_data = client_socket.recv(1024).decode('utf-8') print(f"Response from server: {received_data}") finally: client_socket.close() ``` 此段代码片段说明了一个基本的阻塞式 TCP 客户端行为模型——它先向目标服务端发出握手信号,并随后依据实际需求交换信息包。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值