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连接信息。
异步选择
其核心代码:
(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()是干嘛的