天天看點

Overlapped I/O 重疊I/O模型Client——》Server

///////////////////////////////////////////////////////

// OverlappedIO.cpp檔案

#include <winsock2.h>

#pragma comment(lib, "WS2_32") // 連結到WS2_32.lib

class CInitSock

{

public:

CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)

// 初始化WS2_32.dll

WSADATA wsaData;

WORD sockVersion = MAKEWORD(minorVer, majorVer);

if(::WSAStartup(sockVersion, &wsaData) != 0)

exit(0);

}

~CInitSock()

::WSACleanup();

};

#include <Mswsock.h>

#include <stdio.h>

#include <windows.h>

CInitSock theSock;

#define BUFFER_SIZE 1024

typedef struct _SOCKET_OBJ//自定義套接字對象

SOCKET s; // 套接字句柄

int nOutstandingOps; // 記錄此套接字上的重疊I/O數量

LPFN_ACCEPTEX lpfnAcceptEx; // 擴充函數AcceptEx的指針(僅對監聽套接字而言)

} SOCKET_OBJ, *PSOCKET_OBJ;

typedef struct _BUFFER_OBJ//自定義緩沖區對象

OVERLAPPED ol; // 重疊結構

char *buff; // send/recv/AcceptEx所使用的緩沖區

int nLen; // buff的長度

PSOCKET_OBJ pSocket; // 此I/O所屬的套接字對象

int nOperation; // 送出的操作類型

#define OP_ACCEPT 1

#define OP_READ 2

#define OP_WRITE 3

SOCKET sAccept; // 用來儲存AcceptEx接受的客戶套接字(僅對監聽套接字而言)

_BUFFER_OBJ *pNext;

} BUFFER_OBJ, *PBUFFER_OBJ;

HANDLE g_events[WSA_MAXIMUM_WAIT_EVENTS]; // I/O事件句柄數組

int g_nBufferCount; // 上數組中有效句柄數量

PBUFFER_OBJ g_pBufferHead, g_pBufferTail; // 記錄緩沖區對象組成的表的位址

// 申請套接字對象和釋放套接字對象的函數

PSOCKET_OBJ GetSocketObj(SOCKET s)

PSOCKET_OBJ pSocket = (PSOCKET_OBJ)::GlobalAlloc(GPTR, sizeof(SOCKET_OBJ));

if(pSocket != NULL)

pSocket->s = s;

return pSocket;

void FreeSocketObj(PSOCKET_OBJ pSocket)

if(pSocket->s != INVALID_SOCKET)

::closesocket(pSocket->s);

::GlobalFree(pSocket);

PBUFFER_OBJ GetBufferObj(PSOCKET_OBJ pSocket, ULONG nLen)

if(g_nBufferCount > WSA_MAXIMUM_WAIT_EVENTS - 1)

return NULL;

PBUFFER_OBJ pBuffer = (PBUFFER_OBJ)::GlobalAlloc(GPTR, sizeof(BUFFER_OBJ));

if(pBuffer != NULL)

pBuffer->buff = (char*)::GlobalAlloc(GPTR, nLen);

pBuffer->ol.hEvent = ::WSACreateEvent();

pBuffer->pSocket = pSocket;

pBuffer->sAccept = INVALID_SOCKET;

// 将新的BUFFER_OBJ添加到清單中

if(g_pBufferHead == NULL)

g_pBufferHead = g_pBufferTail = pBuffer;

else

g_pBufferTail->pNext = pBuffer;

g_pBufferTail = pBuffer;

g_events[++ g_nBufferCount] = pBuffer->ol.hEvent;

return pBuffer;

void FreeBufferObj(PBUFFER_OBJ pBuffer)

// 從清單中移除BUFFER_OBJ對象

PBUFFER_OBJ pTest = g_pBufferHead;

BOOL bFind = FALSE;

if(pTest == pBuffer)

g_pBufferHead = g_pBufferTail = NULL;

bFind = TRUE;

while(pTest != NULL && pTest->pNext != pBuffer)

pTest = pTest->pNext;

if(pTest != NULL)

pTest->pNext = pBuffer->pNext;

if(pTest->pNext == NULL)

g_pBufferTail = pTest;

// 釋放它占用的記憶體空間

if(bFind)

g_nBufferCount --;

::CloseHandle(pBuffer->ol.hEvent);

::GlobalFree(pBuffer->buff);

::GlobalFree(pBuffer);

PBUFFER_OBJ FindBufferObj(HANDLE hEvent)

PBUFFER_OBJ pBuffer = g_pBufferHead;

while(pBuffer != NULL)

if(pBuffer->ol.hEvent == hEvent)

break;

pBuffer = pBuffer->pNext;

void RebuildArray()

int i = 1;

g_events[i++] = pBuffer->ol.hEvent;

BOOL PostAccept(PBUFFER_OBJ pBuffer)

PSOCKET_OBJ pSocket = pBuffer->pSocket;

if(pSocket->lpfnAcceptEx != NULL)

// 設定I/O類型,增加套接字上的重疊I/O計數

pBuffer->nOperation = OP_ACCEPT;

pSocket->nOutstandingOps ++;

// 投遞此重疊I/O

DWORD dwBytes;

pBuffer->sAccept =

::WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

BOOL b = pSocket->lpfnAcceptEx(pSocket->s,

pBuffer->sAccept,

pBuffer->buff,

BUFFER_SIZE - ((sizeof(sockaddr_in) + 16) * 2),

sizeof(sockaddr_in) + 16,

&dwBytes,

&pBuffer->ol);

if(!b)

if(::WSAGetLastError() != WSA_IO_PENDING)

return FALSE;

return TRUE;

BOOL PostRecv(PBUFFER_OBJ pBuffer)

pBuffer->nOperation = OP_READ;

pBuffer->pSocket->nOutstandingOps ++;

DWORD dwFlags = 0;

WSABUF buf;

buf.buf = pBuffer->buff;

buf.len = pBuffer->nLen;

if(::WSARecv(pBuffer->pSocket->s, &buf, 1, &dwBytes, &dwFlags, &pBuffer->ol, NULL) != NO_ERROR)

BOOL PostSend(PBUFFER_OBJ pBuffer)

pBuffer->nOperation = OP_WRITE;

if(::WSASend(pBuffer->pSocket->s,

&buf, 1, &dwBytes, dwFlags, &pBuffer->ol, NULL) != NO_ERROR)

BOOL HandleIO(PBUFFER_OBJ pBuffer)

PSOCKET_OBJ pSocket = pBuffer->pSocket; // 從BUFFER_OBJ對象中提取SOCKET_OBJ對象指針,為的是友善引用

pSocket->nOutstandingOps --;

// 擷取重疊操作結果

DWORD dwTrans;

DWORD dwFlags;

BOOL bRet = ::WSAGetOverlappedResult(pSocket->s, &pBuffer->ol, &dwTrans, FALSE, &dwFlags);

if(!bRet)

// 在此套接字上有錯誤發生,是以,關閉套接字,移除此緩沖區對象。

// 如果沒有其它抛出的I/O請求了,釋放此緩沖區對象,否則,等待此套接字上的其它I/O也完成

pSocket->s = INVALID_SOCKET;

if(pSocket->nOutstandingOps == 0)

FreeSocketObj(pSocket);

FreeBufferObj(pBuffer);

// 沒有錯誤發生,處理已完成的I/O

switch(pBuffer->nOperation)

case OP_ACCEPT: // 接收到一個新的連接配接,并接收到了對方發來的第一個封包

// 為新客戶建立一個SOCKET_OBJ對象

PSOCKET_OBJ pClient = GetSocketObj(pBuffer->sAccept);

// 為發送資料建立一個BUFFER_OBJ對象,這個對象會在套接字出錯或者關閉時釋放

PBUFFER_OBJ pSend = GetBufferObj(pClient, BUFFER_SIZE);

if(pSend == NULL)

printf(" Too much connections! /n");

FreeSocketObj(pClient);

RebuildArray();

//while(dwTrans > 0 && pBuffer->nOperation==OP_READ){

// 将資料複制到發送緩沖區

pSend->nLen = dwTrans;

memcpy(pSend->buff, pBuffer->buff, dwTrans);

printf("data from Client to Server:%s/n",pSend->buff);

//}

// 投遞此發送I/O(将資料回顯給客戶)

if(!PostSend(pSend))

// 萬一出錯的話,釋放上面剛申請的兩個對象

FreeBufferObj(pSend);

// 繼續投遞接受I/O

PostAccept(pBuffer);

case OP_READ: // 接收資料完成

if(dwTrans > 0)

// 建立一個緩沖區,以發送資料。這裡就使用原來的緩沖區

PBUFFER_OBJ pSend = pBuffer;

// 投遞發送I/O(将資料回顯給客戶)

PostSend(pSend);

else // 套接字關閉

// 必須先關閉套接字,以便在此套接字上投遞的其它I/O也傳回

case OP_WRITE: // 發送資料完成

// 繼續使用這個緩沖區投遞接收資料的請求

pBuffer->nLen = BUFFER_SIZE;

PostRecv(pBuffer);

// 同樣,要先關閉套接字

void main1();

int main2();

void main()

printf("Please select a item:/n");

printf("s:Server/n");

printf("c:Client/n");

char a;

while(1){

scanf("%c",&a);

if(a=='s'){main1();break;}

else if(a=='c'){main2();break;}

else continue;

void main1()

// 建立監聽套接字,綁定到本地端口,進入監聽模式

int nPort = 4567;

SOCKET sListen =

::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

SOCKADDR_IN si;

si.sin_family = AF_INET;

si.sin_port = ::ntohs(nPort);

si.sin_addr.S_un.S_addr = INADDR_ANY;

::bind(sListen, (sockaddr*)&si, sizeof(si));

::listen(sListen, 200);

// 為監聽套接字建立一個SOCKET_OBJ對象

PSOCKET_OBJ pListen = GetSocketObj(sListen);

// 加載擴充函數AcceptEx

GUID GuidAcceptEx = WSAID_ACCEPTEX;

WSAIoctl(pListen->s,

SIO_GET_EXTENSION_FUNCTION_POINTER,

&GuidAcceptEx,

sizeof(GuidAcceptEx),

&pListen->lpfnAcceptEx,

sizeof(pListen->lpfnAcceptEx),

NULL,

NULL);

// 建立用來重建立立g_events數組的事件對象

g_events[0] = ::WSACreateEvent();

// 在此可以投遞多個接受I/O請求

for(int i=0; i<5; i++)

PostAccept(GetBufferObj(pListen, BUFFER_SIZE));

::WSASetEvent(g_events[0]);

while(TRUE)

int nIndex =

::WSAWaitForMultipleEvents(g_nBufferCount + 1, g_events, FALSE, WSA_INFINITE, FALSE);

if(nIndex == WSA_WAIT_FAILED)

printf("WSAWaitForMultipleEvents() failed /n");

nIndex = nIndex - WSA_WAIT_EVENT_0;

for(int i=0; i<=nIndex; i++)

int nRet = ::WSAWaitForMultipleEvents(1, &g_events[i], TRUE, 0, FALSE);

if(nRet == WSA_WAIT_TIMEOUT)

continue;

::WSAResetEvent(g_events[i]);

// 重建立立g_events數組

if(i == 0)

// 處理這個I/O

PBUFFER_OBJ pBuffer = FindBufferObj(g_events[i]);

if(!HandleIO(pBuffer))

//Client

int main2()

// 建立套接字

SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

if(s == INVALID_SOCKET)

printf(" Failed socket() /n");

return 0;

// 也可以在這裡調用bind函數綁定一個本地位址

// 否則系統将會自動安排

// 填寫遠端位址資訊

sockaddr_in servAddr;

servAddr.sin_family = AF_INET;

servAddr.sin_port = htons(4567);

// 注意,這裡要填寫伺服器程式(TCPServer程式)所在機器的IP位址

// 如果你的計算機沒有聯網,直接使用127.0.0.1即可

servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

if(::connect(s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1)

printf(" Failed connect() /n");

char szText[256];

while(TRUE){

scanf("%s",szText);

//puts(szText);

/// 向伺服器端發送資料

::send(s, szText, strlen(szText), 0);

// 關閉套接字

::closesocket(s);

繼續閱讀