Windows网络编程

Windows网络编程

大家好,又见面了,我是全栈君,祝每个程序员都可以多学几门语言。

第一章 序言



 

              我写这个专题的目的,一方面是为了通过对网络编程再一次系统的总结,提高自己的网络编程水平,特别是Windows下的网络编程水平。同一时候,我也希望,能为众多初学网络编程的人提供一点帮助,由于我開始学习网络编程的时候,能找到的资料就非常少。当然,花钱能够买到翻译版本号的书:)

              首先向大家推荐一本非常好的參考书,Network Programming for Microsoft Windows 2nd

初学网络编程的时候我还不知道有这样一本好书,仅仅是上各大论坛把能找到的网络编程方面的文章和代码下载下来,然后自己研究。后来看到别人推荐这一本书,下载了一个,看了感觉非常好,里面的内容写得非常规范,条理也非常清楚,英文好的朋友能够直接阅读,不然就仅仅好去弄一本翻译好的来研究了。、

              我试着从Windows编程的基础開始,一直到探索建立高性能的网络应用程序。我说过,我并非以高手的身份写这本书,而是以和大家一起学习的心态学习网络编程,写书仅仅是让自己的思路更清晰,以后还能够翻阅。所以,我不保证书中全部的内容都是绝对正确和标准的,有不妥的地方,还希望高手批评指正。

              这本书是全然免费的,读者能够随意使用书中的代码。可是假设须要转载,请注明原作者和出处。假设有商业运作的需求,请直接和我联系。



 



 

第二章 Windows网络编程基础



 

              这本书主要探索Windows网络编程,开发平台是Windows 2000 Visual C++.NET,从一个合格的C++程序猿到网络编程高手,还是须要花不少功夫,至少我觉得写一个聊天程序非常easy,而要写一个能同一时候响应成千上万用户的高性能网络程序,的确不容易。这篇文章所介绍的方法也并非能直接应用于每个详细的应用程序,仅仅能作为学习的參考资料。

              开发高性能网络游戏恐怕是促使非常多程序猿研究网络编程的原因(包含我),如今的大型网络游戏对同一时候在线人数的要求比較高,真正的项目往往採取多个server(组)负荷分担的方式工作,我将首先把注意力放到单个server的情况。

              大家都知道,我们用得最多的协议是UDPTCPUDP是不可靠传输服务,TCP是可靠传输服务。UDP就像点对点的传输数据一样,发送者把数据打包,包上有收信者的地址和其它必要信息,至于收信者能不能收到,UDP协议并不保证。而TCP协议就像(实际他们是一个层次的网络协议)是建立在UDP的基础上,添�了校验和重传等复杂的机制来保证数据可靠的传达到收信者。关于网络协议的详细内容,读者能够參考专门介绍网络协议的书籍,或者查看RFC中的有关内容。本书直接探讨编程实现网络程序的问题。

             



 



 

21 Window Socket介绍



 

              Windows Socket是从UNIX Socket继承发展而来,最新的版本号是2.2。进行Windows网络编程,你须要在你的程序中包括WINSOCK2.HMSWSOCK.H,同一时候你须要加入�引入库WS2_32. LIBWSOCK32.LIB。准备好后,你就能够着手建立你的第一个网络程序了。

              Socket编程有堵塞和非堵塞两种,在操作系统I/O实现时又有几种模型,包含SelectWSAAsyncSelectWSAEventSelect IO重叠模型,完毕port等。要学习主要的网络编程概念,能够选择从堵塞模式開始,而要开发真正有用的程序,就要进行非堵塞模式的编程(非常难想象一个大型server採用堵塞模式进行网络通信)。在选择I/O模型时,我建议刚開始学习的人能够从WSAAsyncSelect模型開始,由于它比較简单,并且有一定的有用性。可是,差点儿全部人都认识到,要开发同一时候响应成千上万用户的网络程序,完毕port模型是最好的选择。

              既然完毕port模型是最好的选择,那为什么我们不直接写出一个使用完毕port的程序,然后大家稍加改动就OK了。我觉得这确实是一个好的想法,可是真正做项目的时候,不同的情况对程序有不同的要求,假设不深入学习网络编程的各方面知识,是不可能写出符合要求的程序,在学习网络编程曾经,我建议读者先学习一下网络协议。



 



 



 

22 第一个网络程序



 

因为server/client模式的网络应用比較多,并且server端的设计是重点和难点。所以我想首先探讨server的设计方法,在完毕server的设计后再探讨其它模式的网络程序。

设计一个主要的网络server有下面几个步骤:

1、初始化Windows Socket

2、创建一个监听的Socket

3、设置server地址信息,并将监听port绑定到这个地址上

4、開始监听

5、接受client连接

6、和client通信

7、结束服务并清理Windows Socket和相关数据,或者返回第4



 

              我们能够看出设计一个最简单的server并不须要太多的代码,它全然能够做一个小型的聊天程序,或进行数据的传输。可是这仅仅是我们的開始,我们的终于目的是建立一个有大规模响应能力的网络server。假设读者对操作系统部分的线程使用还有疑问,我建议你如今就開始复习,由于我们常常使用线程来提高程序性能,事实上线程就是让CPU不停的工作,而不是总在等待I/O,或者是一个CPI,累死了还是一个CPU。千万不要以为线程越多的server,它的性能就越好,线程的切换也是须要消耗时间的,对于I/O等待少的程序,线程越多性能反而越低。

              以下是简单的server和client源码。(堵塞模式下的,供刚開始学习的人理解)



 



 

TCPServer



 

#include <winsock2.h> 
    

   
    
      
   
void main(void) 
    
{ 
    
   WSADATA              wsaData; 
    
   SOCKET               ListeningSocket; 
    
   SOCKET               NewConnection; 
    
   SOCKADDR_IN          ServerAddr; 
    
   SOCKADDR_IN          ClientAddr; 
    
   int                  Port = 5150; 
    
    
    
   // 初始化Windows Socket 2.2 
    
   WSAStartup(MAKEWORD(2,2), &wsaData); 
    
    
    
   // 创建一个新的Socket来响应client的连接请求 
    
   ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    
    
    
   // 填写server地址信息 
    
   // port为5150 
    
   // IP地址为INADDR_ANY,注意使用htonlIP地址转换为网络格式 
    
   ServerAddr.sin_family = AF_INET; 
    
   ServerAddr.sin_port = htons(Port);     
    
   ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    
           
    
   // 绑定监听port 
    
   bind(ListeningSocket, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr)); 
    

   
    
      
   
   // 開始监听,指定最大同一时候连接数为5 
    
      listen(ListeningSocket, 5); 
    

   
    
      
   
   // 接受新的连接 
    
   NewConnection = accept(ListeningSocket, (SOCKADDR *) &ClientAddr,&ClientAddrLen)); 
    

   
    
      
   
   // 新的连接建立后,就能够互相通信了,在这个简单的样例中,我们直接关闭连接, 
    
   // 并关闭监听Socket,然后退出应用程序 
    
   //   
    
      closesocket(NewConnection); 
    
      closesocket(ListeningSocket); 
    

   
    
      
   
   // 释放Windows Socket DLL的相关资源 
    
      WSACleanup(); 
    
} 
    



 



 

TCPClient



 

# include <winsock2.h> 
    

   
    
      
   
void main(void) 
    
{ 
    
   WSADATA              wsaData; 
    
   SOCKET               s; 
    
   SOCKADDR_IN          ServerAddr; 
    
   int                  Port = 5150; 
    
    
    
   //初始化Windows Socket 2.2 
    
   WSAStartup(MAKEWORD(2,2), &wsaData); 
    
    
    
   // 创建一个新的Socket来连接server 
    
      s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    
    
    
   // 填写client地址信息 
    
   // port为5150 
    
   // serverIP地址为"136.149.3.29",注意使用inet_addrIP地址转换为网络格式 
    
      ServerAddr.sin_family = AF_INET; 
    
      ServerAddr.sin_port = htons(Port);     
    
      ServerAddr.sin_addr.s_addr = inet_addr("136.149.3.29"); 
    

   
    
      
   
   // 向server发出连接请求 
    
      connect(s, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr)); 
    
       
    
   // 新的连接建立后,就能够互相通信了,在这个简单的样例中,我们直接关闭连接, 
    
   // 并关闭监听Socket,然后退出应用程序 
    
      closesocket(s); 
    

   
    
      
   
   // 释放Windows Socket DLL的相关资源 
    
      WSACleanup(); 
    

}



 

23 WSAAsyncSelect模式

              前面说过,Windows网络编程模式有好几种,他们各有特点,实现起来复杂程度各不同样,适用范围也不一样。下图是Network Programming for Microsoft Windows 2nd 一书中对不同模式的一个性能測试结果。server採用Pentium 4 1.7 GHz XeonCPU768M内存;client有3PC,配置各自是Pentium 2 233MHz 128 MB 内存,Pentium 2 350 MHz 128 MB内存,Itanium 733 MHz 1 GB内存。























              详细的结果分析大家能够看看原书中作者的叙述,我关心的是哪种模式是我须要的。首先是server,勿庸置疑,肯定是完毕port模式。那么client呢,当然也能够採用完毕port,可是不同模式是在不同的操作系统下支持的,看下图:






              完毕port在Windows 98下是不支持的,尽管我们能够假定全部的用户都已经装上了Windows 2000Windows XP,。可是,假设是商业程序,这样的想法在现阶段不应该有,我们不能让用户为了使用我们的client而去升级他的操作系统。Overlapped I/O能够在Windows 98下实现,性能也不错,可是实现和理解起来快赶上完毕port了。并且,最关键的一点,client程序不是用来进行大规模网络响应的,client的主要工作应该是进行诸如图形运算等非网络方面的任务。原书作者,包含我强烈推荐大家使用WSAAsyncSelect模式实现client,由于它实现起来比較直接和easy,并且他全然能够满足client编程的需求。

              以下是一段源码,尽管我们是用它来写client,我还是把它的服务端代码放上来,一方面是有兴趣的朋友能够用他做測试和了解怎样用它实现server;还有一方面是client的代码能够非常easy的从它改动而成,不同的地方仅仅要參考一下2.1节里的代码就知道了。



 

#define WM_SOCKET WM_USER + 1 
    
#include <winsock2.h> 
    
#include <windows.h> 
    

   
    
      
   
int WINAPI WinMain(HINSTANCE hInstance, 
    
    HINSTANCE hPrevInstance, LPSTR lpCmdLine, 
    
    int nCmdShow) 
    
{ 
    
    WSADATA wsd; 
    
    SOCKET Listen; 
    
    SOCKADDR_IN InternetAddr; 
    
    HWND Window; 
    
    // 创建主窗体 
    

   
    
      
   
    Window = CreateWindow(); 
    
    // 初始化Windows Socket 2.2 
    
WSAStartup(MAKEWORD(2,2), &wsd); 
    

   
    
      
   
// 创建监听Socket 
    
    Listen = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    

   
    
      
   
    // 设置server地址 
    
    InternetAddr.sin_family = AF_INET; 
    
    InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    
    InternetAddr.sin_port = htons(5150); 
    

   
    
      
   
    // 绑定Socket 
    
    bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)); 
    

   
    
      
   
    // 设置Windows消息,这样当有Socket事件发生时,窗体就能收到相应的消息通知 
    
// server一般设置 FD_ACCEPT │ FD_READ | FD_CLOSE 
    
// client一般设置 FD_CONNECT │ FD_READ | FD_CLOSE 
    
    WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT │ FD_READ | FD_CLOSE); 
    

   
    
      
   
   // 開始监听 
    
   listen(Listen, 5); 
    

   
    
      
   
    // Translate and dispatch window messages 
    
    // until the application terminates 
    
    while (1) { 
    
     // ... 
    
 } 
    
} 
    

   
    
      
   
BOOL CALLBACK ServerWinProc(HWND hDlg,UINT wMsg, 
    
    WPARAM wParam, LPARAM lParam) 
    
{ 
    
    SOCKET Accept; 
    

   
    
      
   
    switch(wMsg) 
    
    { 
    
        case WM_PAINT: 
    
            // Process window paint messages 
    
            break; 
    

   
    
      
   
        case WM_SOCKET: 
    
            // Determine whether an error occurred on the 
    
            // socket by using the WSAGETSELECTERROR() macro 
    

   
    
      
   
            if (WSAGETSELECTERROR(lParam)) 
    
            { 
    
                 // Display the error and close the socket 
    
                closesocket( (SOCKET) wParam); 
    
                break; 
    
            } 
    

   
    
      
   
            // Determine what event occurred on the 
    
            // socket 
    

   
    
      
   
            switch(WSAGETSELECTEVENT(lParam)) 
    
            { 
    
                case FD_ACCEPT: 
    

   
    
      
   
                    // Accept an incoming connection 
    
                    Accept = accept(wParam, NULL, NULL); 
    

   
    
      
   
                    // Prepare accepted socket for read, 
    
                    // write, and close notification 
    

   
    
      
   
                    WSAAsyncSelect(Accept, hDlg, WM_SOCKET, 
    
                        FD_READ │ FD_WRITE │ FD_CLOSE); 
    
                    break; 
    

   
    
      
   
                case FD_READ: 
    
                    // Receive data from the socket in 
    
                    // wParam 
    
                    break; 
    

   
    
      
   
                case FD_WRITE: 
    
                    // The socket in wParam is ready 
    
                    // for sending data 
    
                    break; 
    

   
    
      
   
                case FD_CLOSE: 
    
                    // The connection is now closed 
    
                    closesocket( (SOCKET)wParam); 
    
                    break; 
    
            } 
    
            break; 
    
    } 
    
    return TRUE; 
    

}



 



 

24 小节

              眼下为止,我非常简要的介绍了Windows网络编程的一些东西,附上了一些源码。能够说,读者特别是刚開始学习的人,看了后不一定就能立即写出程序来,而那些代码也不是能够直接应用于实际的项目。别急,万里长征才開始第一步呢,非常多书里都是依照基础到应用的顺序来写的,可是我喜欢更直接一点,更有用一些的方式。并且,我写的这个专题,毕竟不是商业化的,时间上不能投入过多,仅仅是作为给刚開始学习的人的一个小小帮助。很多其它的还是希望读者自己刻苦研究,有问题的时候能够到我的论坛上给我留言,以后有机会我也会发布一些实际的代码。希望结交很多其它热爱编程和中国游戏事业的朋友。下一章里我将主要解说完毕port编程,这也是我写这篇文章的初衷,希望对大家能有所帮助。



 



 



 

第三章 完毕port模式下的高性能网络server



 

31開始

              完毕port听起来好像非常神奇和复杂,事实上并没有想象的那么难。这方面的文章在论坛上能找到的我差点儿相同都看过,写得好点的就是CSDN.NET上看到的一组系列文章,只是我觉得它仅仅是简单的翻译了一下Network Programming for Microsoft Windows 2nd 中的相关内容,附上的代码好像不是原书中的,可能是还有一本外文书里的。我看了以后,觉得还不如看原版的更easy理解。所以在我的開始部分,我主要带领刚開始学习的人理解一下完毕port的有关内容,是我开发的经验,其它的请參考原书的相关内容。

              採用完毕port的优点是,操作系统的内部重叠机制能够保证大量的网络请求都被server处理,而不是像WSAAsyncSelect WSAEventSelect的那样对并发的网络请求有限制,这一点从上一章的測试表格中能够清楚的看出。

              完毕port就像一种消息通知的机制,我们创建一个线程来不断读取完毕port状态,接收到对应的完毕通知后,就进行对应的处理。事实上感觉就像WSAAsyncSelect一样,只是还是有一些的不同。比方我们想接收消息,WSAAsyncSelect会在消息到来的时候直接通知Windows消息循环,然后就能够调用WSARecv来接收消息了;而完毕port则首先调用一个WSARecv表示程序须要接收消息(这时可能还没有不论什么消息到来),可是仅仅有当消息来的时候WSARecv才算完毕,用户就能够处理消息了,然后再调用一个WSARecv表示等待下一个消息,如此不停循环,我想这就是完毕port的最大特点吧。

              Per-handle Data Per-I/O Operation Data 是两个比較重要的概念,Per-handle Data用来把client数据和相应的完毕通知关联起来,这样每次我们处理完毕通知的时候,就能知道它是哪个client的消息,而且能够依据client的信息作出相应的反应,我想也能够理解为Per-Client handle Data吧。Per-I/O Operation Data则不同,它记录了每次I/O通知的信息,比方接收消息时我们就能够从中读出消息的内容,也就是和I/O操作有关的信息都记录在里面了。当你亲手实现完毕port的时候就能够理解他们的不同和用途了。

              CreateIoCompletionPort函数中有个參数NumberOfConcurrentThreads,完毕port编程里有个概念Worker Threads。这里比較easy引起混乱,NumberOfConcurrentThreads须要设置多少,又须要创建多少个Worker Threads才算合适?NumberOfConcurrentThreads的数目和CPU数量一样最好,由于少了就没法利用多CPU的优势,而多了则会由于线程切换造成性能下降。Worker Threads的数量是不是也要一样多呢,当然不是,它的数量取决于应用程序的须要。举例来说,我们在Worker Threads里进行消息处理,假设这个过程中有可能会造成线程堵塞,那假设我们仅仅有一个Worker Thread,我们就不能非常快响应其它client的请求了,而仅仅有当这个堵塞操作完毕了后才干继续处理下一个完毕消息。可是假设我们还有其它的Worker Thread,我们就能继续处理其它client的请求,所以究竟须要多少的Worker Thread,须要依据应用程序来定,而不是能够事先估算出来的。假设工作者线程里没有堵塞操作,对于某些情况来说,一个工作者线程就能够满足须要了。

              其它问题,Network Programming for Microsoft Windows 2nd中,作者还提出了怎样安全的退出应用程序等等实现中的细节问题,这里我就不一一讲述了,请读者參考原书的相关内容,假设仍有疑问,能够联系我。



 



 



 

32实现

以下是一般的实现步骤

1.    获得计算机信息,得到CPU的数量。创建一个完毕port,第四个參数置0,指定NumberOfConcurrentThreadsCPU个数。

2.  Determine how many processors exist on the system.

3.  Create worker threads to service completed I/O requests on the completion port using processor information in step 2. In the case of this simple example, we create one worker thread per processor because we do not expect our threads to ever get in a suspended condition in which there would not be enough threads to execute for each processor. When the CreateThread function is called, you must supply a worker routine that the thread executes upon creation. We will discuss the worker thread’s responsibilities later in this section.

4.  Prepare a listening socket to listen for connections on port 5150.

5.  Accept inbound connections using the accept function.

6.  Create a data structure to represent per-handle data and save the accepted socket handle in the structure.

7.  Associate the new socket handle returned from accept with the completion port by calling CreateIoCompletionPort. Pass the per-handle data structure to CreateIoCompletionPort via the completion key parameter.

8.  Start processing I/O on the accepted connection. Essentially, you want to post one or more asynchronous WSARecv or WSASend requests on the new socket using the overlapped I/O mechanism. When these I/O requests complete, a worker thread services the I/O requests and continues processing future I/O requests, as we will see later in the worker routine specified in step 3.

9.  Repeat steps 5–8 until server terminates.



 

那么学习完毕port编程从哪里開始比較好,对于刚開始学习的人而言,直接进入编程并非一个好主意,我建议刚開始学习的人首先学习用异步Socket模式,即WSAEventSelect模式构建一个简单的聊天server。当把Windows网络编程的概念有一个清晰的认识之后,再深入研究完毕port编程。

接着就是深入研究详细的编程实现了,从Network Programming for Microsoft Windows 2nd中摘录的这段经典代码能够说是很合适的,这里我仅仅简单解释一下当中比較关键的地方,还有不明确的能够參看原书,或者联系我。



 

主程序段:

1.    HANDLE CompletionPort; 
    
2.    WSADATA wsd; 
    
3.    SYSTEM_INFO SystemInfo; 
    
4.    SOCKADDR_IN InternetAddr; 
    
5.    SOCKET Listen; 
    
6.    int i; 
    
7.    
   
    
      
   
8.    typedef struct _PER_HANDLE_DATA 
    
9.    { 
    
10.  SOCKET          Socket; 
    
11.  SOCKADDR_STORAGE  ClientAddr; 
    
12.  // 在这里还能够添�其它和client关联的数据 
     
13.} PER_HANDLE_DATA, * LPPER_HANDLE_DATA; 
    
14. 
    
        
    
15.// 初始化Windows Socket 2.2 
    
16.StartWinsock(MAKEWORD(2,2), &wsd); 
    
17. 
    
        
    
18.// Step 1: 
    
19.// 创建完毕port 
     
20. 
    
        
    
21.CompletionPort = CreateIoCompletionPort( 
    
22.    INVALID_HANDLE_VALUE, NULL, 0, 0); 
    
23. 
    
        
    
24.// Step 2: 
    
25.// 检測系统信息 
     
26. 
    
        
    
27.GetSystemInfo(&SystemInfo); 
    
28. 
    
        
    
29.// Step 3: 创建工作者线程,数量和CPU的数量一样多 
     
30.// Create worker threads based on the number of 
    
31.// processors available on the system. For this 
    
32.// simple case, we create one worker thread for each 
    
33.// processor. 
    
34. 
    
        
    
35.for(i = 0; i < SystemInfo.dwNumberOfProcessors; i++) 
    
36.{ 
    
37.    HANDLE ThreadHandle; 
    
38. 
    
        
    
39.    // Create a server worker thread, and pass the 
    
40.    // completion port to the thread. NOTE: the 
    
41.    // ServerWorkerThread procedure is not defined 
    
42.    // in this listing. 
    
43. 
    
        
    
44.    ThreadHandle = CreateThread(NULL, 0, 
    
45.        ServerWorkerThread, CompletionPort, 
    
46.        0, NULL; 
    
47. 
    
        
    
48.    // Close the thread handle 
    
49.    CloseHandle(ThreadHandle); 
    
50.} 
    
51. 
    
        
    
52.// Step 4: 
    
53.// 创建监听Socket 
    
54. 
    
        
    
55.Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, 
    
56.    WSA_FLAG_OVERLAPPED); 
    
57. 
    
        
    
58.InternetAddr.sin_family = AF_INET; 
    
59.InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY); 
    
60.InternetAddr.sin_port = htons(5150); 
    
61.bind(Listen, (PSOCKADDR) &InternetAddr, 
    
62.    sizeof(InternetAddr)); 
    
63. 
    
        
    
64.// 開始监听 
     
65. 
    
        
    
66.listen(Listen, 5); 
    
67. 
    
        
    
68.while(TRUE) 
    
69.{ 
    
70.    PER_HANDLE_DATA *PerHandleData=NULL; 
    
71.    SOCKADDR_IN saRemote; 
    
72.    SOCKET Accept; 
    
73.    int RemoteLen; 
    
74.    // Step 5: 等待client连接,然后将clientSocket添�完毕port 
     
75.    // Accept connections and assign to the completion 
    
76.    // port 
    
77. 
    
        
    
78.    RemoteLen = sizeof(saRemote); 
    
79.    Accept = WSAAccept(Listen, (SOCKADDR *)&saRemote, 
    
80.    &RemoteLen); 
    
81. 
    
        
    
82.    // Step 6: 初始化client数据 
     
83.    // Create per-handle data information structure to 
    
84.    // associate with the socket 
    
85.    PerHandleData = (LPPER_HANDLE_DATA) 
    
86.        GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA)); 
    
87. 
    
        
    
88.    printf("Socket number %d connected/n", Accept); 
    
89.    PerHandleData->Socket = Accept; 
    
90.    memcpy(&PerHandleData->ClientAddr, &saRemote, RemoteLen); 
    
91. 
    
        
    
92.    // Step 7: 
    
93.    // Associate the accepted socket with the 
    
94.    // completion port 
    
95. 
    
        
    
96.    CreateIoCompletionPort((HANDLE) Accept, 
    
97.        CompletionPort, (DWORD) PerHandleData, 0); 
    
98. 
    
        
    
99.    // Step 8: 发出对client的I/O请求,等待完毕消息 
     
100.       //  Start processing I/O on the accepted socket. 
    
101.       //  Post one or more WSASend() or WSARecv() calls 
    
102.       //  on the socket using overlapped I/O. 
    
103.       WSARecv(...); 
    
104.   } 
    
105.   
   
    
      
   
106.        
    



 



 

工作者线程



 

DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID) 
    
{ 
    
    HANDLE CompletionPort = (HANDLE) CompletionPortID; 
    
    DWORD BytesTransferred; 
    
    LPOVERLAPPED Overlapped; 
    
    LPPER_HANDLE_DATA PerHandleData; 
    
    LPPER_IO_DATA PerIoData; 
    
    DWORD SendBytes, RecvBytes; 
    
    DWORD Flags; 
    
     
    
    while(TRUE) 
    
    { 
    
        // 等待完毕port消息,未收到消息德时候则堵塞线程 
     
           
    
        ret = GetQueuedCompletionStatus(CompletionPort, 
    
            &BytesTransferred,(LPDWORD)&PerHandleData, 
    
            (LPOVERLAPPED *) &PerIoData, INFINITE); 
    

   
    
      
   
        // First check to see if an error has occurred 
    
        // on the socket; if so, close the 
    
        // socket and clean up the per-handle data 
    
        // and per-I/O operation data associated with 
    
        // the socket 
    

   
    
      
   
        if (BytesTransferred == 0 && 
    
            (PerIoData->OperationType == RECV_POSTED ││ 
    
             PerIoData->OperationType == SEND_POSTED)) 
    
        { 
    
            // A zero BytesTransferred indicates that the 
    
            // socket has been closed by the peer, so 
    
            // you should close the socket. Note: 
    
            // Per-handle data was used to reference the 
    
            // socket associated with the I/O operation. 
    
 
    
            closesocket(PerHandleData->Socket); 
    

   
    
      
   
            GlobalFree(PerHandleData); 
    
            GlobalFree(PerIoData); 
    
            continue; 
    
        } 
    

   
    
      
   
        // Service the completed I/O request. You can 
    
        // determine which I/O request has just 
    
        // completed by looking at the OperationType 
    
        // field contained in the per-I/O operation data. 
    
         if (PerIoData->OperationType == RECV_POSTED) 
    
        { 
    
            // Do something with the received data 
    
            // in PerIoData->Buffer 
    
        } 
    

   
    
      
   
        // Post another WSASend or WSARecv operation. 
    
        // As an example, we will post another WSARecv() 
    
        // I/O operation. 
    

   
    
      
   
        Flags = 0; 
    

   
    
      
   
        // Set up the per-I/O operation data for the next 
    
        // overlapped call 
    
        ZeroMemory(&(PerIoData->Overlapped), 
    
            sizeof(OVERLAPPED)); 
    

   
    
      
   
        PerIoData->DataBuf.len = DATA_BUFSIZE; 
    
        PerIoData->DataBuf.buf = PerIoData->Buffer; 
    
        PerIoData->OperationType = RECV_POSTED; 
    

   
    
      
   
        WSARecv(PerHandleData->Socket, 
    
            &(PerIoData->DataBuf), 1, &RecvBytes, 
    
            &Flags, &(PerIoData->Overlapped), NULL); 
    
    } 
    

}



 



 

33 小节



 

              讲这么点就完了?你一定觉得我介绍的东西并没有超过原书中的内容,实事上完毕port编程的精髓就是上面的代码和原书中的有关叙述。假设我再把他们完整的反复一遍,那又有什么意思呢?依据我的经验,设计网络server的真正难点,不在于完毕port技术,所以我想利用小节把自己编程中的一些经验告诉大家。

              首先是server的管理,一个server首先要分析它的设计目标是应对非常多的连接还是非常大的数据传送量。这样在设计工作者线程时就能够最大限度的提高性能。管理client方面,我们能够将client的数据捆绑到Perhand-Data数据结构上,假设还有须要,能够建一个表来记录client的宏观情况。

              Ares引擎中,我将文件传送和大容量数据传送功能也封装进了server和client。我建议server和client都应该封装这些功能,虽然我们并非做FTPserver,可是当client须要和server交换文件和大块数据时,你会发现这样做,灵活性和性能都能做得比用单纯的FTP协议来更好,所以在你的server和client能够传送数据包以后,把他们都做进去吧。

              为了server不被黑客攻击,或被BUG弄崩溃,我们还须要认真设计server的认证机制,以及密切注意程序中的溢出,一定要在每个使用缓冲区的地方加上检查代码。可以说并没有现成的办法来解决问题,不然就没有人研究网络安全了,所以我们要做的是尽量降低错误,即使出现错误也不会造成太大损失,在发现错误的时候可以非常快纠正同类错误。

              还有就是对client情况的检測,比方client的正常和非正常断开连接。假设不注意这一点,就会造成server资源持续消耗而终于崩溃,由于我们的server不可能总是重新启动,而是要持续的执行,越久越好。还有比方client断开连接后又尝试连接,可是在server看来这个客户“仍然在线“,这个时候我们不能单纯的拒绝client的连接,也不能单纯的接收。

              讲了几点server设计中的问题,他们仅仅是众多问题中的一小部分,限于时间原因,在这个版本号的文章中就说这么多。你一定会发现,事实上网络编程最困难和有成就的地方,并非server用了什么模式等等,而是真正深入设计的时候碰到的众多问题。正是那些没有标准答案的问题,值得我们去研究和解决。



 



 



 



 

第四章 作者的话



 

写这篇文章的目的,一方面是简要的谈谈游戏编程中的网络部分。还有一方面是结交众多开发的朋友。毕竟我们做东西不可能不和他人交流,也不可能仅仅做非商业化的项目。我开发的Ares引擎就是同一时候为了这两个目的,到我写这篇文章的时候,引擎的版本号仍然是3.2,并非我不想继续开发,也不是没有新的改变了。恰恰相反,我有非常多新的想法,急切想把他们添�新的版本号中,仅仅是如今手上还有短期的项目没有完毕。

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/118675.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 反三角函数在线计算机,反三角函数(反正弦,反余弦,反正切,反余切,反正割,反余割)在线计算器_三贝计算网_23bei.com…[通俗易懂]

    输入已知数据点计算按钮,可求出对应的角度值、弧度值、反正弦arcsin、反余弦arcos、反正切artan、反余切arcot、反正割arsec、反余割arcsc等值。为限制反三角函数为单值函数,将反正弦函数的值y限在-π/2≤y≤π/2,将y作为反正弦函数的主值,记为y=arcsinx;相应地,反余弦函数y=arccosx的主值限在0≤y≤π;反正切函数y=arctanx的主值限在-π/2余…

  • hibernate实现多租户[通俗易懂]

    hibernate实现多租户[通俗易懂]hibernate实现多租户

  • 数据库主从复制_sqlserver主从复制

    数据库主从复制_sqlserver主从复制一、什么是主从复制?主从复制,是用来建立一个和主数据库完全一样的数据库环境,称为从数据库;主数据库一般是准实时的业务数据库。二、主从复制的作用(好处,或者说为什么要做主从)重点!1、架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,物理服务器增加,负荷增加。2、读写分离,使数据库能支撑更大的并发。主从只负责各自的写和读,极大程度的缓解X锁和S锁争用。在报表中尤其重要。由于部分报表sql语句非常的慢,导致锁表,影响前台服务。如果前台使用master,报表使用slave,那

  • 青苹果论坛重新开放[通俗易懂]

    青苹果论坛重新开放[通俗易懂]青苹果论坛重新开放

  • 巧用cssText[通俗易懂]

    巧用cssText[通俗易懂]IE6/7/8下cssText值与IE9/Firefox/Safari/Chrome/Opera不同1,IE6/7/8下cssText下返回值结尾没有分号,且属性名四十大写TESTvardiv=document.getElementsByTagName(‘div’);alert(div[0].style.cssText);IE6/7/8下 IE9/F

  • fpga以太网通信例程_verilog参数传递

    fpga以太网通信例程_verilog参数传递1本实验将实现FPGA芯片和PC之间进行千兆以太网数据通信,通信协议采用Ethernet UDP通信协议。FPGA通过GMII总线和开发板上的GigabitPHY芯片通信,GigabitPHY芯片把数据通过网线发给PC。在上次的实验中,我们详细讲解了网络传输的过程中如何对数据进行传输,以及数据传输的格式,这次实验中,我们详细讲解如何使用Verilog语言

    2022年10月24日

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号