recv函数详解
一、recv函数
recv
函数是WinSock编程中用于从已连接套接字或数据报套接字接收数据的函数,其原型如下:
int recv( SOCKET s, char FAR* buf, int len, int flags);
参数说明:
s
:标识已连接的套接字的描述符。
buf
:指向缓冲区的指针,该缓冲区用于存放recv
函数接收到的数据。
len
:缓冲区的长度(以字节为单位)。
flags
:一组影响此函数行为的标志,通常置0。
返回值:
成功时返回接收到的字节数。
如果连接已中止,则返回0。
出错时返回SOCKET_ERROR,可以通过调用WSAGetLastError()获取特定的错误代码。
二、recv函数的执行流程
当应用程序调用recv
函数时,其执行流程如下:
1、等待发送缓冲区数据传送完毕:如果协议在传送发送缓冲中的数据时出现网络错误,recv
函数将返回SOCKET_ERROR
。
2、检查接收缓冲区:如果没有数据或者协议正在接收数据,那么recv
函数会一直等待,直到协议把数据接收完毕。
3、复制数据:一旦协议把数据接收完毕,recv
函数就把接收缓冲中的数据复制到提供的缓冲区buf
中,注意,协议接收到的数据可能大于缓冲区的大小,因此在这种情况下需要多次调用recv
函数才能完全复制接收缓冲中的数据。
4、返回结果:recv
函数返回其实际复制的字节数,如果在复制过程中发生错误,则返回SOCKET_ERROR
;如果在等待期间网络中断了,则返回0。
三、阻塞与非阻塞模式下的recv返回值
阻塞模式:默认情况下,socket是阻塞的,在阻塞模式下,recv
函数会一直等待,直到有数据可读或者发生错误。
成功接收到数据时,返回接收到的字节数。
如果连接被远程计算机优雅地关闭了,返回0。
如果发生错误,返回SOCKET_ERROR。
非阻塞模式:在非阻塞模式下,recv
函数的行为有所不同。
如果有数据可读,返回接收到的字节数。
如果没有数据可读,立即返回0,并设置WSAEWOULDBLOCK
错误。
如果发生错误,返回SOCKET_ERROR。
四、常见错误代码及其含义
WSANOTINITIALISED
:在使用此API之前,必须成功调用WSAStartup
。
WSAENETDOWN
:WINDOWS套接口实现检测到网络子系统失效。
WSAENOTCONN
:套接口未连接。
WSAEINTR
:阻塞进程被WSACancelBlockingCall()
函数取消。
WSAEINPROGRESS
:一个阻塞的WINDOWS套接口调用正在运行中。
WSAENOTSOCK
:描述字不是一个套接口。
WSAEOPNOTSUPP
:指定了MSG_OOB,但套接口不是流样式套接口。
WSAESHUTDOWN
:套接口已被关闭,当一个套接口以0或2的how参数调用shutdown()
关闭后,无法再用recv()
接收数据。
WSAEWOULDBLOCK
:套接口标识为非阻塞模式,但接收操作会产生阻塞。
WSAEMSGSIZE
:数据报太大无法全部装入缓冲区,故被剪切。
WSAEINVAL
:套接口未用bind()
进行捆绑。
WSAECONNABORTED
:由于超时或其他原因,虚电路失效。
WSAECONNRESET
:远端强制中止了虚电路。
五、示例代码
以下是一个简单的示例代码,演示如何使用recv
函数从服务器接收数据:
#include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #include <string.h> // Link with ws2_32.lib #pragma comment(lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 512 #define DEFAULT_PORT "27015" int main() { WSADATA wsaData; int iResult; SOCKET ConnectSocket = INVALID_SOCKET; struct sockaddr_in clientService; char *sendbuf = "this is a test"; char recvbuf[DEFAULT_BUFLEN]; int recvbuflen = DEFAULT_BUFLEN; // Initialize Winsock iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != NO_ERROR) { printf("WSAStartup failed: %d ", iResult); return 1; } // Create a SOCKET for connecting to server ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ConnectSocket == INVALID_SOCKET) { printf("Error at socket(): %ld ", WSAGetLastError()); WSACleanup(); return 1; } // The sockaddr_in structure specifies the address family, // IP address, and port of the server to be connected to. clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr("127.0.0.1"); clientService.sin_port = htons(27015); // Connect to server. iResult = connect(ConnectSocket, (SOCKADDR *)&clientService, sizeof(clientService)); if (iResult == SOCKET_ERROR) { closesocket(ConnectSocket); printf("Unable to connect to server: %ld ", WSAGetLastError()); WSACleanup(); return 1; } // Send an initial buffer iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0); if (iResult == SOCKET_ERROR) { printf("Send failed: %d ", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } printf("Bytes Sent: %ld ", iResult); // Shutdown the connection since no more data will be sent iResult = shutdown(ConnectSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("Shutdown failed: %d ", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } // Receive until the peer closes the connection do { iResult = recv(ConnectSocket, recvbuf, sizeof(recvbuf), 0); if (iResult > 0) { printf("Bytes received: %d ", iResult); printf("Data: %s ", recvbuf); } else if (iResult == 0) { printf("Connection closed "); } else { printf("recv failed: %d ", WSAGetLastError()); } } while (iResult > 0); // Cleanup closesocket(ConnectSocket); WSACleanup(); return 0; }
上述代码展示了如何创建一个套接字,连接到服务器,发送数据,然后接收服务器响应的数据,通过循环调用recv
函数,可以持续接收数据直到连接关闭。