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

时间:2022-05-24 18:19:00

主要参考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_CONCURRENT2000// 并发线程最大数
#define IP"192.168.1.185"// 服务器端IP地址
#define PORT123456// 服务器端口号

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;
}