【c/c++】完成端口服务器中转实现两个客户端之间通信

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wochendaixin/article/details/75313344

主要参考1程序:http://blog.csdn.net/u010025913/article/details/24467351    点击打开链接

主要参考2理论:完成端口的原理我是看的迷糊,还是要看看那篇百度首页大神(小猪)的解读。

现在我的疑问是:

1.是否在完成端口投递的每个异步操作时,都需要新建一个重叠结构OVERLAPPED。因为在参考1中,只是将原来的重叠结构清0.但是在后续的客户端断开后操作之后可能要释放这些内存,这块比较麻烦。

2.在设计通信之间的逻辑时,简直麻烦死了。要服务器针对每个客户端的操作来设计逻辑。(也可能是我思路不对)所以想求一个比较好的完成端口例子。

接下来是我的控制台程序代码:

1.客户端. 采用多线程来模拟两个不同的客户端访问服务器。

头文件   connet.h

#pragma once
#include <iostream>
#include <windows.h>
#include < Ws2bth.h >   

using namespace std;

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

#define MAX_CONCURRENT	2000			// 并发线程最大数
#define IP				"192.168.1.185"			// 服务器端IP地址
#define PORT			123456					// 服务器端口号

struct MyStruct
{
	SOCKET soc;				// SOCKET

	char wStr[512] = { '0' };			// 日志字符串
};
MyStruct mystruct[1024];//结构体数组

class CSOCKET
{

public:
	// 加载Winsock库
	static BOOL WSAInit();

	// 连接服务器端并发送数据
	static BOOL ConnectA(DWORD threadID);
	static BOOL ConnectB(DWORD threadID);
	// 线程函数
	static unsigned int __stdcall _ThreadFuncA();
	static unsigned int __stdcall _ThreadFuncB();

	// 清理工作
	static BOOL Clean(SOCKET sockClient);
};

工程cpp文件 clientMain.cpp(这个文件是以前的一个文件改的,有一些无关的变量没有删去)

#include <process.h> 
#include<iostream>
#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<string>
#include "Connect.h"

using namespace std;
int time;//线程持续时间

volatile long g_ncountright = 0;//全局变量计算多个线程累计成功发送多少条log信息
volatile long g_ncountwrong = 0;//全局变量计算多个线程累计失败发送了多少条log信息
volatile long g_ncountThread = 0;//全局变量计算多少个线程



 // This function returns a pointer to a new string
 // consisting of the first n characters in the str string.
char * left(const char * str, int n)
{
	if (n < 0)
		n = 0;
	char * p = new char[n + 1];
	int i;
	for (i = 0; i < n && str[i]; i++)
		p[i] = str[i];  // copy characters
	while (i <= n)
		p[i++] = '\0';  // set rest of string to '\0'
	return p;
}

int SendDataToSocketServer(SOCKET socketL, const char * cSendData)
{
	Sleep(10);
	int Flag = send(socketL, cSendData, strlen(cSendData), 0);
	if (Flag == SOCKET_ERROR)
	{
		return 444;//如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话那么send函数也返回SOCKET_ERROR
	}
	else
	{
		return Flag;//返回实际copy到发送缓冲区的的字节数
	}
}
int RecvDataToSocketClient(SOCKET socketL, char * cSendData)
{

	if (recv(socketL, cSendData, strlen(cSendData), 0) == SOCKET_ERROR)
	{
		return FALSE;
	}
	return TRUE;
}
/**********************************************************************
Description:   :客户端执行发,收的逻辑程序
Input      :      客户端所用的线程ID
***********************************************************************/
//发送PIN码的客户端A
int DataIntegrationA(SOCKET socketL, DWORD threadID)
{
	int k = GetTickCount();
	int oldK = k;//确定循环发送体的起始时间
	int currK = k;

	//定义并初始化客户端A要发送给服务器的PIN码
	char message[40] = {0};
	sprintf(message, "%s%d%s", "hello<TID:", threadID, ">");


	//在一段时间内time持续发送
	while (currK < (k + time))
	{
		oldK = currK;//
		Sleep(1000);

		//发送
		int  Sendflag = SendDataToSocketServer(socketL, message);
		//发送失败
		if (Sendflag == 444)
		{
			//找发送失败原因		
			printf("\n<线程 %d>客户端发送已经被服务器切断 %d\n", threadID, WSAGetLastError());
			return 1;
		}

		else
		{
			//接收 
			cout << "["<<threadID<<"]" << "发给中转站:" << message << endl;
		}

		currK = GetTickCount();//更新当前运行时间,循环体执行判断	
	}


	return 0;
}
//接收PIN码的客户端
int DataIntegrationB(SOCKET socketL, DWORD threadID)
{
	int k = GetTickCount();
	int oldK = k;//确定循环发送体的起始时间
	int currK = k;


	//定义并初始化客户端从服务器收到的通信回应消息
	char FromSever[100] = {0};

	//在一段时间内time持续发送
	while (currK < (k + time))
	{
		oldK = currK;//
		Sleep(1000);
		memset(FromSever,0,100);
		int RecvFlag = recv(socketL, FromSever, 100, 0);
		if (RecvFlag == 0)
		{
			printf("\n<线程 %d>客户端连接已经被服务器切断,误码 %d\n", threadID, WSAGetLastError());
			return 1;
		}
		else if (RecvFlag < 0)
		{
			printf("\n<线程 %d>客户端接收时出现网络错误, 误码:%d\n", threadID, WSAGetLastError());
		}
		else
		{
			cout <<"[" << threadID << "]" << "收到中转站:" << FromSever << endl;

		}
		
		currK = GetTickCount();//更新当前运行时间,循环体执行判断
	}
}


/***********************************
Description:加载Winsock库
************************************/
BOOL CSOCKET::WSAInit()
{
	WSADATA wsaData;
	int nRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (nRet != 0)
	{
		return FALSE;
	}
	return TRUE;
}


/********************************************
Description	:	连接服务器端并发送数据
InPut		:	sockClient - SOCKET
wStr	   - 日志字符串
Return		:	TRUE	   - 执行成功
FALSE	   - 连接或发送失败
*********************************************/
BOOL CSOCKET::ConnectA(DWORD threadID)
{

	struct sockaddr_in ServerAddress;
	struct hostent *Server;		//包含主机名字和地址信息的hostent结构指针

	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	BOOL bOptval = TRUE;
	setsockopt(sockClient, SOL_SOCKET, SO_KEEPALIVE, (char*)&bOptval, sizeof(bOptval));

	// 生成地址信息
	Server = gethostbyname(IP);
	ZeroMemory((char *)&ServerAddress, sizeof(ServerAddress));
	ServerAddress.sin_family = AF_INET;
	CopyMemory((char *)&ServerAddress.sin_addr.s_addr,
		(char *)Server->h_addr,
		Server->h_length);
	ServerAddress.sin_port = htons(PORT);

	int nRet = 0;
	nRet = connect(sockClient, (SOCKADDR*)&ServerAddress, sizeof(ServerAddress));
	if (nRet == SOCKET_ERROR)
	{
		cout << "初始连接Server失败。" << endl;
		closesocket(sockClient);
		return FALSE;
	}

	else
	{
		DataIntegrationA(sockClient, threadID);

		//时间走完(或者自己未通过验证)关闭套接字
		closesocket(sockClient);
		return TRUE;
	}


}
BOOL CSOCKET::ConnectB(DWORD threadID)
{

	struct sockaddr_in ServerAddress;
	struct hostent *Server;		//包含主机名字和地址信息的hostent结构指针

	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	BOOL bOptval = TRUE;
	setsockopt(sockClient, SOL_SOCKET, SO_KEEPALIVE, (char*)&bOptval, sizeof(bOptval));

	// 生成地址信息
	Server = gethostbyname(IP);
	ZeroMemory((char *)&ServerAddress, sizeof(ServerAddress));
	ServerAddress.sin_family = AF_INET;
	CopyMemory((char *)&ServerAddress.sin_addr.s_addr,
		(char *)Server->h_addr,
		Server->h_length);
	ServerAddress.sin_port = htons(PORT);

	int nRet = 0;
	nRet = connect(sockClient, (SOCKADDR*)&ServerAddress, sizeof(ServerAddress));
	if (nRet == SOCKET_ERROR)
	{
		cout << "初始连接Server失败。" << endl;
		closesocket(sockClient);
		return FALSE;
	}

	else
	{
		
		DataIntegrationB(sockClient, threadID);

		//发完关闭套接字
		closesocket(sockClient);
		return TRUE;
	}


}

/********************************************
Description	:	多线程函数
InPut		:	mystruct - 结构体
*********************************************/
unsigned int CSOCKET::_ThreadFuncA()
{
	DWORD  TID = GetCurrentThreadId();//获得当前线程Id
	Sleep(500);
	//全局变量计算多个线程
	cout << "(客户端)线程 " << TID << "开启" << endl;
	ConnectA(TID);
	InterlockedDecrement(&g_ncountThread);
	cout << "(客户端)线程 " << TID << "关闭" << endl;
	return 1;
}
unsigned int CSOCKET::_ThreadFuncB()
{
	DWORD  TID = GetCurrentThreadId();//获得当前线程Id
	Sleep(1500);
	InterlockedIncrement(&g_ncountThread);//全局变量计算多个线程
	cout << "(客户端)线程 " << TID << "开启" << endl;
	ConnectB(TID);
	InterlockedDecrement(&g_ncountThread);
	cout << "(客户端)线程 " << TID << "关闭" << endl;
	return 1;
}


/********************************************
Description	:	清理工作
InPut		:	sockClient - SOCKET
*********************************************/
BOOL CSOCKET::Clean(SOCKET sockClient)
{
	closesocket(sockClient);
	return TRUE;
}
/*
* Synchronically waiting for all objects signaled.
* - handles : An array of object handles to wait.
* - count   : The count of handles.
* returns : Same as WaitForMultipleObjects.
*/
DWORD SyncWaitForMultipleObjs(HANDLE * handles, int count)
{
	int waitingThreadsCount = count;
	int index = 0;
	DWORD res = 0;
	while (waitingThreadsCount >= MAXIMUM_WAIT_OBJECTS)
	{
		res = WaitForMultipleObjects(MAXIMUM_WAIT_OBJECTS, &handles[index], TRUE, INFINITE);
		if (res == WAIT_TIMEOUT || res == WAIT_FAILED)
		{
			puts("1. Wait Failed.");
			return res;
		}

		waitingThreadsCount -= MAXIMUM_WAIT_OBJECTS;
		index += MAXIMUM_WAIT_OBJECTS;
	}

	if (waitingThreadsCount > 0)
	{
		res = WaitForMultipleObjects(waitingThreadsCount, &handles[index], TRUE, INFINITE);
		if (res == WAIT_TIMEOUT || res == WAIT_FAILED)
		{
			puts("2. Wait Failed.");
		}
	}

	return res;
}


int main()
{
	cout << endl << "每个线程运行时间:";
	cin >> time;

	//num = atoi(argv[1]);
	//time = atoi(argv[2]);
	//cout << "num:" << num << " time:" << time << endl;

	int j = 0;
	CSOCKET::WSAInit();
	HANDLE  hThread[2];

	for (int i = 0; i < 1; i++)
	{
		Sleep(200);

		hThread[j++] = (HANDLE)_beginthreadex(
			NULL,
			0,
			(unsigned int(__stdcall *)(void *))CSOCKET::_ThreadFuncA,
			NULL,
			0,
			NULL
		);

	}
	for (int i = 0; i < 1; i++)
	{
		Sleep(200);

		hThread[j++] = (HANDLE)_beginthreadex(
			NULL,
			0,
			(unsigned int(__stdcall *)(void *))CSOCKET::_ThreadFuncB,
			NULL,
			0,
			NULL
		);

	}

	SyncWaitForMultipleObjs(hThread, 2);
	WSACleanup();

	system("pause");
	return 0;

}

服务器端:Sever.cpp

/////////////////////////////////////////
////要加入验证信息等,和结束释放
//

#include <Afx.h> 
#include <Windows.h>  
#include <Winsock2.h>  
#pragma comment(lib, "WS2_32.lib")  
#include<stdio.h>
#include <mswsock.h>    //微软扩展的类库  
#include<vector>
#include<iostream>
using namespace std;


#define DATA_BUFSIZE 100  
#define READ   0  
#define WRITE  1  
#define ACCEPT 2  

CRITICAL_SECTION  m_thread;
DWORD g_count = 0;
volatile long IDnum = 0;//全局变量计算多少个线程


						//IO结构  
typedef struct _io_operation_data
{
	OVERLAPPED  overlapped;
	WSABUF      databuf;
	CHAR        buffer[DATA_BUFSIZE];
	DWORD       len;
	SOCKET      sock;
	BYTE        type;//请求操作类型(连入,发送,接收)

	int        IOnum = 0; //每个新连入的客户端分配IO数据结构ID
}IO_OPERATION_DATA, *LP_IO_OPERATION_DATA;

//完成键  
typedef struct _completion_key
{
	SOCKET sock;
	char   sIP[100];     //本机测试,IP都是127.0.0.1,没啥意思,实际写时候这个值填的是端口号  
	BOOL  first;//表示是不是客户端连接时发的第一条消息(第一条则定为true,否则定为flase
	char  MsgBuff[100] = "";//不停的更新在这个socket的数据

	int  clientID = 0;//每个新连入的客户端分配完成键ID
}COMPLETION_KEY, *LP_COMPLETION_KEY;

///////////////////////////////////////////////////////////////////////
//程序设计中,为每一个新接入的客户端建立并开辟一个完成键结构和一个IO数据结构,将来客户断开时要释放内存

vector<LP_COMPLETION_KEY> m_arrayClientFlag;// 结构体指针数组,存放指向每个连入客户端Socket的相关信息(地址等)
vector<LP_IO_OPERATION_DATA>m_arrayIOFlag;//结构体指针数组,存放指向每个连入客户端IO数据结构

void  _AddToContextList(LP_COMPLETION_KEY pHandleData, LP_IO_OPERATION_DATA pHandleIO);
void _DelToContextList(LP_COMPLETION_KEY pHandleData); 
void _DelToIOtList(LP_IO_OPERATION_DATA pHandleIO);
 

void RemoveTheLast();//当1号客户端退出时,要剩下的一些内存

///////////////////////////////////////////////////  
//完成端口句柄  
HANDLE g_hComPort = NULL;
//主进程运行标志
BOOL   g_bRun = FALSE;
//监听套接字,其实也不一定要是全局的。用于接收到客户端连接后继续发起等待下一个客户端连接操作。  
SOCKET g_sListen;


///////////////////////////////////////////////////  
//函数指针
LPFN_ACCEPTEX lpfnAcceptEx = NULL;                   //AcceptEx函数指针  
LPFN_GETACCEPTEXSOCKADDRS lpfnGetAcceptExSockaddrs;  //加载GetAcceptExSockaddrs函数指针  

													 ////////////////////////////////////////////////// 
													 //发起接收连接操作  
BOOL AcceptClient(SOCKET sListen);

//发起接收数据操作 
BOOL RecvFunc(COMPLETION_KEY *pComKey, IO_OPERATION_DATA *pIO);

//发起发送数据操作  
BOOL SendFunc(COMPLETION_KEY *pComKey, IO_OPERATION_DATA *pIO, char* msgBuff);
BOOL SendFunc1(COMPLETION_KEY *pComKey, IO_OPERATION_DATA *pIOoperData, char* msgBuff);

//处理IO结果  
BOOL ProcessIO(IO_OPERATION_DATA *pIOdata, COMPLETION_KEY *pComKey);

//服务线程  
DWORD WINAPI ServerWorkerThread(LPVOID pParam);

bool IsSocketAlive(SOCKET s);

int main(int argc, char* argv[])
{
	g_bRun = TRUE;
	InitializeCriticalSection(&m_thread);

	//// 建立第一个完成端口  
	g_hComPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
	if (g_hComPort == NULL)
	{
		printf("Create completionport error!   %d\n", WSAGetLastError());
		return 0;
	}

	//创建服务线程  
	SYSTEM_INFO sysInfor;
	GetSystemInfo(&sysInfor);
	int i = 0;
	for (i = 0; i < sysInfor.dwNumberOfProcessors * 2; i++)
	{
		HANDLE hThread;
		DWORD  dwThreadID;

		hThread = CreateThread(NULL, 0, ServerWorkerThread, g_hComPort, 0, &dwThreadID);
		CloseHandle(hThread);
	}


	//加载套接字库 
	WSADATA wsData;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsData))
	{
		printf("加载套接字库失败!   %d\n", WSAGetLastError());
		g_bRun = FALSE;
		return 0;
	}

	////////////////////////////////////////////////////////////////////  
	//等待客户端连接  

	//先创建一个套接字用于监听  
	SOCKET sListen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	g_sListen = sListen;


	LP_COMPLETION_KEY pComKey;      //完成键(将来存放监听套接字的地址信息和值) 
	pComKey = (LP_COMPLETION_KEY)GlobalAlloc(GPTR, sizeof(COMPLETION_KEY));//开辟内存
	pComKey->sock = sListen;

	//将监听套接字与完成端口绑定(并把完成键传入进来)
	CreateIoCompletionPort((HANDLE)sListen, g_hComPort, (DWORD)pComKey, 0);

	//填充服务器Ip和端口地址  
	SOCKADDR_IN serAdd;
	serAdd.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	serAdd.sin_family = AF_INET;
	serAdd.sin_port = htons(123456);

	//将地址和完成端口进行绑定
	if (SOCKET_ERROR == bind(sListen, (SOCKADDR*)&serAdd, sizeof(SOCKADDR)))
	{
		printf("端口和地址绑定失败\n");
		return 0;
	}


	// 开始进行监听完成端口
	if (SOCKET_ERROR == listen(sListen, SOMAXCONN))
	{
		goto STOP_SERVER;
	}


	/////////////////////////////////////////////////////////////////////  
	//使用WSAIoctl获取AcceptEx函数指针  
	if (true)
	{
		DWORD dwbytes = 0;

		//Accept function GUID  
		GUID guidAcceptEx = WSAID_ACCEPTEX;

		if (SOCKET_ERROR == WSAIoctl(
			sListen,
			SIO_GET_EXTENSION_FUNCTION_POINTER,//将进行的操作的控制代码。
			&guidAcceptEx,
			sizeof(guidAcceptEx),
			&lpfnAcceptEx,
			sizeof(lpfnAcceptEx),
			&dwbytes,
			NULL, //WSAOVERLAPPED结构的地址
			NULL//一个指向操作结束后调用的例程指针
		)
			)
		{
			printf("WSAIoctl 未能获取AcceptEx函数指针\n"); //百度百科,有关该函数的所有返回值都有!  
		}


		// 获取GetAcceptExSockAddrs函数指针,也是同理  
		GUID guidGetAcceptExSockaddrs = WSAID_GETACCEPTEXSOCKADDRS;
		if (SOCKET_ERROR == WSAIoctl(
			sListen,
			SIO_GET_EXTENSION_FUNCTION_POINTER,
			&guidGetAcceptExSockaddrs,
			sizeof(guidGetAcceptExSockaddrs),
			&lpfnGetAcceptExSockaddrs,
			sizeof(lpfnGetAcceptExSockaddrs),
			&dwbytes,
			NULL,
			NULL
		)
			)
		{
			printf("WSAIoctl 未能获取GuidGetAcceptExSockAddrs函数指针\n");
		}

	}

	//第一次就一次性投递多个AcceptEx异步请求,发起接收客户端的异步请求  
	for (i = 0; i < 1; i++)
	{
		AcceptClient(sListen);
	}

	//不让主线程退出  
	while (g_bRun)
	{
		Sleep(1000);
	}

STOP_SERVER:
	closesocket(sListen);
	g_bRun = FALSE;
	DeleteCriticalSection(&m_thread);
	WSACleanup();

	return 0;
}

/////////////////////////////////////////////////////////////////////////  
//服务线程  
DWORD WINAPI ServerWorkerThread(LPVOID pParam)
{
	HANDLE  completionPort = (HANDLE)pParam;
	DWORD   dwIoSize;

	COMPLETION_KEY        *pComKey;     //完成键  
	LP_IO_OPERATION_DATA  lpIOoperData; //I/O数据  
							
	BOOL bRet;

	while (g_bRun)
	{
		//将所用到的变量、指针初始化
		bRet = FALSE;
		dwIoSize = -1;
		pComKey = NULL;
		lpIOoperData = NULL;

		//检查完成端口请求队列,是否有网络请求到来
		//下函数从完成端口取出一个成功I/O操作的完成包,返回值为非0
		bRet = GetQueuedCompletionStatus(
			g_hComPort,
			&dwIoSize,
			(LPDWORD)&pComKey,
			(LPOVERLAPPED*)&lpIOoperData,
			INFINITE
		);

		// 判断是否出现了错误  
		if (bRet == FALSE)
		{
			if (NULL == lpIOoperData) //函数则不会在lpNumberOfBytes and lpCompletionKey所指向的参数中存储信息 
			{
				continue;
			}
			else
			{   //当lpIOoperData !=NULL 不为空并且函数从完成端口出列一个失败I/O操作的完成包,返回值为0。
				//函数在指向lpNumberOfBytes, lpCompletionKey,  lpOverlapped的参数指针中存储相关信息 

				DWORD dwErr = GetLastError();
				if (pComKey == NULL)
				{
					printf("此时链接的socket为空\n");
					continue;
				}
				else
				{
					//链接超时
					if (WAIT_TIMEOUT == dwErr)
					{
						// 确认客户端是否还活着...因为如果客户端网络异常断开(例如客户端崩溃或者拔掉网线等)的时候,服务器端是无法收到客户端断开的通知的  
						if (!IsSocketAlive(pComKey->sock))
						{
							//若该socket已经失效				
							printf("一个客户端的socket已经异常断开(非正常结束)\n");
							CancelIo((HANDLE)pComKey->sock);
							closesocket(pComKey->sock); pComKey->sock = NULL;//关闭这个客户所占用的socket
							GlobalFree(pComKey); pComKey = NULL;//该函数是释放指定的全局内存块
							GlobalFree(lpIOoperData); lpIOoperData = NULL;
							//_DelToContextList(pComKey);//删掉链接列表中指向这个客户端的指针
							continue;
						}
						else
						{
							continue;
						}
					}

					//未知错误
					else
					{
						printf("完成端口遇到未知错误 :%d!", dwErr);

						CancelIo((HANDLE)pComKey->sock);
						closesocket(pComKey->sock); pComKey->sock = NULL;//关闭这个客户所占用的socket
						GlobalFree(pComKey); pComKey = NULL;//该函数是释放指定的全局内存块
						GlobalFree(lpIOoperData); lpIOoperData = NULL;
						//_DelToContextList(pComKey);//删掉链接列表中指向这个客户端的指针
						continue;
					}
				}
			}
		}


		//从完成端口取出一个成功I O操作的完成包
		else
		{
			// 判断是否有客户端断开了
			if (0 == dwIoSize && (READ == lpIOoperData->type || WRITE == lpIOoperData->type))
			{

				printf("[端口:%s] 客户自己断开了连接!", pComKey->sIP);

				//当关闭套接字时,如果此时系统还有未完成的异步操作,
				//调用CancelIo函数取消等待执行的异步操作,如果函数调用成功,返回TRUE,所有在此套接字上等待的异步操作都被成功的取消。
				CancelIo((HANDLE)pComKey->sock);
				closesocket(pComKey->sock); pComKey->sock = INVALID_SOCKET;//关闭这个客户所占用的socket
				GlobalFree(pComKey); //该函数是释放指定的全局内存块
				GlobalFree(lpIOoperData);
				printf(" 开辟的内存已经释放\n");

				_DelToContextList(pComKey);//删掉链接列表中指向这个客户端完成键的指针
				_DelToIOtList(lpIOoperData);

				RemoveTheLast();
				continue;
			}

			//正常接收到客户端发的包,处理IO端口的请求
			else
			{
				ProcessIO(lpIOoperData, pComKey);
			}
		}

	}

	return 0;
}

BOOL ProcessIO(IO_OPERATION_DATA *pIOoperData, COMPLETION_KEY *pComKey)
{
	//1.服务器要从1号客户端收PIN(系统已经执行过数据处理了)
	if (pIOoperData->type == READ)
	{
		ZeroMemory(pComKey->MsgBuff, sizeof(pComKey->MsgBuff));
		strcpy(pComKey->MsgBuff, pIOoperData->databuf.buf);//将要发送的数据复制到1号客户端的pComKey->MsgBuff中


		vector<LP_COMPLETION_KEY>::iterator  it2;
		for (it2 = m_arrayClientFlag.begin(); it2 != m_arrayClientFlag.end();)
		{

			//判断是否找到指向了等待PIN码的2号客户端
			if (0 != strcmp(pComKey->sIP, (*it2)->sIP))
			{
				//找到后,向2号套接字上投递1号套接字上的数据PIN
				SendFunc1((*it2), pIOoperData, pComKey->MsgBuff);

				break;
			}
			it2++;
		}


	}

	//2。服务器要发给2号客户端(系统已经执行过数据处理了)
	else if (pIOoperData->type == WRITE)
	{
		ZeroMemory(pComKey->MsgBuff, sizeof(pComKey->MsgBuff));
		strcpy(pComKey->MsgBuff, pIOoperData->databuf.buf);//将从服务器得到的数据包复制到2号的pComKey->MsgBuff中,继续发送给2号客户端
														   //cout << pComKey->clientID << "从中转站收到" << pComKey->MsgBuff << endl;

		EnterCriticalSection(&m_thread);
		vector<LP_COMPLETION_KEY>::iterator  it1;
		for (it1 = m_arrayClientFlag.begin(); it1 != m_arrayClientFlag.end();)
		{

			//判断是否找到指向了发送PIN码的客户端
			if (0 != strcmp(pComKey->sIP, (*it1)->sIP))
			{
				RecvFunc((*it1), pIOoperData);
				break;
			}
			it1++;
		}
		LeaveCriticalSection(&m_thread);

	}

	//3.客户端要接入
	else if (pIOoperData->type == ACCEPT)
	{
		//accept 创建的 socket 会自动继承监听 socket 的属性, AcceptEx 却不会. 
		//因此如果有必要, 在 AcceptEx 成功接受了一个客户端的连接之后, 我们必须调用:
		//设置socket的一些属性比如超时等,不调用setsockopt,也不会有什么问题
		setsockopt(
			pIOoperData->sock,//将要被设置或者获取选项的套接字。
			SOL_SOCKET, //选项所在的协议层(此处是套接字层)
			SO_UPDATE_ACCEPT_CONTEXT,//需要访问的选项名
			(char*)&(pComKey->sock),
			sizeof(pComKey->sock)
		);

		//为新接入的客户新建一个完成键结构,来存放此时接入的客户端socket信息,之前的完成键是属于完成端口的。
		LP_COMPLETION_KEY pClientComKey = (LP_COMPLETION_KEY)GlobalAlloc(GPTR, sizeof(COMPLETION_KEY));
		pClientComKey->sock = pIOoperData->sock;

		//定义客户端地址,服务器本机地址(备用)
		SOCKADDR_IN *addrClient = NULL, *addrLocal = NULL;
		int nClientLen = sizeof(SOCKADDR_IN), nLocalLen = sizeof(SOCKADDR_IN);

		//使用GetAcceptExSockaddrs函数 获得具体的各个地址参数.(这函数没有返回值)  
		lpfnGetAcceptExSockaddrs(
			pIOoperData->buffer,//指向传递给AcceptEx函数接收客户第一块数据的缓冲区
			0,//lpoutputBuffer缓冲区的大小,必须和传递给AccpetEx函数的一致
			sizeof(SOCKADDR_IN) + 16,//为本地地址预留的空间大小,必须和传递给AccpetEx函数一致
			sizeof(SOCKADDR_IN) + 16,//为远程地址预留的空间大小,必须和传递给AccpetEx函数一致
			(LPSOCKADDR*)&addrLocal,//用来返回连接的本地地址
			&nLocalLen,//用来返回本地地址的长度
			(LPSOCKADDR*)&addrClient,//用来返回远程地址
			&nClientLen//用来返回远程地址的长度
		);
		ZeroMemory(pClientComKey->sIP, 100);
		sprintf(pClientComKey->sIP, "%s+%d", inet_ntoa(addrClient->sin_addr), addrClient->sin_port); //cliAdd.sin_port ; 
		printf("客户端接入:[%s]\n", pClientComKey->sIP);

		pClientComKey->first = TRUE;//此时标记为true(表明接下来收的是客户端发的第一条消息)
		int MyNum = InterlockedIncrement(&IDnum);
		pClientComKey->clientID = MyNum;//为新连入的客户端分配完成键序号
		pIOoperData->IOnum = MyNum;//为新连入的客户端分配IO数据结构体序号

		//将新连入的socket绑定到完成端口  
		CreateIoCompletionPort((HANDLE)pClientComKey->sock, g_hComPort, (DWORD)pClientComKey, 0);  //将监听到的套接字关联到完成端口
		
        //将新接入客户端对应开辟好的完成键、IO结构的指针存放至列表																						   //并将该新连入的客户端相关信息存到链接列表数组中
		_AddToContextList(pClientComKey, pIOoperData);

		//当新客户连入服务器后,就开始根据自己的程序逻辑进行投递接收请求RecvFunc,或者投递发送请求SendFunc																					   //在新连入的socket投递第一个WSARecv请求
		if (1 == pClientComKey->clientID)
		{
			RecvFunc(pClientComKey, pIOoperData);
		}
		else if (2 == pClientComKey->clientID)
		{
			EnterCriticalSection(&m_thread);
			vector<LP_COMPLETION_KEY>::iterator it1;
			for (it1 = m_arrayClientFlag.begin(); it1 != m_arrayClientFlag.end();)
			{
				//判断是否找到指向了等待PIN码的客户端1,并且1还未断开
				if (((*it1)->sock != pClientComKey->sock) && ((*it1)->sock != NULL))
				{
					//找到后,向2号套接字上投递1号套接字上的数据PIN
					SendFunc(pClientComKey, pIOoperData, (*it1)->MsgBuff);
				}
				it1++;
			}
			LeaveCriticalSection(&m_thread);
		}

		AcceptClient(g_sListen);
	}
	return TRUE;
}

BOOL AcceptClient(SOCKET sListen)
{
	DWORD dwBytes;
	LP_IO_OPERATION_DATA pIO;

	//为单IO开辟内存,
	pIO = (LP_IO_OPERATION_DATA)GlobalAlloc(GPTR, sizeof(IO_OPERATION_DATA));
	pIO->databuf.buf = pIO->buffer;
	pIO->databuf.len = pIO->len = DATA_BUFSIZE;
	pIO->type = ACCEPT;

	//先创建一个套接字(相比accept有点就在此,accept是接收到连接才创建出来套接字,浪费时间. 这里先准备一个,用于接收连接)  
	pIO->sock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
	if (INVALID_SOCKET == pIO->sock)
	{
		printf("创建用于AcceptEX的Socket失败!错误代码: %d", WSAGetLastError());
		return false;
	}

	//调用AcceptEx函数,地址长度需要在原有的上面加上16个字节  
	//向服务线程投递一个接收连接的的请求  
	BOOL rc = lpfnAcceptEx(
		sListen,//一参本地监听Socket 
		pIO->sock,//二参为即将到来的客人准备好的Socket 
		pIO->buffer,// 三参接收缓冲区: 存客人发来的第一份数据、存Client远端地址地址包括IP和端口,
		0,			//四参定三参数据区长度,0表只连不接收、连接到来->请求完成,否则连接到来+任意长数据到来->请求完成 
		sizeof(SOCKADDR_IN) + 16,//
		sizeof(SOCKADDR_IN) + 16,
		&dwBytes,
		&(pIO->overlapped)
	);

	if (FALSE == rc)
	{
		if (WSAGetLastError() != ERROR_IO_PENDING)
		{
			printf("投递 AcceptEx 请求失败,错误代码%d", WSAGetLastError());
			return false;
		}
	}

	return true;
}


BOOL RecvFunc(COMPLETION_KEY *pComKey, IO_OPERATION_DATA *pIOoperData)
{
	DWORD flags = 0;
	DWORD recvBytes = 0;
	ZeroMemory(&pIOoperData->overlapped, sizeof(OVERLAPPED));

	pIOoperData->type = READ;

	pIOoperData->databuf.buf = pIOoperData->buffer;
	pIOoperData->databuf.len = pIOoperData->len = DATA_BUFSIZE;
	ZeroMemory(pIOoperData->buffer, sizeof(pIOoperData->buffer));

	if (SOCKET_ERROR == WSARecv(pComKey->sock, &pIOoperData->databuf, 1, &recvBytes, &flags, &pIOoperData->overlapped, NULL))
	{
		if (ERROR_IO_PENDING != WSAGetLastError())
		{
			printf("向1号客户端投递重叠接收失败!   %d\n", GetLastError());
			return FALSE;
		}
	}
	return TRUE;
}


BOOL SendFunc(COMPLETION_KEY *pComKey, IO_OPERATION_DATA *pIOoperData, char* msgBuff)
{
	DWORD flags = 0;
	DWORD SendBytes = 0;
	ZeroMemory(&pIOoperData->overlapped, sizeof(OVERLAPPED));
	pIOoperData->type = WRITE;


	strcpy(pIOoperData->databuf.buf, msgBuff);
	pIOoperData->databuf.len = 100;

	if (SOCKET_ERROR == WSASend(pComKey->sock, &pIOoperData->databuf, 1, &SendBytes, flags, &pIOoperData->overlapped, NULL))
	{
		if (ERROR_IO_PENDING != WSAGetLastError())
		{
			printf("2号客户端自己投递发送重叠接收失败! 错误码%d\n", GetLastError());
			return FALSE;
		}
	}

	return TRUE;


}

BOOL SendFunc1(COMPLETION_KEY *pComKey, IO_OPERATION_DATA *pIOoperData, char* msgBuff)
{
	DWORD flags = 0;
	DWORD SendBytes = 0;
	ZeroMemory(&pIOoperData->overlapped, sizeof(OVERLAPPED));
	pIOoperData->type = WRITE;
	pIOoperData->sock = pComKey->sock;

	strcpy(pIOoperData->databuf.buf, msgBuff);
	pIOoperData->databuf.len = 100;

	if (SOCKET_ERROR == WSASend(pComKey->sock, &pIOoperData->databuf, 1, &SendBytes, flags, &pIOoperData->overlapped, NULL))
	{
		if (ERROR_IO_PENDING != WSAGetLastError())
		{
			printf("1号客户端投递发送重叠接收失败! 错误码%d\n", GetLastError());
			return FALSE;
		}
	}

	return TRUE;


}

//////////////////////////////////////////////////////////////
// 将客户端的相关信息指针(完成键、IO结构体)存储到连接列表数组,方便后来的查找,端对端通信
void _AddToContextList(LP_COMPLETION_KEY pHandleData, LP_IO_OPERATION_DATA pHandleIO)
{
	EnterCriticalSection(&m_thread);

	m_arrayClientFlag.push_back(pHandleData);
	m_arrayIOFlag.push_back(pHandleIO);

	LeaveCriticalSection(&m_thread);
}


//////////////////////////////////////////////////////////////
// 有客户端退出时,列表数组某个客户端的相关信息指针删掉,
void _DelToContextList(LP_COMPLETION_KEY pHandleData)
{
	//vetor类型 http://blog.csdn.net/duan19920101/article/details/50717748

	EnterCriticalSection(&m_thread);

	vector<LP_COMPLETION_KEY>::iterator it;
	for (it = m_arrayClientFlag.begin(); it != m_arrayClientFlag.end();)
	{
		//判断是否找到指向了那个退出客户端的指针
		if ((*it)->clientID== pHandleData->clientID)
		{
			it = m_arrayClientFlag.erase(it);
			printf("<=列表中客户端完成键信息已经被清除\n");
		}
		else
		{
			it++;
		}
	}

	LeaveCriticalSection(&m_thread);
}

void _DelToIOtList(LP_IO_OPERATION_DATA pHandleIO)
{
	//vetor类型 http://blog.csdn.net/duan19920101/article/details/50717748

	EnterCriticalSection(&m_thread);

	vector<LP_IO_OPERATION_DATA>::iterator it;
	for (it = m_arrayIOFlag.begin(); it != m_arrayIOFlag.end();)
	{
		//判断是否找到指向了那个退出客户端的指针
		if ((*it)->IOnum == pHandleIO->IOnum)
		{
			it = m_arrayIOFlag.erase(it);
			printf("<=列表中客户端单IO结构信息已经被清除\n");
		}
		else
		{
			it++;
		}
	}

	LeaveCriticalSection(&m_thread);
}

void RemoveTheLast() {

	EnterCriticalSection(&m_thread);
	vector<LP_COMPLETION_KEY>::iterator it1;
	for (it1 = m_arrayClientFlag.begin(); it1 != m_arrayClientFlag.end();)
	{
		//判断是否找到指向了那个退出客户端的指针
		printf("[端口:%s] 客户被服务器断开了连接!", (*it1)->sIP);

		//当关闭套接字时,如果此时系统还有未完成的异步操作,
		//调用CancelIo函数取消等待执行的异步操作,如果函数调用成功,返回TRUE,所有在此套接字上等待的异步操作都被成功的取消。
		CancelIo((HANDLE)(*it1)->sock);
		closesocket((*it1)->sock); (*it1)->sock = INVALID_SOCKET;//关闭这个客户所占用的socket
		GlobalFree((*it1)); //该函数是释放指定的全局内存块
		it1 = m_arrayClientFlag.erase(it1);
		printf("<=列表中客户端完成键信息已经被清除\n");
		
	}

	vector<LP_IO_OPERATION_DATA>::iterator it;
	for (it = m_arrayIOFlag.begin(); it != m_arrayIOFlag.end();)
	{
		//判断是否找到指向了那个退出客户端的指针
		GlobalFree(*it);
		it = m_arrayIOFlag.erase(it);
		printf("<=列表中客户端单IO结构信息已经被清除\n");
		
	}

	LeaveCriticalSection(&m_thread);

}

bool IsSocketAlive(SOCKET s)
{
	int nByteSent = send(s, "", 0, 0);
	if (-1 == nByteSent)
		return false;
	return true;
}


展开阅读全文

没有更多推荐了,返回首页