[笔记]windows网络编程之常见模型

22 篇文章 0 订阅
19 篇文章 4 订阅

前言

1.1 select模型

是什么?

对多个socket 进行管理 调用select()可以获取指定socket状态,即select 选择获得有响应的指定的socket

为什么?

解决基本C/S模型中,accept()、recv()、send()阻塞的问题

select模型与C/S模型的不同点
C/S模型中accept()会阻塞一直傻等socket来链接
select模型只解决accept()傻等的问题,不解决recv(),send()执行阻塞问题

怎么用?

服务端
1.一般创建非阻塞步骤(初始化,创建,绑定ip和端口,监听,ioctlsocket设置非阻塞)
2.装填socket数组 FD_SET(socketServer, &allSockets);
3.调用select() 对有响应的socket 做相应处理(accept,send,recv)

具体参照
《windows网络编程》第八章 8.3 基于select模型的socket编程

参考:网络编程——select模型(总结)

1.2 WSAAsyncSelect模型

是什么?

应用程序可以在一个socket上接收以windows消息为基础的网络事件通知,它实现了读写数据的异步通知功能,但不提供异步的数据传输。

为什么?

WSAAsyncSelect是select模型的异步版本。在应用程序使用select函数时会发生阻塞现象。可以通过select的timeout参数设置阻塞的时间。在设置的时间内,select函数等待,直到一个或多个套接字满足可读或可写的条件。
而WSAAsyncSelect是非阻塞的。Windows sockets程序在调用recv或send之前,调用WSAAsyncSelect注册网络事件。WSAAsyncSelect函数立即返回。当系统中数据准备好时,会向应用程序发送消息。此此消息的处理函数中可以调用recv或send进行接收或发送数据。
WSAAsyncSelect模型与select模型的相同点是它们都可以对多个套接字进行管理。但它们也有不小的区别。首先WSAAsyncSelect模型是异步的,且通知方式不同。更重要的一点是:WSAAsyncSelect模型应用在基于消息的Windows环境下,使用该模型时必须创建窗口,而select模型可以广泛应用在Unix系统,使用该模型不需要创建窗口。最后一点区别:应用程序在调用WSAAsyncSelect函数后,套接字就被设置为非阻塞状态。而使用select函数不改变套接字的工作方式。

怎么用?

1.初始化socket环境,并创建win32自定义事件的socket事件 WM_SOCKET
2.调用WSAAsyncSelect()绑定事件到窗口消息队列
3.通过GetMessage()轮询消息,当消息事件发生时,实现对socket的处理(包括accept,send,recv)

具体参照
《windows网络编程》第八章 8.5 基于WSAAsyncSelect模型的服务器编程

1.3 WSAEventSelect模型

是什么?

允许在多个Socket上接收以事件为基础的网络事件通知,应用程序在创建Socket后,调用WSAEventSelect()函数将事件对象与网络事件集合相关联。当网络事件发生时,应用程序以事件的形式接收网络事件通知。

为什么?

WSAEventSelect与WSAAsyncSelect在网络事件发生时系统通知应用程序的形式不同。

Select 主动获取指定socket状态,
WSAEventSelect,WSAAsyncSelect 则会被动选择系统通知应用程序socket状态变化。

WSAEventSelect模型和WSAAsyncSelec模型类似,都是用调用WSAXXXXXSelec函数将socket和事件关联并注册到系统,并将socket设置成非阻塞模式。二者不同之处在于socket事件的通知方法:WSAAsyncSelec模型利用窗口句柄和消息映射函数通知网络事件,而WSAEventSelect模型利用WSAEVENT通知网络事件。

怎么用?

服务端:
1.初始化socket环境,并创建用于监听的socket
2.创建事件对象 WSACreateEvent()
3.将新建的事件对象与监听Socket相关联,并注册该Socket关注的网络事件集合,通常为FD_ACCEPT 和 FD_CLOSE
4.等待所有事件对象上发生注册的网络事件,并对网络事件进行处理。
WSWaitForMultipleEvents()
WSAEnumNetWorkEvents();
5.如果触发了FD_ACCEPT事件,则程序接收来自客户端的请求,得到与客户端通信的Socket,并为该Socket创建相关联的事件
对象,注册该Socket关注网络事件集合,通常为FD_READ,FD_CLOSE和FD_WRITE
6.如果触发了FD_CLOSE事件,则关闭Socket,释放其占用资源
7.如果触发了FD_READ事件,则调用recv函数接收来则客户端的请求
8.如果触发了FD_WRITE事件,则调用send()函数向客户端发送数据

具体参照
《windows网络编程》第八章 8.5 基于WSAEventSelect模型的服务器编程

1.4 重叠I/O模型

是什么?

重叠模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求。针对这些提交的请求,在它们完成之后,应用程序会收到通知,于是就可以通过自己另外的代码来处理这些数据了。
重叠i/o是真正意义上的异步io模型 调用输入/输出函数后 立即返回(WSARecv,WSASend)。

为什么?

1.可以运行在支持Winsock2的所有Windows平台 ,而不像完成端口只是支持NT系统。

2.比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。
因为它和这4种模型不同的是,使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接被拷贝到投递的缓冲区。不需要等待调用recv时才拷贝
而这4种模型种,数据到达并拷贝到单套接字接收缓冲区中,此时应用程序会被告知可以读入的容量。当应用程序调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差别就体现出来了。
3. 从《windows网络编程》中提供的试验结果中可以看到,在使用了P4 1.7G Xero处理器(CPU很强啊)以及768MB的回应服务器中,最大可以处理4万多个SOCKET连接,在处理1万2千个连接的时候CPU占用率才40% 左右 ―― 非常好的性能,已经直逼完成端口。

怎么用?

使用事件通知来管理重叠i/o操作

通过事件来通知应用程序I/O 操作已完成

在WSASend()或者WSARecv函数中,当重叠操作完成后,如果lpCompletionRoutine参数为NULL,则lpOverlapped的hEvent参数将被设为已授信状态。应用程序调用WSAWaitForMultipleEvents()函数或者WSAGetOverlappedResult函数等待或者轮询事件对象变为未授信状态。

WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL) //lpCompletionRoutine参数为NULL,使用事件通知

// OverlappedServer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdlib.h>
#include "winsock2.h"

#pragma   comment(lib,"ws2_32.lib")
#define DATA_BUFSIZE 4096


int _tmain(int argc, _TCHAR* argv[])
{
	//-----------------------------------------
	// 声明和初始化变量
	WSABUF DataBuf;							// 发送和接收数据的缓冲区结构体
	char buffer[DATA_BUFSIZE];			// 缓冲区结构体DataBuf中
	DWORD EventTotal = 0,					// 记录事件对象数组中的数据
		RecvBytes = 0,								// 接收的字节数
		Flags = 0,										// 标识位
		BytesTransferred = 0;					// 在读、写操作中实际传输的字节数
	
	// 数组对象数组
	WSAEVENT EventArray[WSA_MAXIMUM_WAIT_EVENTS];
	WSAOVERLAPPED AcceptOverlapped;		// 重叠结构体
	SOCKET ListenSocket, AcceptSocket;		// 监听套接字和与客户端进行通信的套接字

	//-----------------------------------------
	// 初始化Windows Sockets
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2,2), &wsaData);

	//-----------------------------------------
	// 创建监听套接字,并将其绑定到本地IP地址和9990端口
	ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	u_short port = 9990;
	char* ip;
	sockaddr_in service;
	service.sin_family = AF_INET;
	service.sin_port = htons(port);
	hostent* thisHost;
	thisHost = gethostbyname("");
	ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list);	
	service.sin_addr.s_addr = inet_addr(ip);
	bind(ListenSocket, (SOCKADDR *) &service, sizeof(SOCKADDR));

	//-----------------------------------------
	// 开始监听
	listen(ListenSocket, 1);
	printf("Listening...\n");

	//-----------------------------------------
	// 接收连接请求
	AcceptSocket = accept(ListenSocket, NULL, NULL);
	printf("Client Accepted...\n");

	//-----------------------------------------
	// 创建事件对象,建立重叠结构
	EventArray[EventTotal] = WSACreateEvent();
	ZeroMemory(buffer, DATA_BUFSIZE);
	ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));		// 初始化重叠结构
	AcceptOverlapped.hEvent = EventArray[EventTotal];					// 设置重叠结构中的hEvent字段
	DataBuf.len = DATA_BUFSIZE;														// 设置缓冲区
	DataBuf.buf = buffer;
	EventTotal++;																				// 事件对象总数加1

	//-----------------------------------------
	// 处理在套接字上接收到数据
	while (1) {
		DWORD Index;		// 保存处于授信状态的事件对象句柄

		//-----------------------------------------
		// 调用WSARecv()函数在AcceptSocket套接字上以重叠I/O方式接收数据,保存到DataBuf缓冲区中
		if (WSARecv(AcceptSocket, &DataBuf, 1, &RecvBytes, &Flags, &AcceptOverlapped, NULL) == SOCKET_ERROR) {
			if (WSAGetLastError() != WSA_IO_PENDING)
				printf("Error occured at WSARecv()\n");
		}
		//-----------------------------------------
		// 等待完成的重叠I/O调用
		Index = WSAWaitForMultipleEvents(EventTotal, EventArray, FALSE, WSA_INFINITE, FALSE);


		//-----------------------------------------
		// 决定重叠事件的状态
		WSAGetOverlappedResult(AcceptSocket, &AcceptOverlapped, &BytesTransferred, FALSE, &Flags);
		//-----------------------------------------
		// 如果连接已经关闭,则关闭AcceptSocket套接字
		if (BytesTransferred == 0) {
			printf("Closing Socket %d\n", AcceptSocket);
			closesocket(AcceptSocket);
			WSACloseEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
			return -1;
		}
		//-----------------------------------------
		// 如果有数据到达,则将收到的数据则发送回客户端
		if (WSASend(AcceptSocket, &DataBuf, 1, &RecvBytes, Flags, &AcceptOverlapped, NULL) == SOCKET_ERROR)
			printf("WSASend() is busted\n");

		//-----------------------------------------
		// 重置已授信的事件对象
		WSAResetEvent(EventArray[Index - WSA_WAIT_EVENT_0]);
		//-----------------------------------------		
		// 重置Flags变量和重叠结构
		Flags = 0;
		ZeroMemory(&AcceptOverlapped, sizeof(WSAOVERLAPPED));
		ZeroMemory(buffer, DATA_BUFSIZE);

		AcceptOverlapped.hEvent = EventArray[Index - WSA_WAIT_EVENT_0];
		//-----------------------------------------
		// 重置缓冲区
		DataBuf.len = DATA_BUFSIZE;
		DataBuf.buf = buffer;
	}
	system("pause");
	return 0;
}

使用完成例程来管理重叠i/o操作

完成例程则指定应用程序在完成i/o操作后调用一个事先定义的回调函数。

在WSASend()或者WSARecv函数中,如果lpCompletionRoutine参数不为NULL,则hEvent参数将被忽略,而是将上下文信息传送给完成例程函数,调用WSAGet-OverlapperResult函数查询重叠操作的结果。

完成例程函数原型如下:
void CALLBACK CompletionROUTINE( DWORD dwError, // 重叠操作的完成状态
DWORD cbTransferred, // 发送的字节数
LPWSAOVERLAPPED lpOverlapped, // 指定重叠操作的结构体
DWORD dwFlags) // 标识位

服务端
1.初始化windows socket环境
2.创建监听socket sListen 并将其绑定到本地地址,端口
3.在socket sListen上进行监听
4.创建工作线程,对客户端发送来的数据进行处理
5.循环处理来自客户端的连接请求,接收连接,并将得到的与客户端的socket保存到g_sNewClientConnectio中,将变量g_bNewConnectionArried设置为TRUE,表示存在新的客户端连接。

// CompletionRoutineServer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <winsock2.h> 
#include <stdio.h> 

#define PORT 9990				// 监听的端口
#define MSGSIZE 1024		// 发送和接收消息的最大长度
# pragma comment( lib, "ws2_32.lib" ) 

// I/O操作的数据
typedef struct 
{ 
	WSAOVERLAPPED overlap;				// 重叠结构体
	WSABUF Buffer;								// 缓冲区对象
	char szMessage[MSGSIZE] ;			// 缓冲区字符数组
	DWORD NumberOfBytesRecvd;		// 接收字节数
	DWORD Flags;									// 标识位
	SOCKET sClient;								// 套接字
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA; 

// 工作线程,用于接收用户数据
DWORD WINAPI WorkerThread( LPVOID); 
// 在工作线程中调用WSARecv()函数接收数据时指定的完成例程函数
void CALLBACK CompletionROUTINE( DWORD, DWORD, LPWSAOVERLAPPED, DWORD);

SOCKET g_sNewClientConnection;					// 接收客户端连接请求后得到的
BOOL g_bNewConnectionArrived = FALSE ;		// 标识是否存在未经WorkerThread()函数处理的新的连接

int _tmain(int argc, _TCHAR* argv[])
{
	WSADATA wsaData;										// Windows Sockets对象
	SOCKET sListen;											// 与客户端进行通信的套接字
	SOCKADDR_IN local, client;							// 服务器本地地址和客户端地址
	DWORD dwThreadId;									// 工作线程的线程ID
	int iaddrSize = sizeof(SOCKADDR_IN) ;		// 地址的大小  
	
	// 初始化Windows Socket环境
	WSAStartup( 0x0202, & wsaData) ; 
	// 创建监听套接字
	sListen = socket ( AF_INET , SOCK_STREAM , IPPROTO_TCP ) ; 
	// 绑定
	local.sin_addr. S_un. S_addr = htonl(INADDR_ANY); 
	local.sin_family = AF_INET ; 
	local.sin_port = htons ( PORT) ; 
	bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN)) ; 
	// 监听
	listen(sListen, 3) ; 
	
	// 创建工作线程
	CreateThread( NULL , 0, WorkerThread, NULL , 0, & dwThreadId) ; 
	// 循环处理来自客户端的连接请求
	while(TRUE) 
	{ 
		// 接收连接,得到与客户端进行通信的套接字g_sNewClientConnection
		g_sNewClientConnection = accept(sListen, (struct sockaddr *)&client, &iaddrSize) ; 
		// 标识有新的连接
		g_bNewConnectionArrived = TRUE ; 
		// 打印接入的客户端
		printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client. sin_port)); 
	} 
} 

// 工作线程
DWORD WINAPI WorkerThread( LPVOID lpParam) 
{ 
	LPPER_IO_OPERATION_DATA lpPerIOData = NULL;		// 保存I/O操作的数据
	while (TRUE) 
	{ 
		if ( g_bNewConnectionArrived)		// 如果有新的连接请求
		{ 
			// 为新的连接执行一个异步操作
			// 为LPPER_IO_OPERATION_DATA结构体分配堆空间
			lpPerIOData = (LPPER_IO_OPERATION_DATA) HeapAlloc( 
				GetProcessHeap( ), 
				HEAP_ZERO_MEMORY, 
				sizeof (PER_IO_OPERATION_DATA)) ; 
			// 初始化结构体lpPerIOData
			lpPerIOData->Buffer.len = MSGSIZE; 
			lpPerIOData->Buffer.buf = lpPerIOData->szMessage; 
			lpPerIOData->sClient = g_sNewClientConnection; 
			// 接收数据
			WSARecv( lpPerIOData->sClient,					// 接收数据的套接字
				&lpPerIOData->Buffer,								// 接收数据的缓冲区
				1,																// 缓冲区对象的数量
				&lpPerIOData->NumberOfBytesRecvd,		// 接收数据的字节数
				&lpPerIOData->Flags,								// 标识位
				&lpPerIOData->overlap,							// 重叠结构
				CompletionROUTINE) ;								// 完成例程函数,将会在接收数据完成的时候进行相应的调用 
			g_bNewConnectionArrived = FALSE ;			// 标识新的连接已经处理完成
		} 
		SleepEx(1000, TRUE) ;										// 休息1秒钟,然后继续
	} 
	return 0; 
} 

// 完成例程函数
void CALLBACK CompletionROUTINE( DWORD dwError,	// 重叠操作的完成状态
        DWORD cbTransferred,											// 发送的字节数
        LPWSAOVERLAPPED lpOverlapped,							// 指定重叠操作的结构体
        DWORD dwFlags)														// 标识位
{ 
	// 将LPWSAOVERLAPPED类型的lpOverlapped转化成了LPPER_IO_OPERATION_DATA
	LPPER_IO_OPERATION_DATA lpPerIOData = ( LPPER_IO_OPERATION_DATA) lpOverlapped;
	// 如果发生错误或者没有数据传输,则关闭套接字,释放资源
	if (dwError != 0 || cbTransferred == 0)		
	{ 
		closesocket( lpPerIOData-> sClient) ; 
		HeapFree( GetProcessHeap(), 0, lpPerIOData) ; 
	} 
	else 
	{ 
		lpPerIOData->szMessage[cbTransferred] = '\0';	  // 标识接收数据的结束
		// 向客户端发送接收到的数据
		send(lpPerIOData->sClient, lpPerIOData->szMessage, cbTransferred, 0) ; 
		// 执行另一个异步操作,接收数据
		memset (&lpPerIOData->overlap, 0, sizeof(WSAOVERLAPPED)); 
		lpPerIOData->Buffer.len = MSGSIZE; 
		lpPerIOData->Buffer.buf = lpPerIOData->szMessage; 
		// 接收数据
		WSARecv( lpPerIOData->sClient, 
						&lpPerIOData->Buffer, 
						1, 
						&lpPerIOData->NumberOfBytesRecvd, 
						&lpPerIOData->Flags, 
						&lpPerIOData->overlap, 
						CompletionROUTINE); 
	} 
}

具体参照
《windows网络编程》第八章 8.6 基于重叠io模型的服务器编程

重叠IO模型
重叠IO模型之完成例程

1.5 完成端口模型

是什么?

利用线程池处理异步I/O请求,利用完成端口模型可以管理成百上千Socket。

可以把完成端口看成系统维护的一个队列,操作系统把重叠I/O操作完成的事件通知放到该队列中,因此称其为“完成”端口,当Socket被创建后,可以将其与一个完成端口联系起来。

一个应用程序可以创建多个工作线程用于处理完成端口上的通知事件,通常应该为每个CPU创建一个线程。

一个完成端口实际就是一个通知队列,操作系统把已经完成的重叠I/O请求的通知放到队列中,当某项IO操作完成后,系统会向服务端完成端口发送一个i/o完成数据包,此操作在系统内部完成,应用程序在收到I/o完成数据包后,完成端口队列的一个线程被唤醒,为客户端提供服务,服务完成后,该线程会继续在完成端口上等待.

为什么?

(1) 首先,如果使用“同步”的方式来通信的话,这里说的同步的方式就是说所有的操作都在一个线程内顺序执行完成,这么做缺点是很明显的:因为同步的通信操作会阻塞住来自同一个线程的任何其他操作,只有这个操作完成了之后,后续的操作才可以完成;一个最明显的例子就是咱们在MFC的界面代码中,直接使用阻塞Socket调用的代码,整个界面都会因此而阻塞住没有响应!所以我们不得不为每一个通信的Socket都要建立一个线程,多麻烦?这不坑爹呢么?所以要写高性能的服务器程序,要求通信一定要是异步的。

    (2) 各位读者肯定知道,可以使用使用“同步通信(阻塞通信)+多线程”的方式来改善(1)的情况,那么好,想一下,我们好不容易实现了让服务器端在每一个客户端连入之后,都要启动一个新的Thread和客户端进行通信,有多少个客户端,就需要启动多少个线程,对吧;但是由于这些线程都是处于运行状态,所以系统不得不在所有可运行的线程之间进行上下文的切换,我们自己是没啥感觉,但是CPU却痛苦不堪了,因为线程切换是相当浪费CPU时间的,如果客户端的连入线程过多,这就会弄得CPU都忙着去切换线程了,根本没有多少时间去执行线程体了,所以效率是非常低下的,承认坑爹了不?

    (3) 而微软提出完成端口模型的初衷,就是为了解决这种"one-thread-per-client"的缺点的,它充分利用内核对象的调度,只使用少量的几个线程来处理和客户端的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能,

怎么用?

服务端:
1.初始化windows socket 环境
2.创建完成端口对象completionPort
3.根据当前计算机CPU的数量创建工作线程,并将新建的完成端口对象CompletionPort作为线程的参数
4.创建监听SocketListen 并将其绑定到本地地址 端口
5.在while循环中处理来自客服端的请求连接,接收连接,并将得到的与客户端进行通信的socketAccept保存到PER_HANDLE_DATA结构体对象PerHandleData中.将SocketAccept与前面的端口completionPort关联
6.在socket accept上调用 WSARecv函数,异步接收socket上来自客户端的数据,WSARecv函数立即返回,此时socketAccept上不一定有客户端发送来的消息,在工作线程中会检测完成端口对象的状态,并接收来自客户端的数据,再将这些数据发送回客户端程序.

// CompletionPortServer.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>

#define PORT 9990							// 监听的端口
#define DATA_BUFSIZE 8192			// 发送和接收消息的最大长度
#pragma comment(lib, "Ws2_32")

// 定义I/O操作的结构体
typedef struct                       
{
   OVERLAPPED Overlapped;					// 重叠结构
   WSABUF DataBuf;									// 缓冲区对象
   CHAR Buffer[DATA_BUFSIZE];				// 缓冲区数组
   DWORD BytesSEND;                             // 发送字节数
   DWORD BytesRECV;                             // 接收的字节数    
} PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;

// 套接字句柄结构体
typedef struct											
{
   SOCKET Socket;
} PER_HANDLE_DATA, * LPPER_HANDLE_DATA;

// 服务器端工作线程
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);


int _tmain(int argc, _TCHAR* argv[])
{
	SOCKADDR_IN InternetAddr;							// 服务器地址
	SOCKET Listen;												// 监听套接字
	SOCKET Accept;												// 与客户端进行通信的套接字
	HANDLE CompletionPort;									// 完成端口句柄
	SYSTEM_INFO SystemInfo;								// 获取系统信息(这里主要用于获取CPU数量)
	LPPER_HANDLE_DATA PerHandleData;			// 套接字句柄结构体
	LPPER_IO_OPERATION_DATA PerIoData;		// 定义I/O操作的结构体
	DWORD RecvBytes;											// 接收到的字节数
	DWORD Flags;													// WSARecv()函数中指定的标识位
	DWORD ThreadID;											// 工作线程编号
	WSADATA wsaData;											// Windows Socket初始化信息
	DWORD Ret;														// 函数返回值
	
	// 初始化Windows Sockets环境
	if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
	{
		printf("WSAStartup failed with error %d\n", Ret);
		return -1;
	}
	// 创建新的完成端口
	if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
	{
		printf( "CreateIoCompletionPort failed with error: %d\n", GetLastError());
		return -1;
	}
	// 获取系统信息
	GetSystemInfo(&SystemInfo);   
   // 根据CPU数量启动线程
	for(int i = 0; i<SystemInfo.dwNumberOfProcessors * 2; i++)
	{
		HANDLE ThreadHandle;
		// 创建线程,运行ServerWorkerThread()函数            
		if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,
		 0, &ThreadID)) == NULL)
		{
			 printf("CreateThread() failed with error %d\n", GetLastError());
			 return -1;
		}      
		CloseHandle(ThreadHandle);
	}
	// 创建监听套接字
	if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,
	  WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
	{
		printf("WSASocket() failed with error %d\n", WSAGetLastError());
		return -1;
	}
	// 绑定到本地地址的9990端口
	InternetAddr.sin_family = AF_INET;
	InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	InternetAddr.sin_port = htons(PORT);
	if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
	{
		printf("bind() failed with error %d\n", WSAGetLastError());
		return -1;
	}   
	// 开始监听
	if (listen(Listen, 5) == SOCKET_ERROR)
	{
		printf("listen() failed with error %d\n", WSAGetLastError());
		return -1;
	}
   // 监听端口打开,就开始在这里循环,一有socket连上,WSAAccept就创建一个socket,
   // 这个socket 和完成端口联上
	while(TRUE)
	{
		// 等待客户连接
		if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
		{
			printf("WSAAccept() failed with error %d\n", WSAGetLastError());
			return -1;
		}
		// 分配并设置套接字句柄结构体
		if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA))) == NULL)
		{
			printf("GlobalAlloc() failed with error %d\n", GetLastError());
			return -1;
		}	  
		PerHandleData->Socket = Accept;
	  
		// 将与客户端进行通信的套接字Accept与完成端口CompletionPort相关联
		if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData,
				0) == NULL)
		{
			printf("CreateIoCompletionPort failed with error %d\n", GetLastError());
			return -1;
		}
		// 为I/O操作结构体分配内存空间
		if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR,sizeof(PER_IO_OPERATION_DATA))) == NULL)
		{
			printf("GlobalAlloc() failed with error %d\n", GetLastError());
			return -1;
		}
		// 初始化I/O操作结构体
		ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
		PerIoData->BytesSEND = 0;
		PerIoData->BytesRECV = 0;
		PerIoData->DataBuf.len = DATA_BUFSIZE;
		PerIoData->DataBuf.buf = PerIoData->Buffer;
		Flags = 0;
	  
		// 接收数据,放到PerIoData中,而perIoData又通过工作线程中的ServerWorkerThread函数取出,
		if (WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
			&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
		{
			if (WSAGetLastError() != ERROR_IO_PENDING)
			{
				printf("WSARecv() failed with error %d\n", WSAGetLastError());
				return -1;
			}
		}
	}
	return 0;
}


//	工作线程,循环检测完成端口状态,获取PerIoData中的数据
DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
{
	HANDLE CompletionPort = (HANDLE) CompletionPortID;	// 完成端口句柄   
	DWORD BytesTransferred;										// 数据传输的字节数
	LPOVERLAPPED Overlapped;									// 重叠结构体
	LPPER_HANDLE_DATA PerHandleData;					// 套接字句柄结构体
	LPPER_IO_OPERATION_DATA PerIoData;				// I/O操作结构体
	DWORD SendBytes, RecvBytes;								// 发送和接收的数量
	DWORD Flags;															// WSARecv()函数中的标识位
  
	while(TRUE)
	{
		// 检查完成端口的状态
		if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
			(LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0)
		{
			printf("GetQueuedCompletionStatus failed with error %d\n", GetLastError());
			return 0;
		}

		// 如果数据传送完了,则退出
		if (BytesTransferred == 0)
		{
			printf("Closing socket %d\n", PerHandleData->Socket);
			// 关闭套接字
			if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
			{
				printf("closesocket() failed with error %d\n", WSAGetLastError());
				return 0;
			}
			// 释放结构体资源
			GlobalFree(PerHandleData);
			GlobalFree(PerIoData);
			continue;
		}     
		// 如果还没有记录接收的数据数量,则将收到的字节数保存在PerIoData->BytesRECV中
		if (PerIoData->BytesRECV == 0)
		{
			PerIoData->BytesRECV = BytesTransferred;
			PerIoData->BytesSEND = 0;
		}
		else   // 如果已经记录了接收的数据数量,则记录发送数据量
		{
			PerIoData->BytesSEND += BytesTransferred;
		}
   
		// 将收到的数据原样发送回客户端
		if (PerIoData->BytesRECV > PerIoData->BytesSEND)
		{
			ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED)); //清0为发送准备
			PerIoData->DataBuf.buf = PerIoData->Buffer + PerIoData->BytesSEND;
			PerIoData->DataBuf.len = PerIoData->BytesRECV - PerIoData->BytesSEND;

			// 一个字节一个字节发送发送数据出去
			if (WSASend(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &SendBytes, 0,
					&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
			{
				if (WSAGetLastError() != ERROR_IO_PENDING)
				{
					printf("WSASend() failed with error %d\n", WSAGetLastError());
					return 0;
				}
			}
		}
		else
		{
			PerIoData->BytesRECV = 0;
			Flags = 0;
			ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
			PerIoData->DataBuf.len = DATA_BUFSIZE;
			PerIoData->DataBuf.buf = PerIoData->Buffer;

			if (WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
				&(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
			{
				if (WSAGetLastError() != ERROR_IO_PENDING)
				{
					printf("WSARecv() failed with error %d\n", WSAGetLastError());
					return 0;
				}
			}
		}
   }
}

具体参照
《windows网络编程》第八章 8.7 基于完成端口模型的服务器编程

完成端口(CompletionPort)详解 - 手把手教你玩转网络编程系列之三

1.6 单播,组播,多播

是什么?

单播,1 对 1
组播,1 对 多
广播,1 对 局域网所有

为什么?

单播,基于tcp,建立可靠连接,需要c/s ip;
组播,基于udp,监听同一ip(224.0.0.0至239.255.255.255)的一组都会接 收到消息;
广播,基于udp,监听 地址:xx.xx.xx.255 都会接收到消息

用途

单播用于连接建立后的私密通讯,组播和广播用于通知,建立连接前的通讯,获取ip。

1.7 安全套接字协议SSL

是什么?

SSL 可以用来保障在internet上数据传输的安全,利用数据加密技术,可确保数据在网络上的传输过程不会被截取及其监听.

SSL用于在web服务器和浏览器之间建立加密连接的标准安全技术.

SSL协议提供的安全信道有以下三个特性:

私密性。因为在握手协议定义了会话密钥后,所有的消息都被加密。

确认性。因为尽管会话的客户端认证是可选的,但是服务器端始终是被认证的。

可靠性。因为传送的消息包括消息完整性检查(使用MAC)。

主要工作在应用层(http,ftp,telnet)和 传输层(tcp/udp)之间的

在这里插入图片描述

参考
图解安全套接字SSL协议的工作原理

安全套接层(SSL)协议

简单了解:Openssl开源安全套接字协议

<<windows网络编程代码>>以及广播代码

百度网盘代码 提取码:72y7

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

二进制怪兽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值