主要參考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;
}