Windows网络编程

 

第一章 序言程序员

 

       我写这个专题的目的,一方面是为了经过对网络编程再一次系统的总结,提升本身的网络编程水平,特别是Windows下的网络编程水平。同时,我也但愿,能为众多初学网络编程的人提供一点帮助,由于我开始学习网络编程的时候,能找到的资料就不多。固然,花钱能够买到翻译版本的书:)编程

       首先向你们推荐一本很好的参考书,NetworkProgramming for Microsoft Windows 2ndwindows

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

       我试着从Windows编程的基础开始,一直到探索创建高性能的网络应用程序。我说过,我并非以高手的身份写这本书,而是以和你们一块儿学习的心态学习网络编程,写书只是让本身的思路更清晰,之后还能够翻阅。因此,我不保证书中全部的内容都是绝对正确和标准的,有不妥的地方,还但愿高手批评指正。服务器

       这本书是彻底免费的,读者能够任意使用书中的代码。可是若是须要转载,请注明原做者和出处。若是有商业运做的需求,请直接和我联系。网络

 

 

 

 

 

 

 

第二章 Windows网络编程基础数据结构

 

       这本书主要探索Windows网络编程,开发平台是Windows 2000VisualC++.NET,从一个合格的C++程序员到网络编程高手,仍是须要花很多功夫,至少我认为写一个聊天程序很简单,而要写一个能同时响应成千上万用户的高性能网络程序,的确不容易。这篇文章所介绍的方法也并非能直接应用于每个具体的应用程序,只能做为学习的参考资料。并发

       开发高性能网络游戏恐怕是促使不少程序员研究网络编程的缘由(包括我),如今的大型网络游戏对同时在线人数的要求比较高,真正的项目每每采起多个服务器(组)负荷分担的方式工做,我将首先把注意力放到单个服务器的状况。app

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

      

 

 

2.1 Window Socket介绍

 

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

       Socket编程有阻塞和非阻塞两种,在操做系统I/O实现时又有几种模型,包括Select,WSAAsyncSelect,WSAEventSelect ,IO重叠模型,完成端口等。要学习基本的网络编程概念,能够选择从阻塞模式开始,而要开发真正实用的程序,就要进行非阻塞模式的编程(很难想象一个大型服务器采用阻塞模式进行网络通讯)。在选择I/O模型时,我建议初学者能够从WSAAsyncSelect模型开始,由于它比较简单,并且有必定的实用性。可是,几乎全部人都认识到,要开发同时响应成千上万用户的网络程序,完成端口模型是最好的选择。

       既然完成端口模型是最好的选择,那为何咱们不直接写出一个使用完成端口的程序,而后你们稍加修改就OK了。我认为这确实是一个好的想法,可是真正作项目的时候,不一样的状况对程序有不一样的要求,若是不深刻学习网络编程的各方面知识,是不可能写出符合要求的程序,在学习网络编程之前,我建议读者先学习一下网络协议。

 

 

 

2.2 第一个网络程序

 

因为服务器/客户端模式的网络应用比较多,并且服务器端的设计是重点和难点。因此我想首先探讨服务器的设计方法,在完成服务器的设计后再探讨其余模式的网络程序。

设计一个基本的网络服务器有如下几个步骤:

一、初始化Windows Socket

二、建立一个监听的Socket

三、设置服务器地址信息,并将监听端口绑定到这个地址上

四、开始监听

五、接受客户端链接

六、和客户端通讯

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

 

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

       下面是简单的服务器和客户端源代码。(阻塞模式下的,供初学者理解)

 

 

 

 

 

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来响应客户端的链接请求
 
   ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
           
   // 填写服务器地址信息
   // 端口为5150
   // IP地址为INADDR_ANY,注意使用htonl将IP地址转换为网络格式
           
   ServerAddr.sin_family = AF_INET;
   ServerAddr.sin_port = htons(Port);    
   ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY);
           
   // 绑定监听端口
           
   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来链接服务器
 
      s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
   
   // 填写客户端地址信息
   // 端口为5150
   // 服务器IP地址为"136.149.3.29",注意使用inet_addr将IP地址转换为网络格式
 
      ServerAddr.sin_family = AF_INET;
      ServerAddr.sin_port = htons(Port);    
      ServerAddr.sin_addr.s_addr = inet_addr("136.149.3.29");
 
   // 向服务器发出链接请求
 
      connect(s, (SOCKADDR *) &ServerAddr, sizeof(ServerAddr)); 
      
   // 新的链接创建后,就能够互相通讯了,在这个简单的例子中,咱们直接关闭链接,
   // 并关闭监听Socket,而后退出应用程序
 
      closesocket(s);
 
   // 释放Windows Socket DLL的相关资源
 
 
      WSACleanup();

}

 

 

 

 

2.3 WSAAsyncSelect模式

       前面说过,Windows网络编程模式有好几种,他们各有特色,实现起来复杂程度各不相同,适用范围也不同。下图是Network Programming for Microsoft Windows 2nd 一书中对不一样模式的一个性能测试结果。服务器采用Pentium 4 1.7 GHz Xeon的CPU,768M内存;客户端有3台PC,配置分别是Pentium 2 233MHz ,128 MB 内存,Pentium 2 350 MHz ,128 MB内存,Itanium 733MHz ,1 GB内存。

       具体的结果分析你们能够看看原书中做者的叙述,我关心的是哪一种模式是我须要的。首先是服务器,勿庸置疑,确定是完成端口模式。那么客户端呢,固然也能够采用完成端口,可是不一样模式是在不一样的操做系统下支持的,看下图:

       完成端口在Windows 98下是不支持的,虽然咱们能够假定全部的用户都已经装上了Windows2000和Windows XP,。可是,若是是商业程序,这种想法在现阶段不该该有,咱们不能让用户为了使用咱们的客户端而去升级他的操做系统。Overlapped I/O能够在Windows 98下实现,性能也不错,可是实现和理解起来快遇上完成端口了。并且,最关键的一点,客户端程序不是用来进行大规模网络响应的,客户端的主要工做应该是进行诸如图形运算等非网络方面的任务。原书做者,包括我强烈推荐你们使用WSAAsyncSelect模式实现客户端,由于它实现起来比较直接和容易,并且他彻底能够知足客户端编程的需求。

       下面是一段源代码,虽然咱们是用它来写客户端,我仍是把它的服务端代码放上来,一方面是有兴趣的朋友能够用他作测试和了解如何用它实现服务器;另外一方面是客户端的代码能够很容易的从它修改而成,不一样的地方只要参考一下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);
    
    // 设置服务器地址
 
    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事件发生时,窗口就能收到对应的消息通知
// 服务器通常设置 FD_ACCEPT │ FD_READ | FD_CLOSE
// 客户端通常设置 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;

}

 

 

2.4 小节

       目前为止,我很是简要的介绍了Windows网络编程的一些东西,附上了一些源代码。能够说,读者特别是初学者,看了后不必定就能立刻写出程序来,而那些代码也不是能够直接应用于实际的项目。别急,万里长征才开始第一步呢,不少书里都是按照基础到应用的顺序来写的,可是我喜欢更直接一点,更实用一些的方式。并且,我写的这个专题,毕竟不是商业化的,时间上不能投入过多,只是做为给初学者的一个小小帮助。更多的仍是但愿读者本身刻苦研究,有问题的时候能够到个人论坛上给我留言,之后有机会我也会公布一些实际的代码。但愿结交更多热爱编程和中国游戏事业的朋友。下一章里我将主要讲解完成端口编程,这也是我写这篇文章的初衷,但愿对你们能有所帮助。

 

 

 

 

 

 

第三章 完成端口模式下的高性能网络服务器

 

3.1开始

       完成端口听起来好像很神秘和复杂,其实并无想象的那么难。这方面的文章在论坛上能找到的我差很少都看过,写得好点的就是CSDN.NET上看到的一组系列文章,不过我认为它只是简单的翻译了一下Network Programming for Microsoft Windows 2nd 中的相关内容,附上的代码好像不是原书中的,多是另外一本外文书里的。我看了之后,以为还不如看原版的更容易理解。因此在个人开始部分,我主要带领初学者理解一下完成端口的有关内容,是我开发的经验,其余的请参考原书的相关内容。

       采用完成端口的好处是,操做系统的内部重叠机制能够保证大量的网络请求都被服务器处理,而不是像WSAAsyncSelect 和WSAEventSelect的那样对并发的网络请求有限制,这一点从上一章的测试表格中能够清楚的看出。

       完成端口就像一种消息通知的机制,咱们建立一个线程来不断读取完成端口状态,接收到相应的完成通知后,就进行相应的处理。其实感受就像WSAAsyncSelect同样,不过仍是有一些的不一样。好比咱们想接收消息,WSAAsyncSelect会在消息到来的时候直接通知Windows消息循环,而后就能够调用WSARecv来接收消息了;而完成端口则首先调用一个WSARecv表示程序须要接收消息(这时可能尚未任何消息到来),可是只有当消息来的时候WSARecv才算完成,用户就能够处理消息了,而后再调用一个WSARecv表示等待下一个消息,如此不停循环,我想这就是完成端口的最大特色吧。

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

     CreateIoCompletionPort函数中有个参数NumberOfConcurrentThreads,完成端口编程里有个概念Worker Threads。这里比较容易引发混乱,NumberOfConcurrentThreads须要设置多少,又须要建立多少个Worker Threads才算合适?NumberOfConcurrentThreads的数目和CPU数量同样最好,由于少了就无法利用多CPU的优点,而多了则会由于线程切换形成性能降低。WorkerThreads的数量是否是也要同样多呢,固然不是,它的数量取决于应用程序的须要。举例来讲,咱们在Worker Threads里进行消息处理,若是这个过程当中有可能会形成线程阻塞,那若是咱们只有一个Worker Thread,咱们就不能很快响应其余客户端的请求了,而只有当这个阻塞操做完成了后才能继续处理下一个完成消息。可是若是咱们还有其余的Worker Thread,咱们就能继续处理其余客户端的请求,因此到底须要多少的Worker Thread,须要根据应用程序来定,而不是能够事先估算出来的。若是工做者线程里没有阻塞操做,对于某些状况来讲,一个工做者线程就能够知足须要了。

       其余问题,Network Programming forMicrosoft Windows 2nd中,做者还提出了如何安全的退出应用程序等等实现中的细节问题,这里我就不一一讲述了,请读者参考原书的相关内容,若是仍有疑问,能够联系我。

 

 

 

3.2实现

下面是通常的实现步骤

1. 得到计算机信息,获得CPU的数量。建立一个完成端口,第四个参数置0,指定NumberOfConcurrentThreads为CPU个数。

2. Determine how many processorsexist on the system.

3. Create worker threads toservice completed I/O requests on the completion port using processorinformation in step 2. In the case of this simple example, we create one workerthread per processor because we do not expect our threads to ever get in asuspended condition in which there would not be enough threads to execute foreach processor. When the CreateThreadfunction is called, you must supply a worker routine that the thread executesupon creation. We will discuss the worker thread's responsibilities later inthis section.

4. Prepare a listening socket tolisten for connections on port 5150.

5. Accept inbound connectionsusing the accept function.

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

7. Associate the new socket handlereturned from accept with the completion portby calling CreateIoCompletionPort. Pass theper-handle data structure to CreateIoCompletionPortvia the completion key parameter.

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

9. Repeat steps 5–8 until serverterminates.

 

那么学习完成端口编程从哪里开始比较好,对于初学者而言,直接进入编程并非一个好主意,我建议初学者首先学习用异步Socket模式,即WSAEventSelect模式构建一个简单的聊天服务器。当把Windows网络编程的概念有一个清晰的认识以后,再深刻研究完成端口编程。

接着就是深刻研究具体的编程实现了,从NetworkProgramming 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.   // 在这里还能够加入其余和客户端关联的数据
13. } PER_HANDLE_DATA, * LPPER_HANDLE_DATA;
14.  
15. // 初始化Windows Socket 2.2
16. StartWinsock(MAKEWORD(2,2), &wsd);
17.  
18. // Step 1:
19. // 建立完成端口
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: 等待客户端链接,而后将客户端Socket加入完成端口
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: 初始化客户端数据
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: 发出对客户端的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)
    {
        // 等待完成端口消息,未收到消息德时候则阻塞线程
          
        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);
    }

}

 

 

3.3 小节

 

       讲这么点就完了?你必定认为我介绍的东西并无超过原书中的内容,实事上完成端口编程的精髓就是上面的代码和原书中的有关叙述。若是我再把他们完整的重复一遍,那又有什么意思呢?根据个人经验,设计网络服务器的真正难点,不在于完成端口技术,因此我想利用小节把本身编程中的一些经验告诉你们。

       首先是服务器的管理,一个服务器首先要分析它的设计目标是应对不少的链接仍是很大的数据传送量。这样在设计工做者线程时就能够最大限度的提升性能。管理客户端方面,咱们能够将客户端的数据捆绑到Perhand-Data数据结构上,若是还有须要,能够建一个表来记录客户端的宏观状况。

       在Ares引擎中,我将文件传送和大容量数据传送功能也封装进了服务器和客户端。我建议服务器和客户端都应该封装这些功能,尽管咱们并非作FTP服务器,可是当客户端须要和服务器交换文件和大块数据时,你会发现这样作,灵活性和性能都能作得比用单纯的FTP协议来更好,因此在你的服务器和客户端能够传送数据包之后,把他们都作进去吧。

       为了服务器不被黑客攻击,或被BUG弄崩溃,咱们还须要认真设计服务器的认证机制,以及密切注意程序中的溢出,必定要在每个使用缓冲区的地方加上检查代码。能够说并无现成的办法来解决这个问题,否则就没有人研究网络安全了,因此咱们要作的是尽可能减小错误,即便出现错误也不会形成太大损失,在发现错误的时候可以很快纠正同类错误。

       还有就是对客户端状况的检测,好比客户端的正常和非正常断开链接。若是不注意这一点,就会形成服务器资源持续消耗而最终崩溃,由于咱们的服务器不可能老是重启,而是要持续的运行,越久越好。还有好比客户端断开链接后又尝试链接,可是在服务器看来这个客户“仍然在线“,这个时候咱们不能单纯的拒绝客户端的链接,也不能单纯的接收。

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

 

 

 

 

第四章 做者的话

 

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

有但愿交流的朋友,但愿合做开发的朋友,有项目委托的朋友。。。联系我。

相关文章
相关标签/搜索