HTTP连接生命周期中SHUT_WR与SHUT_RD的使用问题咨询
关于TCP Socket实现HTTP连接的shutdown问题
背景
我用TCP Socket与服务器建立HTTP连接,步骤如下:
- 调用
connect建立Socket连接 - 调用
send发送HTTP请求 - 调用
recv接收响应
遇到的问题:
- 在步骤2和3之间调用
shutdown(socket_id, SHUT_WR)关闭写入端时,recv会获取到0字节数据; - 在
recv之后尝试关闭写入端时,shutdown函数返回“Socket is not connected”。
目前可行但不理想的解决方法:
- 在
send和shutdown之间等待足够长时间(方案不优) - 不关闭Socket
我的问题
- 为什么在send后立即关闭Socket写入端会导致recv无法获取数据?
- 是否因为HTTP连接在单次请求响应后会自动关闭,导致接收后无法执行shutdown?
- 我的作业要求必须在某个时刻显式关闭Socket写入端,这么做的必要性是什么?应该在何时执行?
完整代码(省略shutdown部分)
int download_page(url_info* info, http_reply* reply) { printf("Downloading page...\n"); int ret = 0; // ===== 解析主机名为IP地址 ===== struct addrinfo* res; struct addrinfo hints; // 指定使用TCP建立连接 memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; char* service = (char*) malloc(10); sprintf(service, "%d", info->port); printf("Resolving host %s on port %s...\n", info->host, service); int errcode = getaddrinfo(info->host, service, &hints, &res); if (errcode || !res || !(res->ai_addr)) { fprintf(stderr, "Could not resolve host %s\n\t%s (%d)", info->host, strerror(errcode), errcode); ret = 1; goto clean0; } printf("Host resolved\n"); char* request_message = http_get_request(info); printf("Request message:\n%s\nEND of request message\n", request_message); // ===== 创建Socket ===== int socket_id = socket(res->ai_family, SOCK_STREAM, 0); if (socket_id == -1) { fprintf(stderr, "Could not create socket\n"); ret = 1; goto clean2; } printf("Socket created\n"); // ===== 连接服务器 ===== if (connect(socket_id, res->ai_addr, res->ai_addrlen)) { fprintf(stderr, "Could not connect to server %s\n", strerror(errno)); ret = 1; goto clean2; } printf("Connected to server :)\n"); // ===== 发送请求到服务器 ===== if (send(socket_id, request_message, strlen(request_message), 0) == -1) { //if (write(socket_id, request_message, strlen(request_message)) == -1) { fprintf(stderr, "Could not send request to server %s\n", strerror(errno)); ret = 1; goto clean2; } else { printf("Request sent to server: \"%s\"\n", request_message); } // ===== 接收响应 ===== int buffer_size = 1024; int remaining_space = buffer_size; reply->reply_buffer = (char*) malloc(buffer_size + 1); reply->reply_buffer_length = 0; if (reply->reply_buffer != NULL) { char* buffer = reply->reply_buffer; int received; do { printf("Attempting to receive at most %d bytes from server...\n", remaining_space); received = recv(socket_id, buffer, remaining_space, 0); if (received == -1) { fprintf(stderr, "Could not receive data from server %s\n", strerror(errno)); ret = 1; goto clean2; } else if (received != 0) { reply->reply_buffer_length += received; printf("\tReceived %d bytes, total of %d bytes.\n", received, reply->reply_buffer_length); buffer_size *= 2; reply->reply_buffer = (char*) realloc(reply->reply_buffer, buffer_size + 1); buffer = reply->reply_buffer + reply->reply_buffer_length; remaining_space = buffer_size - reply->reply_buffer_length; } } while (received != 0); reply->reply_buffer = (char*) realloc(reply->reply_buffer, reply->reply_buffer_length + 1); reply->reply_buffer[reply->reply_buffer_length] = '\0'; //printf("Received %d bytes from server:\n\"%s\"\n", reply->reply_buffer_length, reply->reply_buffer); printf("Received %d bytes from server.\n", reply->reply_buffer_length); } clean2: free(request_message); close(socket_id); clean0: freeaddrinfo(res); return ret; }
请求消息
GET / HTTP/1.1 Host: www.google.com Connection: close
问题解答
1. 为什么send后立即shutdown(SHUT_WR)会导致recv拿不到数据?
send返回成功仅代表数据被写入系统发送缓冲区,不代表已经发送到服务器。此时调用shutdown(SHUT_WR)会向服务器发送FIN包,服务器可能还没完整接收HTTP请求,就判定客户端已结束发送,直接关闭连接,自然不会返回响应,你的recv就会读到0字节(表示连接关闭)。
另外你的请求包含Connection: close头,服务器本应处理完请求后关闭连接,但前提是它完整接收并处理了请求。如果客户端提前发FIN,服务器会直接终止处理流程。
2. 是不是HTTP单次请求响应后自动关闭导致recv后无法shutdown?
是的。因为你指定了Connection: close,服务器发送完响应后会主动关闭连接。当recv读到0字节时,说明服务器已经发送FIN包,TCP连接已处于关闭状态。此时再调用shutdown,自然会返回“Socket is not connected”——连接已被服务器关闭,本地Socket的连接状态已失效。
3. 显式关闭Socket写入端的必要性是什么?该何时执行?
显式调用shutdown(SHUT_WR)的必要性主要有两点:
- 明确告知服务器客户端已发送完所有数据:对于HTTP这类请求-响应协议,关闭写入端是在告诉服务器“我没有更多请求要发了,你可以处理并返回响应”,这在分块发送大请求等场景中很关键。
- 避免资源泄漏:虽然
close会关闭整个Socket,但shutdown可以更精细地控制读写流,确保TCP连接优雅关闭,避免半开连接占用系统资源。
正确的执行时机是:确认所有请求数据已发送到服务器之后,再调用shutdown(SHUT_WR)。不能在send后立即调用,因为系统发送缓冲区可能还有未发送的数据。可以通过两种方式确保数据发送完成:
- 调用
send后,用ioctl获取TIOCOUTQ参数检查发送缓冲区,确认缓冲区为空后再调用shutdown; - 更稳妥的方式是,在
recv开始接收响应后立即调用shutdown(SHUT_WR)——不过要注意,若服务器因Connection: close关闭连接,需在recv返回0前执行shutdown,或处理shutdown的错误(此时连接可能已关闭)。
内容的提问来源于stack exchange,提问作者user8171079
相关产品推荐
相关产品推荐

