Windows下的高效网络模型IOCP完整示例

42 篇文章 6 订阅
6 篇文章 0 订阅

IOCP即完成端口(I/O Completion Port),与Linux下的epoll一样,是一种非常高效的网络模型。epoll 是当资源准备就绪时发出可处理通知消息;IOCP 则是当事件完成时发出完成通知消息。

epoll模型就好比去银行办事:
1.到银行排队取号
2.等待期间,可以忙点其它的事情
3.号到了,通知你去窗口办理(需要你自己去办理,有一个办理过程)
4.办理完后离开

IOCP模型就好比去打印资料:
1.到打印店,把资料给老板进行排队处理
2.等待期间,可以忙点其它的事情
3.打印好了,通知你去拿(不需要你自己去打印,老板已经帮你打印好了)
4.办理完后离开

通过类比描述,可以看出,IOCP更先进一些,下面来看看IOCP的实例:

client.c:

#include <WinSock2.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
	WSADATA wsaData;
	int res = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (res != 0)
	{
		printf("Init Network failed:%d", res);
		return 1;
	}

	const char *IP = "127.0.0.1";
	int port = 6000;
	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.S_un.S_addr = inet_addr(IP);

	SOCKET client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	int ret = connect(client, (struct sockaddr *)&server, sizeof(server));
	if (ret < 0)
	{
		printf("connect %s:%d failed\n", IP, port);
		return 1;
	}

	char buffer[1024];
	int idx = 0;
	while (1)
	{
		int n = sprintf(buffer, "No:%d", ++idx);
		send(client, buffer, n, 0);
		memset(buffer, 0, sizeof(buffer));
		int rev = recv(client, buffer, sizeof(buffer), 0);
		if(rev == -1)
		{
			printf("disconnect, exit");
			break;
		}
		if (rev == 0)
		{
			printf("Recv Nothing\n");
		}
		else
		{
			printf("Recv: %s\n", buffer);
		}
		Sleep(5000);
	}
	closesocket(client);
	WSACleanup();
	return 0;
}

使用accept函数的server.c

#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>

#pragma comment(lib, "ws2_32.lib")

typedef enum IoKind
{
	IoREAD,
	IoWRITE
} IoKind;

typedef struct IoData
{
	OVERLAPPED Overlapped;
	WSABUF wsabuf;
	DWORD nBytes;
	IoKind opCode;
	SOCKET cliSock;
} IoData;

void CloseConn(IoData* ctx)
{
	closesocket(ctx->cliSock);
	free(ctx->wsabuf.buf);
	free(ctx);
}

BOOL PostRead(IoData* data)
{
	memset(&data->Overlapped, 0, sizeof(data->Overlapped));
	data->opCode = IoREAD;
	DWORD dwFlags = 0;
	int nRet = WSARecv(data->cliSock, &data->wsabuf, 1, &data->nBytes, &dwFlags, &data->Overlapped, NULL);
	if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
	{
		printf("WASRecv Failed:%d\n", WSAGetLastError());
		CloseConn(data);
		return FALSE;
	}
	return TRUE;
}

BOOL PostWrite(IoData* data)
{
	memset(&data->Overlapped, 0, sizeof(data->Overlapped));
	data->opCode = IoWRITE;
	printf("%lld Send %s\n", data->cliSock, data->wsabuf.buf);
	int nRet = WSASend(data->cliSock, &data->wsabuf, 1, &data->nBytes, 0, &(data->Overlapped), NULL);
	if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
	{
		printf("WASSend Failed:%d", WSAGetLastError());
		CloseConn(data);
		return FALSE;
	}
	return TRUE;
}

DWORD WINAPI WorkerThread(HANDLE hIOCP)
{
	IoData* ctx = NULL;
	DWORD dwIoSize = 0;
	void* lpCompletionKey = NULL;
	LPOVERLAPPED lpOverlapped = NULL;

	while (1)
	{
		GetQueuedCompletionStatus(hIOCP, &dwIoSize, (PULONG_PTR)&lpCompletionKey, (LPOVERLAPPED*)&lpOverlapped, INFINITE);
		ctx = (IoData*)lpOverlapped;
		if (dwIoSize == 0)
		{
			if (ctx == NULL)
			{
				printf("WorkerThread Exit...\n");
				break;
			}
			printf("Client:%lld disconnect\n", ctx->cliSock);
			CloseConn(ctx);
			continue;
		}
		if (ctx->opCode == IoREAD)
		{
			ctx->wsabuf.buf[dwIoSize] = 0;
			PostWrite(ctx, dwIoSize);
		}
		else if (ctx->opCode == IoWRITE)
		{
			PostRead(ctx);
		}
	}
	return 0;
}

static BOOL IsExit = FALSE;

void OnSignal(int sig)
{
	IsExit = TRUE;
	printf("Recv exit signal...\n");
}

void SetNonblocking(SOCKET fd)
{
	// Windows下有两个函数设置套接字为非阻塞,一个是常见的ioctlsocket,另一个是比它更强大的WSAIoctl。
#if 1
	unsigned long inBuf = 1; // 如果要启用非阻止模式,则为非零;如果要禁用该模式,则为零。
	DWORD lpcbBytesReturned = 0;
	WSAOVERLAPPED Overlapped;
	ZeroMemory(&Overlapped, sizeof(Overlapped));
	if (SOCKET_ERROR == WSAIoctl(fd, FIONBIO, &inBuf, sizeof(inBuf), NULL, 0, &lpcbBytesReturned, &Overlapped, NULL))
	{
		printf("set socket:%lld non blocking failed:%d\n", fd, WSAGetLastError());
	}
#else
	unsigned long ul = 1;  // 如果要启用非阻止模式,则为非零;如果要禁用该模式,则为零。
	int ret = ioctlsocket(fd, FIONBIO, &ul);
	if (ret == SOCKET_ERROR)
	{
		printf("set socket:%lld non blocking failed:%d\n", fd, WSAGetLastError());
	}
#endif
}

void NetWork(int port)
{
	WSADATA wsaData;
	int res = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (res != 0)
	{
		printf("Init Network failed:%d", res);
		return;
	}

	SOCKET m_socket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

	if (bind(m_socket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
	{
		printf("bind failed:%d", WSAGetLastError());
		return;
	}
	if (listen(m_socket, 0) == SOCKET_ERROR)
	{
		printf("listen failed:%d", WSAGetLastError());
		return;
	}

	SYSTEM_INFO sysInfo;
	GetSystemInfo(&sysInfo);
	DWORD threadCount = sysInfo.dwNumberOfProcessors;

	HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, threadCount);
	if (hIOCP == NULL)
	{
		printf("CreateIoCompletionPort failed:%d", GetLastError());
		return;
	}
	for (int i = 0; i < threadCount; ++i)
	{
		DWORD dwThreadId = 0;
		HANDLE hThread = CreateThread(NULL, 0, WorkerThread, hIOCP, 0, &dwThreadId);
		if (hThread == NULL)
		{
			printf("CreateThread failed:%d", GetLastError());
			continue;
		}
		CloseHandle(hThread);
	}
	// 设置为非阻塞模式后,下面的accept函数将立即返回,不会阻塞;否则accept函数会等待一个新的连接到来时,才会返回。
	SetNonblocking(m_socket);
	while (!IsExit)
	{
		SOCKET cliSock = accept(m_socket, NULL, NULL);
		if (cliSock == SOCKET_ERROR)
		{
			Sleep(10);
			continue;
		}
		printf("Client:%lld connected.\n", cliSock);
		if (CreateIoCompletionPort((HANDLE)cliSock, hIOCP, 0, 0) == NULL)
		{
			printf("Binding Client Socket to IO Completion Port Failed:%u\n", GetLastError());
			closesocket(cliSock);
		}
		else
		{
			IoData* data = (IoData*)malloc(sizeof(IoData));
			if (data == NULL)
			{
				printf("out of memory");
				closesocket(cliSock);
				continue;
			}
			memset(data, 0, sizeof(IoData));
			data->wsabuf.buf = (char*)malloc(1024);
			data->wsabuf.len = 1024;
			data->cliSock = cliSock;
			PostRead(data);
		}
	}
	PostQueuedCompletionStatus(hIOCP, 0, 0, 0);
	closesocket(m_socket);
	WSACleanup();
}

int main()
{
	SetConsoleOutputCP(65001);
	typedef void (*SignalHandlerPointer)(int);
	SignalHandlerPointer previousHandler = signal(SIGINT, OnSignal);
	NetWork(6000);
	printf("exit\n");
	return 0;
}

Windows下比较常见的还有一种接受网络连接的函数,就是使用AcceptEx函数或者直接使用它的函数指针:

LPFN_ACCEPTEX lpfnAcceptEx = NULL;
GUID GuidAcceptEx = WSAID_ACCEPTEX;
DWORD dwBytes = 0;

// Load the AcceptEx function into memory using WSAIoctl.
// The WSAIoctl function is an extension of the ioctlsocket()
// function that can use overlapped I/O. The function's 3rd
// through 6th parameters are input and output buffers where
// we pass the pointer to our AcceptEx function. This is used
// so that we can call the AcceptEx function directly, rather
// than refer to the Mswsock.lib library.
int iResult = WSAIoctl(listenSocket, SIO_GET_EXTENSION_FUNCTION_POINTER,
	&GuidAcceptEx, sizeof(GuidAcceptEx),
	&lpfnAcceptEx, sizeof(lpfnAcceptEx),
	&dwBytes, NULL, NULL);
if (iResult == SOCKET_ERROR)
{
	wprintf(L"WSAIoctl failed with error: %u\n", WSAGetLastError());
	closesocket(listenSocket);
	WSACleanup();
	return;
}

使用AcceptEx函数指针的server_ex.c

#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <fcntl.h>
#include <mswsock.h>

#pragma comment(lib, "ws2_32.lib")

typedef enum IoKind
{
	IoREAD,
	IoWRITE
} IoKind;

typedef struct IoData
{
	OVERLAPPED Overlapped;
	WSABUF wsabuf;
	DWORD nBytes;
	IoKind opCode;
	SOCKET cliSock;
} IoData;

void CloseConn(IoData* ctx)
{
	if (ctx->Overlapped.hEvent != NULL)
	{
		WSACloseEvent(ctx->Overlapped.hEvent);
		ctx->Overlapped.hEvent = NULL;
	}
	closesocket(ctx->cliSock);
	free(ctx->wsabuf.buf);
	free(ctx);
}

BOOL PostRead(IoData* data)
{
	memset(&data->Overlapped, 0, sizeof(data->Overlapped));
	data->opCode = IoREAD;
	DWORD dwFlags = 0;
	int nRet = WSARecv(data->cliSock, &data->wsabuf, 1, &data->nBytes, &dwFlags, &data->Overlapped, NULL);
	if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
	{
		printf("WASRecv Failed:%d\n", WSAGetLastError());
		CloseConn(data);
		return FALSE;
	}
	return TRUE;
}

BOOL PostWrite(IoData* data)
{
	memset(&data->Overlapped, 0, sizeof(data->Overlapped));
	data->opCode = IoWRITE;
	printf("%lld Send %s\n", data->cliSock, data->wsabuf.buf);
	int nRet = WSASend(data->cliSock, &data->wsabuf, 1, &data->nBytes, 0, &(data->Overlapped), NULL);
	if (nRet == SOCKET_ERROR && (ERROR_IO_PENDING != WSAGetLastError()))
	{
		printf("WASSend Failed:%d", WSAGetLastError());
		CloseConn(data);
		return FALSE;
	}
	return TRUE;
}

DWORD WINAPI WorkerThread(HANDLE hIOCP)
{
	IoData* ctx = NULL;
	DWORD dwIoSize = 0;
	void* lpCompletionKey = NULL;
	LPOVERLAPPED lpOverlapped = NULL;

	while (1)
	{
		GetQueuedCompletionStatus(hIOCP, &dwIoSize, (PULONG_PTR)&lpCompletionKey, (LPOVERLAPPED*)&lpOverlapped, INFINITE);
		ctx = (IoData*)lpOverlapped;
		if (dwIoSize == 0)
		{
			if (ctx == NULL)
			{
				printf("WorkerThread Exit...\n");
				break;
			}
			printf("Client:%lld disconnect\n", ctx->cliSock);
			CloseConn(ctx);
			continue;
		}
		if (ctx->opCode == IoREAD)
		{
			ctx->wsabuf.buf[dwIoSize] = 0;
			PostWrite(ctx);
		}
		else if (ctx->opCode == IoWRITE)
		{
			PostRead(ctx);
		}
	}
	return 0;
}

static BOOL IsExit = FALSE;

void OnSignal(int sig)
{
	IsExit = TRUE;
	printf("Recv exit signal...\n");
}

void NetWork(int port)
{
	WSADATA wsaData;
	int res = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (res != 0)
	{
		printf("Init Network failed:%d", res);
		return;
	}

	SOCKET listenSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

	struct sockaddr_in server;
	server.sin_family = AF_INET;
	server.sin_port = htons(port);
	server.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

	if (bind(listenSocket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)
	{
		printf("bind failed:%d", WSAGetLastError());
		return;
	}
	if (listen(listenSocket, 5) == SOCKET_ERROR)
	{
		printf("listen failed:%d", WSAGetLastError());
		return;
	}

	SYSTEM_INFO sysInfo;
	GetSystemInfo(&sysInfo);
	DWORD threadCount = sysInfo.dwNumberOfProcessors;
	threadCount = 1;

	HANDLE hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, threadCount);
	if (hIOCP == NULL)
	{
		printf("CreateIoCompletionPort failed:%d", GetLastError());
		return;
	}
	for (int i = 0; i < threadCount; ++i)
	{
		DWORD dwThreadId = 0;
		HANDLE hThread = CreateThread(NULL, 0, WorkerThread, hIOCP, 0, &dwThreadId);
		if (hThread == NULL)
		{
			printf("CreateThread failed:%d", GetLastError());
			continue;
		}
		CloseHandle(hThread);
	}

	LPFN_ACCEPTEX lpfnAcceptEx = NULL;
	GUID GuidAcceptEx = WSAID_ACCEPTEX;
	DWORD dwBytes = 0;

	// Load the AcceptEx function into memory using WSAIoctl.
	// The WSAIoctl function is an extension of the ioctlsocket()
	// function that can use overlapped I/O. The function's 3rd
	// through 6th parameters are input and output buffers where
	// we pass the pointer to our AcceptEx function. This is used
	// so that we can call the AcceptEx function directly, rather
	// than refer to the Mswsock.lib library.
	int iResult = WSAIoctl(listenSocket, SIO_GET_EXTENSION_FUNCTION_POINTER,
		&GuidAcceptEx, sizeof(GuidAcceptEx),
		&lpfnAcceptEx, sizeof(lpfnAcceptEx),
		&dwBytes, NULL, NULL);
	if (iResult == SOCKET_ERROR)
	{
		wprintf(L"WSAIoctl failed with error: %u\n", WSAGetLastError());
		closesocket(listenSocket);
		WSACleanup();
		return;
	}

	while (!IsExit)
	{
		IoData* data = (IoData*)malloc(sizeof(IoData));
		if (data == NULL)
		{
			printf("out of memory");
			break;
		}
		
		SOCKET cliSock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

		data->cliSock = cliSock;
		data->opCode = IoREAD;
		data->wsabuf.len = 1024;
		data->wsabuf.buf = malloc(data->wsabuf.len);
		if (data->wsabuf.buf == NULL)
		{
			printf("out of memory");
			free(data);
			closesocket(cliSock);
			break;
		}
		ZeroMemory(&data->Overlapped, sizeof(data->Overlapped));
		data->Overlapped.hEvent = WSACreateEvent();
		if (data->Overlapped.hEvent == NULL)
		{
			printf("WSACreateEvent failed with error: %d\n", WSAGetLastError());
			closesocket(cliSock);
			free(data->wsabuf.buf);
			free(data);
			break;
		}

		BOOL bRetVal = lpfnAcceptEx(listenSocket, cliSock, data->wsabuf.buf,
			data->wsabuf.len - ((sizeof(struct sockaddr_in) + 16) * 2),
			sizeof(struct sockaddr_in) + 16, sizeof(struct sockaddr_in) + 16,
			&dwBytes, &data->Overlapped);
		if (!bRetVal)
		{
			int err = WSAGetLastError();
			if (err != ERROR_IO_PENDING)
			{
				printf("AcceptEx failed with error: %d\n", err);
				closesocket(cliSock);
				free(data->wsabuf.buf);
				free(data);
				break;
			}
		}
		DWORD res = WSAWaitForMultipleEvents(1, &data->Overlapped.hEvent, 1, WSA_INFINITE, 0);
		if (res == WSA_WAIT_FAILED)
		{
			printf("AcceptEx failed with error: %d\n", WSAGetLastError());
			closesocket(cliSock);
			free(data->wsabuf.buf);
			free(data);
			break;
		}
		WSAResetEvent(data->Overlapped.hEvent);
		printf("Client:%lld connected.\n", cliSock);
		DWORD flag = 0;
		WSAGetOverlappedResult(cliSock, &data->Overlapped, &dwBytes, 1, &flag);
		if (dwBytes == 0)
		{
			closesocket(cliSock);
			free(data->wsabuf.buf);
			free(data);
			continue;
		}
		if (CreateIoCompletionPort((HANDLE)cliSock, hIOCP, 0, 0) == NULL)
		{
			printf("Binding Client Socket to IO Completion Port Failed:%u\n", GetLastError());
			closesocket(cliSock);
			free(data->wsabuf.buf);
			free(data);
			continue;
		}
		else
		{
			data->wsabuf.buf[dwBytes] = 0;
			PostWrite(data);
		}
	}
	PostQueuedCompletionStatus(hIOCP, 0, 0, 0);
	closesocket(listenSocket);
	WSACleanup();
}

int main()
{
	SetConsoleOutputCP(65001);
	typedef void (*SignalHandlerPointer)(int);
	SignalHandlerPointer previousHandler = signal(SIGINT, OnSignal);
	NetWork(6000);
	printf("exit\n");
	return 0;
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.0.0)
project(t VERSION 0.1.0)

include(CTest)
enable_testing()

add_executable(server server_ex.c)
add_executable(client client.c)

target_link_libraries(server ws2_32)
target_link_libraries(client ws2_32)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

以上源码在MinGW下编译运行成功。

如果想要添加MiniDump可以添加前文的MiniDump不生成或者生成0字节的MiniDump.c

代码中调用InitMiniDump(),并修改CMakeLists.txt:

cmake_minimum_required(VERSION 3.0.0)
project(t VERSION 0.1.0)

include(CTest)
enable_testing()

add_executable(server server.c MiniDump.c)
add_executable(client client.c)

target_link_libraries(server ws2_32 dbghelp)
target_link_libraries(client ws2_32)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

# 如果不是使用的MS VC编译器,则需要自行生成PDB文件,方便VS调试Dump文件
# 这里使用cv2pdb(https://github.com/rainers/cv2pdb)工具来生成,需要自行下载,
if(NOT CMAKE_C_COMPILER_ID MATCHES "MSVC")
add_custom_command(TARGET server POST_BUILD
	COMMAND ../cv2pdb64 server.exe
	DEPENDS server.exe
)
endif()

在CMakeLists.txt中有一个判断,如果不是使用的MS VC编译器,则需要自行生成PDB文件,方便VS调试Dump文件,使用cv2pdb工具来生成,需要自行下载。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
IOCP是一种高效的I/O多路复用模型,适用于Windows系统上的网络编程。下面是一个简单的使用IOCP模型的C代码示例,实现了一个简单的回显服务器: ``` #include <stdio.h> #include <stdlib.h> #include <string.h> #include <winsock2.h> #include <windows.h> #define BUFSIZE 1024 typedef struct _PER_HANDLE_DATA { SOCKET Socket; SOCKADDR_STORAGE ClientAddr; } PER_HANDLE_DATA, *LPPER_HANDLE_DATA; typedef struct _PER_IO_DATA { OVERLAPPED Overlapped; WSABUF DataBuf; char Buffer[BUFSIZE]; int OperationType; } PER_IO_DATA, *LPPER_IO_DATA; DWORD WINAPI WorkerThread(LPVOID lpParam); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET ListenSocket, AcceptSocket; SOCKADDR_STORAGE LocalAddr, ClientAddr; DWORD Flags; LPPER_HANDLE_DATA PerHandleData; LPPER_IO_DATA PerIoData; DWORD RecvBytes, SendBytes; DWORD BytesTransferred; DWORD ThreadId; HANDLE CompletionPort, ThreadHandle; int i; // 初始化Winsock if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { printf("WSAStartup failed with error: %d\n", WSAGetLastError()); return 1; } // 创建完成端口 CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); if (CompletionPort == NULL) { printf("CreateIoCompletionPort failed with error: %d\n", GetLastError()); return 1; } // 创建监听套接字 ListenSocket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); if (ListenSocket == INVALID_SOCKET) { printf("socket failed with error: %d\n", WSAGetLastError()); return 1; } // 绑定地址并监听 memset(&LocalAddr, 0, sizeof(LocalAddr)); LocalAddr.ss_family = AF_INET6; ((SOCKADDR_IN6*)&LocalAddr)->sin6_port = htons(12345); ((SOCKADDR_IN6*)&LocalAddr)->sin6_addr = in6addr_any; if (bind(ListenSocket, (SOCKADDR*)&LocalAddr, sizeof(LocalAddr)) == SOCKET_ERROR) { printf("bind failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); return 1; } if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR) { printf("listen failed with error: %d\n", WSAGetLastError()); closesocket(ListenSocket); return 1; } // 创建工作线程 for (i = 0; i < 2; i++) { ThreadHandle = CreateThread(NULL, 0, WorkerThread, CompletionPort, 0, &ThreadId); if (ThreadHandle == NULL) { printf("CreateThread failed with error: %d\n", GetLastError()); return 1; } CloseHandle(ThreadHandle); } // 接受连接并关联到完成端口 while (1) { AcceptSocket = accept(ListenSocket, (SOCKADDR*)&ClientAddr, NULL); if (AcceptSocket == INVALID_SOCKET) { printf("accept failed with error: %d\n", WS

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值