Win32 网络编程模型

Posted 不会写代码的丝丽

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Win32 网络编程模型相关的知识,希望对你有一定的参考价值。

概述

我们在进行网络编程时候往往会选择一种方式进行对多个socket进行操作。比如一个socket对应一个线程等

Select模型

这个模式是将若干socket交付给内核去检测是否可读可写或者有异常。

如下代码HandleClientThreadFunc就是检测多个socket


#include <iostream>
#include<WinSock2.h>
#include<WS2tcpip.h>
#pragma comment(lib,"Ws2_32.lib")
#include<vector>

using namespace std;

vector<SOCKET> g_vctSock;//存放socket的数组

//发送读写信息的函数
void HandleData(SOCKET sockClient) {

	//收发数据
	char buf[MAXBYTE] = { 0 };

	int iResult = recv(sockClient, buf, MAXBYTE, 0);

	if (iResult > 0)
		printf("Bytes received: %d   content %s \\n", iResult, buf);
	else if (iResult == 0) {
		printf("Connection closed\\n");
		return;
	}
	else {
		printf("recv failed: %d\\n", WSAGetLastError());
		return ;
	}


	char aryBuffSend[] = { "server recv ok \\r\\n" };

	int nRet = send(sockClient, aryBuffSend, sizeof(aryBuffSend), 0);

	if (nRet == SOCKET_ERROR)
	{
		printf("发送错误\\r\\n");
	}
}

DWORD WINAPI HandleClientThreadFunc(LPVOID lpParam) {
	while (true)
	{
		//fd_set 这个用于存放要监听的socket
		fd_set fdRead;
		//重置fdRead
		FD_ZERO(&fdRead);

		//todo  此处应该加锁 
		//此处将要监听的socket放入fdRead
		for (auto sock : g_vctSock)
		{
			FD_SET(sock, &fdRead);
		}
		//todo  此处应该解锁

		//调用select检测哪些socket可以读,timeval用于设置超时返回
		timeval tv = { 3,0 };
		//交付内核轮训是否可读可写与异常等。返回后会更新传入的fd_set 
		int nRet = select(fdRead.fd_count, &fdRead,
		 NULL,//设置监听可写的集合
		  NULL,//设置异常监听的fd_set 这里不需要
		   &tv//设置超时
		   );
		   
		//超时就continue
		if (nRet == 0 || nRet == SOCKET_ERROR)
		{
			continue;
		}
		//运行到这fdRead只会剩下可读socket在里面
		
		//处理数据 这里的socket是可读的证明有信息 下面两种方式任选
		/*for (auto itr = g_vctSock.begin(); itr!= g_vctSock.end(); itr++)
		{
			if (FD_ISSET(*itr,&fdRead))
			{
				HandleData(*itr);
			}
		}*/
		for (size_t i = 0; i < fdRead.fd_count; i++)
		{
			SOCKET sock = fdRead.fd_array[i];
			//处理信息
			HandleData(sock);
		}

		
	}
}

int main()
{



   //start 常规初始化代码 start 
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		/* Tell the user that we could not find a usable */
		/* Winsock DLL.                                  */
		printf("WSAStartup failed with error: %d\\n", err);
		return 1;
	}

	/* Confirm that the WinSock DLL supports 2.2.*/
	/* Note that if the DLL supports versions greater    */
	/* than 2.2 in addition to 2.2, it will still return */
	/* 2.2 in wVersion since that is the version we      */
	/* requested.                                        */

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		/* Tell the user that we could not find a usable */
		/* WinSock DLL.                                  */
		printf("Could not find a usable version of Winsock.dll\\n");
		WSACleanup();
		return 1;
	}
	else
		printf("The Winsock 2.2 dll was found okay\\n");
   //en d常规初始化代码 end

	//创建socket
	SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sockServer == INVALID_SOCKET)
	{
		printf("socket创建失败 ,请重启\\r\\n");
		return 0;
	}
	//绑定端口
	sockaddr_in siServer;
	siServer.sin_family = AF_INET;
	siServer.sin_port = htons(0x5566);
	int nRet = InetPton(AF_INET, "127.0.0.1", &siServer.sin_addr.s_addr);
	//等于1表示成功
	if (nRet != 1)
	{
		printf("错误");
		return 0;
	}

	nRet = bind(sockServer, (sockaddr*)&siServer, sizeof(siServer));


	if (nRet == SOCKET_ERROR)
	{
		printf("绑定端口失败,请重启 \\r\\n");
		return 0;
	}
	//监听
	nRet = listen(sockServer, SOMAXCONN);
	if (nRet != 0)
	{
		printf("绑定端口失败,请重启 \\r\\n");
		return 0;
	}
	//等候连接
	int len;
	sockaddr_in si;

	int nLen = sizeof(si);

	HANDLE handle = CreateThread(NULL, 0, HandleClientThreadFunc, NULL, 0, NULL);

	while (true)
	{
		SOCKET  resultSocket = accept(sockServer, (sockaddr*)&si, &nLen);
		if (resultSocket == INVALID_SOCKET)
		{
			printf("accept失败,请重启 \\r\\n");
			continue;
		}
		g_vctSock.push_back(resultSocket);
		
	}

	CloseHandle(handle);
	//关闭自身
	closesocket(sockServer);

	/* The Winsock DLL is acceptable. Proceed to use it. */

	/* Add network programming using Winsock here */

	/* then call WSACleanup when done using the Winsock dll */

	WSACleanup();
}

不过上面你会发现select不能进行接收socket连接信息。

异步选择

WSAAsyncSelect 文档

其核心代码:
(1) 创建服务端的套接字,并声明要监听套接字哪些事件

		//创建socket
		SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (sockServer == INVALID_SOCKET)
		{
			printf("socket创建失败 ,请重启\\r\\n");
			return 0;
		}
		//绑定端口
		sockaddr_in siServer;
		siServer.sin_family = AF_INET;
		siServer.sin_port = htons(0x5567);
		int nRet = InetPton(AF_INET, "127.0.0.1", &siServer.sin_addr.s_addr);
		//等于1表示成功
		if (nRet != 1)
		{
			printf("错误");
			return 0;
		}

		nRet = bind(sockServer, (sockaddr*)&siServer, sizeof(siServer));
		
		nRet=listen(sockServer,SOMAXCONN);
		if (nRet == SOCKET_ERROR)
		{
			printf("socket创建失败 ,请重启\\r\\n");
			return 0;
		}
		//为这个WSAAsyncSelect注册对应的事件,有事件时进行回调过程函数,
		nRet = WSAAsyncSelect(sockServer,//放入监听的sock
			hWnd,//窗口
			MY_WM_SOCKMSG,//定义回调的消息
			FD_ACCEPT | //监听哪些事件
			FD_CLOSE
		);
		
		if (nRet != 0)
		{
			printf("WSAEventSelect失败 ,请重启\\r\\n");
			return 0;
		}

(2) 在窗口过程函数中处理消息

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	
	case MY_WM_SOCKMSG: //前面我们定义的消息
	{
		//事件的socket对象
		SOCKET sock = (SOCKET)wParam;
		//这里获得事件
		WORD wEvent = WSAGETSELECTEVENT(lParam);
		//如果当前是接收套接字请求
		if (wEvent & FD_ACCEPT)
		{
			//接收连接
			sockaddr_in si;
			int nLen = sizeof(si);
			SOCKET sockClient = accept(sock,(sockaddr*)&si,&nLen);
			//接收之后再把客户端监听事件也放入
			WSAAsyncSelect(sockClient,//注册的sockServer
				hWnd,//窗口
				MY_WM_SOCKMSG,//定义回调的消息
				FD_READ |
				FD_CLOSE
			);
		}
		else if (wEvent & FD_READ)
		{
			//处理客户端发来的信息
			HandleData(sock);
		}
		else if (wEvent & FD_CLOSE)
		{
			OutputDebugString("close socket port ");

		}
		printf("");
	}
	
}

完整的代码如下:

// TestAsyncSelect.cpp : 定义应用程序的入口点。
//

#include "framework.h"
#include "TestAsyncSelect.h"
#include<WinSock2.h>
#include<WS2tcpip.h>
#pragma comment(lib,"Ws2_32.lib")
#include<vector>
#define MY_WM_SOCKMSG WM_USER+1

#define MAX_LOADSTRING 100

// 全局变量:
HINSTANCE hInst;                                // 当前实例
WCHAR szTitle[MAX_LOADSTRING];                  // 标题栏文本
WCHAR szWindowClass[MAX_LOADSTRING];            // 主窗口类名

// 此代码模块中包含的函数的前向声明:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);

void HandleData(SOCKET sockClient) {

	//收发数据
	char buf[MAXBYTE] = { 0 };

	int iResult = recv(sockClient, buf, MAXBYTE, 0);

	if (iResult > 0)
		printf("Bytes received: %d   content %s \\n", iResult, buf);
	else if (iResult == 0) {
		printf("Connection closed\\n");
		return;
	}
	else {
		printf("recv failed: %d\\n", WSAGetLastError());
		return;
	}


	char aryBuffSend[] = { "server recv ok \\r\\n" };

	int nRet = send(sockClient, aryBuffSend, sizeof(aryBuffSend), 0);

	if (nRet == SOCKET_ERROR)
	{
		printf("发送错误\\r\\n");
	}
}

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
	_In_opt_ HINSTANCE hPrevInstance,
	_In_ LPWSTR    lpCmdLine,
	_In_ int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);



	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
	wVersionRequested = MAKEWORD(2, 2);

	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0) {
		/* Tell the user that we could not find a usable */
		/* Winsock DLL.                                  */
		printf("WSAStartup failed with error: %d\\n", err);
		return 1;
	}

	/* Confirm that the WinSock DLL supports 2.2.*/
	/* Note that if the DLL supports versions greater    */
	/* than 2.2 in addition to 2.2, it will still return */
	/* 2.2 in wVersion since that is the version we      */
	/* requested.                                        */

	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
		/* Tell the user that we could not find a usable */
		/* WinSock DLL.                                  */
		printf("Could not find a usable version of Winsock.dll\\n");
		WSACleanup();
		return 1;
	}
	else
		printf("The Winsock 2.2 dll was found okay\\n");



	// TODO: 在此处放置代码。

	// 初始化全局字符串
	LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadStringW(hInstance, IDC_TESTASYNCSELECT, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// 执行应用程序初始化:
	if (!InitInstance(hInstance, nCmdShow))
	{
		return FALSE;
	}

	HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TESTASYNCSELECT));

	MSG msg;

	// 主消息循环:
	while (GetMessage(&msg, nullptr, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	/* The Winsock DLL is acceptable. Proceed to use it. */

	/* Add network programming using Winsock here */

	/* then call WSACleanup when done using the Winsock dll */

	WSACleanup();
	return (int)msg.wParam;
}



//
//  函数: MyRegisterClass()
//
//  目标: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEXW wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style = CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc = WndProc;
	wcex.cbClsExtra = 0;
	wcex.cbWndExtra = 0;
	wcex.hInstance = hInstance;
	wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TESTASYNCSELECT));
	wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
	wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
	wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_TESTASYNCSELECT);
	wcex.lpszClassName = szWindowClass;
	wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassExW(&wcex);
}

//
//   函数: InitInstance(HINSTANCE, int)
//
//   目标: 保存实例句柄并创建主窗口
//
//   注释:
//
//        在此函数中,我们在全局变量中保存实例句柄并
//        创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
	hInst = hInstance; // 将实例句柄存储在全局变量中

	HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

	if (!hWnd)
	{
		return FALSE;
	}

	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

	return TRUE;
}

//
//  函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目标: 处理主窗口的消息。
//
//  WM_COMMAND  - 处理应用程序菜单
//  WM_PAINT    - 绘制主窗口
//  WM_DESTROY  - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_CREATE:
	{
		//创建socket
		SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (sockServer == INVALID_SOCKET)
		{
			printf("socket创建失败 ,请重启\\r\\n");
			return 0;
		}
		//绑定端口
		sockaddr_in siServer;
		siServer.sin_family = AF_INET;
		siServer.sin_port = htons(0x5567);
		int nRet = InetPton(AF_INET, "127.0.0.1", &siServer.sin_addr.s_addr);
		//等于1表示成功
		if (nRet != 1)
		{
			printf("错误");
			return 0;
		}

		nRet = bind(sockServer, (sockaddr*)&siServer, sizeof(siServer));
		
		nRet=listen(sockServer,SOMAXCONN);
		if (nRet == SOCKET_ERRORwin32网络模型之重叠I/O

win32编程里,ransMessage()和DispatchMessage()是干嘛的

使用片段时 Intellij 无法正确识别 Thymeleaf 模型变量

python将文本转换成语音的代码

WINSOCK.06.重叠IO模型:完成例程

UDP.2.SELECT模型