基于Winsock的SMTP客户端:发送数据后卡在recv()函数处
问题:SMTP客户端发送HELO命令后卡在recv调用
我正在开发简易SMTP客户端学习SMTP协议,基于微软《创建基础Winsock应用程序》指南编写,目前实现了交互式终端功能,后续计划自动化发邮件、读收件箱。当前代码能正常接收服务器初始问候,但发送HELO 1.2.3.4这类命令后,会卡在*iResult = recv(*ConnectSocket, out, 200, 0);语句,直到服务器超时返回错误。
运行输出如下:
Bytes received: 111 220 CP6P284CA0098.outlook.office365.com Microsoft ESMTP MAIL Service ready at Tue, 28 Feb 2023 18:17:15 +0000 $> HELO 1.2.3.4 (some time later) Bytes received: 127 451 4.7.0 Timeout waiting for client input [CP6P284CA0098.BRAP284.PROD.OUTLOOK.COM 2023-02-28T18:22:16.228Z 08DB18B4E5C7B417] $>
以下是当前代码:
#ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #define _WIN32_WINNT 0x0501 #include <windows.h> #include <winsock2.h> #include <ws2tcpip.h> #include <iphlpapi.h> #include <stdio.h> #include <stdlib.h> #define DEFAULT_PORT "25" char data[500]; int initWinsock(int *, WSADATA *); int makeSocket(int *, struct addrinfo *, struct addrinfo **, struct addrinfo **, SOCKET *); int connSocket(int *, SOCKET *, struct addrinfo *, struct addrinfo *); int clientReceive(int *, SOCKET *, char *); int clientSend(int *, char *, SOCKET *); int closeSocketListener(int *, SOCKET *); int closeSocket(SOCKET *); int main() { SOCKET ConnectSocket = INVALID_SOCKET; WSADATA wsaData; int iResult; struct addrinfo *result = NULL, *ptr = NULL, hints; ZeroMemory( &hints, sizeof(hints) ); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; initWinsock(&iResult, &wsaData); makeSocket(&iResult, &hints, &result, &ptr, &ConnectSocket); connSocket(&iResult, &ConnectSocket, ptr, result); while (1){ char prompt[200] = {0}; clientReceive(&iResult, &ConnectSocket, data); printf("%s\n$> ", data); fgets(prompt, sizeof prompt, stdin); clientSend(&iResult, prompt, &ConnectSocket); } closeSocketListener(&iResult, &ConnectSocket); closeSocket(&ConnectSocket); system("pause"); return 0; } int initWinsock(int *iResult, WSADATA *wsaData){ // Initialize Winsock *iResult = WSAStartup(MAKEWORD(2,2), wsaData); if (*iResult != 0) { printf("WSAStartup failed: %d\n", *iResult); return 1; } return 0; } int makeSocket(int *iResult, struct addrinfo *hints, struct addrinfo **result, struct addrinfo **ptr, SOCKET *ConnectSocket){ // Resolve the server address and port *iResult = getaddrinfo("52.97.26.134", DEFAULT_PORT, hints, result); if (*iResult != 0) { printf("getaddrinfo failed: %d\n", *iResult); WSACleanup(); return 1; } // Attempt to connect to the first address returned by // the call to getaddrinfo *ptr = *result; // Create a SOCKET for connecting to server *ConnectSocket = socket((*ptr)->ai_family, (*ptr)->ai_socktype, (*ptr)->ai_protocol); if (*ConnectSocket == INVALID_SOCKET) { printf("Error at socket(): %d\n", WSAGetLastError()); freeaddrinfo(*result); WSACleanup(); return 1; } return 0; } int connSocket(int *iResult, SOCKET *ConnectSocket, struct addrinfo *ptr, struct addrinfo *result){ // Connect to server. *iResult = connect( *ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen); if (*iResult == SOCKET_ERROR) { closesocket(*ConnectSocket); *ConnectSocket = INVALID_SOCKET; } // Should really try the next address returned by getaddrinfo // if the connect call failed // But for this simple example we just free the resources // returned by getaddrinfo and print an error message freeaddrinfo(result); if (*ConnectSocket == INVALID_SOCKET) { printf("Unable to connect to server!\n"); WSACleanup(); return 1; } return 0; } int clientReceive(int *iResult, SOCKET *ConnectSocket, char *out){ *iResult = recv(*ConnectSocket, out, 200, 0); if (iResult > 0) { printf("Bytes received: %d\n", *iResult); return 0; } else if (iResult == 0) { printf("Connection closed\n"); return 1; } else { printf("recv failed: %d\n", WSAGetLastError()); return 2; } return -1; } int clientSend(int *iResult, char *sendbuf, SOCKET *ConnectSocket){ *iResult = send(*ConnectSocket, sendbuf, (int) strlen(sendbuf), 0); if (*iResult == SOCKET_ERROR) { printf("send failed: %d\n", WSAGetLastError()); closesocket(*ConnectSocket); WSACleanup(); return 1; } return 0; } int closeSocketListener(int *iResult, SOCKET *ConnectSocket){ // shutdown the send half of the connection since no more data will be sent *iResult = shutdown(*ConnectSocket, SD_SEND); if (*iResult == SOCKET_ERROR) { printf("shutdown failed: %d\n", WSAGetLastError()); closesocket(*ConnectSocket); WSACleanup(); return 1; } return 0; } int closeSocket(SOCKET *ConnectSocket){ // cleanup closesocket(*ConnectSocket); WSACleanup(); return 0; }
问题原因排查
1. SMTP协议命令格式错误
SMTP要求所有命令必须以\r\n(回车+换行)作为结束标记,但你用fgets读取输入时,只会保留用户输入的\n换行符。服务器未收到完整的命令,因此一直等待直到超时,返回451错误。
2. clientReceive函数逻辑错误
函数中判断if (iResult > 0)时,iResult是指针变量,实际应该判断解引用后的*iResult > 0。当前条件永远为真(指针地址不可能小于等于0),导致recv返回0或错误时,代码无法正确处理。
3. 接收缓冲区未清空
每次调用clientReceive时,data缓冲区未被清空,旧数据会与新数据叠加,可能导致后续解析混乱。
修复方案
修复命令换行问题
在发送前将fgets获取的\n替换为SMTP要求的\r\n:
// 在调用clientSend前处理prompt size_t len = strlen(prompt); if (len > 0 && prompt[len-1] == '\n') { prompt[len-1] = '\0'; // 移除原换行符 strcat(prompt, "\r\n"); // 添加CRLF结束标记 }
修复clientReceive判断逻辑
修改函数内的条件判断,正确解引用指针:
int clientReceive(int *iResult, SOCKET *ConnectSocket, char *out){ memset(out, 0, 500); // 每次接收前清空缓冲区 *iResult = recv(*ConnectSocket, out, 200, 0); if (*iResult > 0) { printf("Bytes received: %d\n", *iResult); return 0; } else if (*iResult == 0) { printf("Connection closed\n"); return 1; } else { printf("recv failed: %d\n", WSAGetLastError()); return 2; } return -1; }
补充:Office365 SMTP端口建议
Office365的25端口常被ISP拦截,建议改用587(STARTTLS)或465(SSL/TLS)端口,后续还需实现TLS加密逻辑,否则服务器可能拒绝执行命令。
内容的提问来源于stack exchange,提问作者fpp
相关产品推荐
相关产品推荐

