///////////////////////////////////////////////////////
// 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);