1. 程式人生 > >windows Socket程式設計之重疊IO模型

windows Socket程式設計之重疊IO模型

上一篇文章我們講了EventSelect網路模型,它已經解決了等待資料到來的這一大部分時間,但是它還有一小部分時間沒有節省下來。那就是把資料從網絡卡的緩衝區拷貝到我們應用程式的緩衝區裡邊。而這一篇的重疊IO模型就是將這一小部分的時間也給節省了下來。

首先,我們在主執行緒裡邊初始化網路環境,然後建立監聽的socket,接下來,執行繫結,監聽的操作,然後,建立一個工作者執行緒來對客戶進行服務。執行以上操作之後呢,是一個死迴圈。在這個迴圈裡邊,我們首先呼叫accept函式來對一個客戶進行連線操作。然後將該函式返回的客戶端的socket儲存到我們定義的一個全域性socket數組裡邊進去。然後對我們自定義的結構體單IO操作分配一個空間,其宣告如下:

typedef struct
{
   WSAOVERLAPPED overlap;<span style="white-space:pre">	</span>//OVERLAPPED結構,該結構裡邊有一個event事件物件
   WSABUF Buffer;<span style="white-space:pre">		</span>//WSABUF結構,裡邊有一個buf的大小,還有一個指標指向buf
   char szMessage[MSGSIZE];<span style="white-space:pre">	</span>//訊息資料
   DWORD NumberOfBytesRecvd;<span style="white-space:pre">	</span>//儲存接收到的位元組數
   DWORD Flags;<span style="white-space:pre">			</span>//標誌位
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;
注意,該結構體是我們自己定義的,其中第一個引數overlapped結構體是最重要的,必須把它放在第一個宣告處。因為我們要的是overlapped的地址,而其它的則是我們自己進行擴充套件的,大家也可以進行自己的擴充套件。那什麼叫做單IO操作呢,比如說你請了一個人來專門打理你的店鋪,你每天呢都會發一個資訊給這個人,資訊的內容就是叫他每天干一些指定的的事情,而這個資訊就是我們這個單IO操作這個資料結構所指定的內容。

我們在堆上給我們的單IO結構分配完空間之後呢,我們來對它進行初始化,我們把Buffer裡面的指標指向szMessage,把裡面的大小指定為szMessage的大小。然後,呼叫WSACreateEvent建立一個事件物件,將這個事件物件賦予給overlappped裡邊的事件物件。

最後,在迴圈的末尾我們呼叫WSARecv,來對資料進行接收,其宣告如下:

int WSARecv(  
  SOCKET s,                                               <span style="white-space:pre">	</span>//客戶連線的socket
  LPWSABUF lpBuffers,                                     <span style="white-space:pre">	</span>//WSABUFFER指標
  DWORD dwBufferCount,                                    <span style="white-space:pre">	</span>//Buffer的個數,一般這裡給個1
  LPDWORD lpNumberOfBytesRecvd,                           <span style="white-space:pre">	</span>//接收的位元組數
  LPDWORD lpFlags,                                        <span style="white-space:pre">	</span>//標誌位
  LPWSAOVERLAPPED lpOverlapped,                           <span style="white-space:pre">	</span>//overlaopped結構地址
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine  <span style="white-space:pre">	</span>//沒啥用,為NULL
);
注意,該函式是非同步的,也就是說它呼叫完就直接進行返回了,不用等待。所以整個迴圈,只有剛開始接收到一個客戶的連線之後,我們就暢通無阻的往下執行一遍,然後再回到迴圈的開始繼續等待客戶的連線。接下來,看下工作者執行緒。

工作者執行緒,也是一個死迴圈,它和我們的EventSelect一樣,剛開始也是呼叫了WSAWaitForMultipleEvents,來監控我們的socket陣列哪一個有訊號了,由於這個函式最多隻能監控64個socket,所以我們的服務端只能同時進行64個客戶的資料收發。呼叫完該函式之後,它會返回一個索引值,我們將呼叫WSAResetEvent,將我們全域性event數組裡邊那個有訊號的手動重置為無訊號狀態。因為我們用WSACreateEvent建立的事件物件是以手動重置的方式建立的。如果,不重置成無訊號的狀態,那麼就像上面我們舉得那個例子一樣,我們請的那個人,他第二天檢視資訊的時候,還會繼續的執行昨天的工作。

接下來,是我們重疊IO模型裡邊和EventSelect裡邊最大的不同點,我們會呼叫WSAGetOverlappedResult,來判斷重疊IO呼叫是否成功,其宣告如下:

BOOL WSAGetOverlappedResult(  
  SOCKET s,                      //有訊號的那個socket
  LPWSAOVERLAPPED lpOverlapped,  //overlapped結構地址
  LPDWORD lpcbTransfer,          //接收的位元組數
  BOOL fWait,                    //TRUE表示操作完成就返回
  LPDWORD lpdwFlags              //標誌位
);
這個函式的第三個引數和我們的WSARecv的第四個引數是一樣的,作業系統會改寫這個值,若該值為0表示客戶端斷開連線或該資料傳輸失敗了。如果沒有失敗,我們就將資料儲存到我們那個單IO結構裡邊的szMessage數組裡邊。然後再次呼叫WSARecv,告訴作業系統繼續幫我們監控這個socket。
以下是重疊IO的例項程式碼:
#include <winsock2.h>
#include <stdio.h>
#define PORT 6000
#define MSGSIZE 1024
#pragma comment (lib, "Ws2_32.lib")
BOOL WinSockInit()
{
	WSADATA data = {0};
	if(WSAStartup(MAKEWORD(2, 2), &data))
		return FALSE;
	if ( LOBYTE(data.wVersion) !=2 || HIBYTE(data.wVersion) != 2 ){
		WSACleanup();
		return FALSE;
	}
	return TRUE;
}

typedef struct
{
   WSAOVERLAPPED overlap;
   WSABUF Buffer;
   char szMessage[MSGSIZE];
   DWORD NumberOfBytesRecvd;
   DWORD Flags;
}PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

int g_iTotalConn = 0;
SOCKET g_CliSocketArr[MAXIMUM_WAIT_OBJECTS];
WSAEVENT g_CliEventArr[MAXIMUM_WAIT_OBJECTS];
LPPER_IO_OPERATION_DATA g_pPerIODataArr[MAXIMUM_WAIT_OBJECTS];


DWORD WINAPI WorkerThread(LPVOID);
void Cleanup(int);

int main()
{
	SOCKET sListen, sClient;
	SOCKADDR_IN local, client;
	DWORD dwThreadId;
	int iaddrSize = sizeof(SOCKADDR_IN);
	// 初始化環境
	WinSockInit();
	// 建立監聽socket
	sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	// 繫結
	local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	local.sin_family = AF_INET;
	local.sin_port = htons(PORT);
	bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));
	// 監聽
	listen(sListen, 3);
	// 建立工作者執行緒
	CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);
	while (TRUE)
	{
	    // 接受連線
	    sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);
	    printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));
	    g_CliSocketArr[g_iTotalConn] = sClient;

	    // 分配一個單io操作資料結構
	    g_pPerIODataArr[g_iTotalConn] = (LPPER_IO_OPERATION_DATA)HeapAlloc(
	    GetProcessHeap(),
	    HEAP_ZERO_MEMORY,
	    sizeof(PER_IO_OPERATION_DATA));
	    //初始化單io結構
	    g_pPerIODataArr[g_iTotalConn]->Buffer.len = MSGSIZE;
	    g_pPerIODataArr[g_iTotalConn]->Buffer.buf = g_pPerIODataArr[g_iTotalConn]->szMessage;
	    g_CliEventArr[g_iTotalConn] = g_pPerIODataArr[g_iTotalConn]->overlap.hEvent = WSACreateEvent();
	    // 開始一個非同步操作
	    WSARecv(
	        g_CliSocketArr[g_iTotalConn],
	        &g_pPerIODataArr[g_iTotalConn]->Buffer,
	        1,
	        &g_pPerIODataArr[g_iTotalConn]->NumberOfBytesRecvd,
	        &g_pPerIODataArr[g_iTotalConn]->Flags,&g_pPerIODataArr[g_iTotalConn]->overlap,
	        NULL);
	    g_iTotalConn++;
	}

	closesocket(sListen);
	WSACleanup();
	return 0;
}
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
	int ret, index;
	DWORD cbTransferred;
	while (TRUE)
	{
		//判斷是否有訊號
		ret = WSAWaitForMultipleEvents(g_iTotalConn, g_CliEventArr, FALSE, 1000, FALSE);
		if (ret == WSA_WAIT_FAILED || ret == WSA_WAIT_TIMEOUT)
			continue;
		index = ret - WSA_WAIT_EVENT_0;
		//手動設定為無訊號
		WSAResetEvent(g_CliEventArr[index]);
		//判斷該重疊呼叫到底是成功,還是失敗
		WSAGetOverlappedResult(
		g_CliSocketArr[index],
		&g_pPerIODataArr[index]->overlap,
		&cbTransferred,
		TRUE,
		&g_pPerIODataArr[g_iTotalConn]->Flags);
		//若呼叫失敗
		if (cbTransferred == 0)
			Cleanup(index);//關閉客戶端連線
		else
		{
			//將資料儲存到szMessage裡邊			
			g_pPerIODataArr[index]->szMessage[cbTransferred] = '\0';
			//這裡直接就轉發回去了
			send(g_CliSocketArr[index], g_pPerIODataArr[index]->szMessage,cbTransferred, 0);
			// 進行另一個非同步操作
			WSARecv(g_CliSocketArr[index],
			&g_pPerIODataArr[index]->Buffer,
			1,
			&g_pPerIODataArr[index]->NumberOfBytesRecvd,
			&g_pPerIODataArr[index]->Flags,
			&g_pPerIODataArr[index]->overlap,
			NULL);
		}
	}
	return 0;
}
void Cleanup(int index)
{
	closesocket(g_CliSocketArr[index]);
	WSACloseEvent(g_CliEventArr[index]);
	HeapFree(GetProcessHeap(), 0, g_pPerIODataArr[index]);
	if (index < g_iTotalConn - 1)
	{  
		g_CliSocketArr[index] = g_CliSocketArr[g_iTotalConn - 1];
		g_CliEventArr[index] = g_CliEventArr[g_iTotalConn - 1];
		g_pPerIODataArr[index] = g_pPerIODataArr[g_iTotalConn - 1];
	}
	g_pPerIODataArr[--g_iTotalConn] = NULL;
}

相關推薦

windows Socket程式設計重疊IO模型

上一篇文章我們講了EventSelect網路模型,它已經解決了等待資料到來的這一大部分時間,但是它還有一小部分時間沒有節省下來。那就是把資料從網絡卡的緩衝區拷貝到我們應用程式的緩衝區裡邊。而這一篇的重疊IO模型就是將這一小部分的時間也給節省了下來。 首先,我們在主執行緒裡邊

Windows Socket程式設計UDP實現大檔案的傳輸

前言:本文實現以下功能:在客戶端,使用者選擇本地的某個檔案,併發送到伺服器端。在伺服器端,接收客戶端傳輸的資料流,並按IP 地址儲存在伺服器端(文件名重複的,可以覆蓋)。如果傳輸過程中伺服器端發現客戶端斷開,伺服器端應刪除檔案,並在螢幕上提示,如“IP:1.2.3.4 發來a

windows Socket程式設計UDP的服務端和客戶端

上一篇講了TCP的服務端和客戶端,本篇文章來介紹一下UDP的服務端和客戶端。 相比TCP來說,UDP相對比較簡單,剛開始的時候,和TCP一樣都需要先進行網路環境的初始化,即呼叫WSAStartup函式。然後呢,我們也需要建立一個socket,這個socket和TCP的那個s

Windows Socket程式設計TCP實現大檔案的傳輸

前言: 本文實現以下功能: 在客戶端,使用者選擇本地的某個檔案,併發送到伺服器端。 在伺服器端,接收客戶端傳輸的資料流,並按IP 地址儲存在伺服器端(文 件名重複的,可以覆蓋)。 如果傳輸過程中伺服器端發現客戶端斷開,伺服器端應刪除檔案,並在螢幕 上提示,如“IP:1.

IOCP程式設計重疊IO

其實這個標題有點“標題黨”的味道,為了大家搜尋方便我故意冠以IOCP程式設計之名,其實重疊IO程式設計並不一定需要IOCP,而IOCP程式設計就一定需要重疊IO。是不是已經被這句話給繞暈了?總之是為了更好的應用IOCP,所以要理解重疊IO。這篇文章的核心就是討論重疊IO的來龍

Socket程式設計模型重疊IO(Overlapped I/O)模型

一、原理 Winsock2的釋出使得Socket I/O有了和檔案I/O統一的介面。我們可以通過使用Win32檔案操縱函式ReadFile和WriteFile來進行Socket I/O。伴隨而來的,用於普通檔案I/O的重疊I/O模型和完成埠模型對Socket I/O也適用

網路伺服器程式設計——重疊IO模型

4.3.4重疊I/O模型 非同步IO和同步IO的區別: 同步IO中,執行緒啟動一個IO操作然後就立即進入等待狀態,直到IO操作完成後才醒來繼續執行。 非同步IO中,執行緒傳送一個IO請求到核心,然後繼續處理其他的事情,核心完成IO請求後,將會通知執行緒IO操作完成了。重疊IO屬於非同步I

Windows網路程式設計面向非連線的Socket程式設計

byzxy,Java/C++程式設計交流群:168424095 面向非連線的Socket通訊是基於UDP的。 UDP是User Datagram Protocol的簡稱,中文名是使用者資料包協議,

windows環境下Socket程式設計的幾種模型

阻塞模型, 這個模型是講解計算機網路時被作為例子介紹的,也是最簡單的。其基本原理是:首先建立一個socket連線,然後對其進行操作,比如,從該socket讀資料。因為網路傳輸是要一定的時間的,即使網路通暢的情況下,接受資料的操作也要花費時間。對於一個簡單的單執行緒程式,接收資料的過程是無法處理其他操作的。

Socket程式設計TCP的簡單實現

Client import java.io.*; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.Socket; public class Client { pub

C# socket 程式設計 accept() 函式返回值解析

accept() 函式會返回一個新的套接字,這個新的套接字在伺服器端與客戶端進行通訊。 伺服器端的繫結監聽是一個套接字,與客戶端通訊的是另一個套接字(accept函式返回的套接字,注意這裡不是返回客戶端的套接字,返回的套接字是新建立在伺服器上的,與客戶端收發訊息用的) 下面這段程式碼,是

python下socket程式設計TCP連線狀態

1. 引言 python作為一門膠水語言,可以在各個領域上作為快速開發的工具,大大提高開發者處理事務的效率。在網際網路行業中,不管是對網路伺服器的開發,還是對網路客戶端,例如爬蟲的開發中,都會涉及到底層的執行原理,那就是socket程式設計,那麼今天,我們將對python下的socke

Windows核心程式設計執行緒

執行緒組成兩部分: 1. 一個執行緒的核心物件,作業系統用它管理執行緒。 2. 一個執行緒棧,用於維護執行緒執行時所需的所有函式引數和區域性變數。 何時建立執行緒?舉例: 作業系統的Windows Indexing Services,磁碟碎片整理程式等,都是使用多執行緒進行效能優化的

windows核心程式設計程序

什麼是程序? 程序是一個正在執行程式的例項。由兩部分組成:一個核心物件,用於管理程序以及一個地址空間,包含所有可執行檔案或DLL模組的程式碼和資料,此外還包含動態記憶體分配。 在分析程序之前,先看下windows程式是如何建立的? Windows應用程式分為CUI和GUI程式,即控

網路程式設計——七層模型與TCP三段握手與四次斷開

轉載請註明出處:https://blog.csdn.net/l1028386804/article/details/83046311 一、C/S架構 客戶端/服務端架構 二、OSI七層架構 七層模型,亦稱OSI(Open System Interconnection)參考模型,是

android程式設計3 socket程式設計udp傳送

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Windows平臺程式設計OnCreate函式的說明

        OnCreate是一個訊息響應函式,是響應WM_CREATE訊息的一個函式,而WM_CREATE訊息是由Create函式呼叫的。   在view類中,Create 是虛擬函式由框架呼叫,是用來“生成一個視窗的子視窗”。 而OnCreate

java Socket程式設計TCP基本原理

通訊原理: 1.伺服器程式建立一個ServerSocket,呼叫accept方法等待客戶機來連線。 2.客戶端程式建立一個Socket,請求與伺服器建立連線。 3.伺服器接收客戶機的連線請求,同時建立一個新的Socket與客戶端建立連線。伺服器繼續等待新的請求。 關鍵類: ServerS

2018.12.02 Socket程式設計初識Socket

Socket程式設計主要分為TCP/UDP/SCTP三種,每一種都有各自的優點,所以會根據實際情況決定選用何種Socket,今天開始我將會逐步學習Socket程式設計,並將學習過程記錄於此。 今天學習的是TCP程式設計。 TCP基本客戶端與服務端的套接字函式:   Client: socket/conn

Socket程式設計 一種死鎖現象

剛接觸socket程式設計的過程中,很容易出現死鎖的現象。下面我來介紹一種死鎖的原因和解決的方法。 先來看這段程式碼: /*客戶端傳送一個資訊到服務端*/ Socket socket = new Socket("127.0.0.1", 8081); outputStream