• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆 - 298  文章 - 377  trackbacks - 0
            <2017年10月>
            24252627282930
            1234567
            891011121314
            15161718192021
            22232425262728
            2930311234

            常用鏈接

            留言簿(34)

            隨筆分類

            隨筆檔案

            文章檔案

            相冊

            收藏夾

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            第 1 樓:細說WinSock IOCP


            1.Socket Modes",什么是"Socket I/O Models"?
            Socket Modes : Determines how winsock functions behave when called with a socket.
            Socket I/O Models : Describes how an application manages and processes I/O on a socket.

            2.那么Winsock提供了哪幾種"Socket Modes"給程序員用呢?
              a.Blocking Mode(阻塞式的)
              b.Noblocking Mode(非阻塞式的)

            3.詳細解釋一下阻塞和非阻塞.
            我們那最常用的send和recv兩個函數(shù)來說吧...
            比如你調(diào)用send函數(shù)發(fā)送一定的Byte,在系統(tǒng)內(nèi)部send做的工作其實只是把數(shù)據(jù)傳輸(Copy)到TCP/IP協(xié)議棧的輸出緩沖區(qū),它執(zhí)行成功并不代表數(shù)據(jù)已經(jīng)成功的發(fā)送出去了,如果TCP/IP協(xié)議棧沒有足夠的可用緩沖區(qū)來保存你Copy過來的數(shù)據(jù)的話...這時候就體現(xiàn)出阻塞和非阻塞的不同之處了:對于阻塞模式的socket send函數(shù)將不返回直到系統(tǒng)緩沖區(qū)有足夠的空間把你要發(fā)送的數(shù)據(jù)Copy過去以后才返回,而對于阻塞的socket來說send會立即返回WSAEWOULDDBLOCK告訴調(diào)用者說:"發(fā)送操作被阻塞了!!!你想辦法處理吧..."
            對于recv函數(shù),同樣道理,該函數(shù)的內(nèi)部工作機制其實是在等待TCP/IP協(xié)議棧的接收緩沖區(qū)通知它說:嗨,你的數(shù)據(jù)來了.對于阻塞模式的socket來說如果TCP/IP協(xié)議棧的接收緩沖區(qū)沒有通知一個結(jié)果給它它就一直不返回:耗費著系統(tǒng)資源....對于阻塞模式的socket該函數(shù)會馬上返回,然后告訴你:WSAEWOULDDBLOCK.

            3.對于阻塞模式(Blocking Mode)我采取開線程的方式不就搞定了嗎,而且還不用我去判斷基本的收/發(fā)什么時候完成,多省心?
              沒錯,這位大蝦說的開線程的方式具體的做法如下:我們將應(yīng)用劃分成一個讀線程(Reading Thread)一個工作線程(Working Thread),兩個線程*同步對象(Synchronization Object,可以是Event,Mutex...)來共享一個數(shù)據(jù)緩沖區(qū),讀線程(Reading Thread)負責(zé)從網(wǎng)
            絡(luò)讀取數(shù)據(jù)并將其放到共享緩沖區(qū)中,在適當(dāng)?shù)臅r機(比如說讀線程將工作線程需要的數(shù)據(jù)接收下來之后)觸發(fā)一個事件,通知/喚醒工作線程(Working Thread),下面是上述文字的簡單代碼實現(xiàn):

            CRITICAL_SECTION g_data;
            HANDLE          g_hEvent;
            TCHAR           g_szBuffer[MAX_BUFFER_SIZE];
            int               g_nBytes;

            void ReadThread(void)
            {
                int nTotle = nRead = nLeft = nBytes = 0;

                   while(!done)
                  {
                    nTotal = 0;
                     nLeft = NUM_BYTES_REQUIRED; //要讀多少的Bytes

                      while( nTotal != NUM_BYTE_REQUIRED)
                      {
                            // 因為涉及線程和全局變量所以說需要用到關(guān)鍵代碼段(Critical Section)
                            EnterCriticalSection(&g_data);

                           nRead = recv(sock,&(g_szBuffer[MAX_BUFFER_SIZE] - nBytes),nLeft);

                             if( -1 == nread )
                              {
                                  printf("socket %d error.\n",WSAGetLasError());
                                  ExitThread();
                                }

                                nTotal += nRead;
                                  nLeft -= nRead;

                                 nBytes += nRead;

                                   LeaveCriticalSelection(&g_data);
                         }

                        // 從網(wǎng)絡(luò)接收完指定的(NUM_BYTES_REQUIRED)Bytes之后通知WorkThread啟動
                         SetEvent(g_hEvent);
                 }
            }


            void WorkThread(void)
            {
               WaitForSingleObject(g_hEvent);

                 EnterCriticalSection(&g_data);


                // 主要的處理工作...
                DoSomeWorkOnData(g_szBuffer);

                  g_nBytes -= NUM_BYTE_REQUIRED;

                 LeaveCriticalSelection(&g_data);
            }

              這種方式的一個缺點是:應(yīng)用程序很難同時通過多個建立好連接的socket通信.當(dāng)然話說回來我們可以為每一個連接好的socket都開一個Reading Thread and WorkThread,但是呢這會帶來很大的開銷,而且擴展性很差,試想:如果用這樣的模型建立一個服務(wù)器端來一個連接開至少兩個線程...天..要是業(yè)務(wù)負擔(dān)很大/連接的socket很多的話怎么辦??所以說別多想了...
               這樣的方式是很不適合做服務(wù)器端的.(注意:我說的服務(wù)器端是指C/S中的S端邏輯).

               從編寫代碼的角度解讀一下IO中最復(fù)雜的IOCP模型

               下面的一段代碼是Microsoft Win32 SDK里面的例子,這是一個Echo服務(wù)器程序:

              //
              // 這就不說了,為了使用Winsock2必須包含的頭文件...
              //
            #include
            #include
            #include

            #define PORT 5150 // 這個服務(wù)器選擇5150端口
            #define DATA_BUFSIZE 8192 // 把Buffer定義為8K是一個比較被Microsoft推薦的大小

              //
              // 這個結(jié)構(gòu)體很重要,請一定看清楚,到后面用到的時候別糊涂了
              // 特別注意:把Overlapped放最開頭是有原因的,你應(yīng)該明確的是
              // 如果有:
              // PER_IO_OPERATION_DATA PerIoData;
              // &PerIoData->Overlapped 和 &PerIoData是一樣的/等價的/
              // 指向同一塊內(nèi)存區(qū)域的.
              //
              typedef struct
              {
               OVERLAPPED Overlapped;
               WSABUF DataBuf;
               CHAR Buffer[DATA_BUFSIZE];
               DWORD BytesSEND; // 下面兩個參數(shù)是用來記錄每個connected socket上面的收發(fā)的Bytes的
               DWORD BytesRECV;
              } PER_IO_OPERATION_DATA, * LPPER_IO_OPERATION_DATA;

              // 別把這個結(jié)構(gòu)體和上面的結(jié)構(gòu)體混淆了,名字很像的...其實它在這個程序中就相當(dāng)于是那個和Client端連接的socket
              typedef struct
              {
               SOCKET Socket;
              } PER_HANDLE_DATA, * LPPER_HANDLE_DATA;


              DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID);

              void main(void)
              {
               SOCKADDR_IN InternetAddr;
               //
               // 一個listen和一個Accept socket是服務(wù)器程序的老套路了...
               //
               SOCKET Listen;
               SOCKET Accept;

               //
               // IO完成端口映射到程序就是一個HANDLE
               //
               HANDLE CompletionPort;
               SYSTEM_INFO SystemInfo;

               //
               // 看清楚,這是指針...不怕你笑話我們處還真有分不清和指針相關(guān)的一切概念的人,比如下面的代碼:
               // 我寫了一個DLL,里面有一個這樣的函數(shù):
               // BOOL GetPlayingPosition(DWORD * dwPos);
               // 結(jié)果大蝦調(diào)用的時候是這樣的....
               // DWORD * p;
               // GetPlayingPostion(p);
               // 它是調(diào)用爽了...我呢...:-(
               // 當(dāng)然了,也不好定義說它就是分不清指針,也許人家就不會寫程序呢..對吧..
               //
               LPPER_HANDLE_DATA PerHandleData;
               LPPER_IO_OPERATION_DATA PerIoData;

               int i;
               DWORD RecvBytes;
               DWORD Flags;
               DWORD ThreadID;
               WSADATA wsaData;
               DWORD Ret;

               // 就像CoInitialize()一樣的把Winsock 2.2服務(wù)啟動起來...
               if ((Ret = WSAStartup(0x0202, &wsaData)) != 0)
               {
                printf("WSAStartup failed with error %d\n", Ret);
                return;
               }

               // CreateIoCompletionPort有兩個不同的用途:1.創(chuàng)建完成端口;2.把已經(jīng)創(chuàng)建好的完成端口和一個Socket關(guān)聯(lián)/綁定起來.
               // 下面這句話是它的第一個用途:創(chuàng)建完成端口
               if ((CompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)) == NULL)
               {
                printf( "CreateIoCompletionPort failed with error: %d\n", GetLastError());
                return;
               }

               //
               // 下面這一些代碼說得是:根據(jù)該服務(wù)器跑的機器上有幾個CPU建立2*CPU數(shù)量的Thread.
               // 這涉及到完成端口工作線程應(yīng)該創(chuàng)建多少個的問題,在早期(MSDN 2000)Microsoft說
               // 是WorkThreadNums = 2 * CPU + 2,現(xiàn)在的MSDN 2003說是WorkThreadNums = 2 * CPU;
               //
               GetSystemInfo(&SystemInfo);

               for(i = 0; i < SystemInfo.dwNumberOfProcessors * 2; i++)
               {
                HANDLE ThreadHandle;

                // 創(chuàng)建一個線程并把上面創(chuàng)建的完成端口傳給線程
                if ((ThreadHandle = CreateThread(NULL, 0, ServerWorkerThread, CompletionPort,
                 0, &ThreadID)) == NULL)
                {
                 printf("CreateThread() failed with error %d\n", GetLastError());
                 return;
                }

                // 這個風(fēng)格我喜歡,創(chuàng)建完Thread就把人家的Handle給release,省得要是線程異常退出搞得資源泄漏之類的麻煩事情.
                CloseHandle(ThreadHandle);
               }

               // 開始創(chuàng)建監(jiān)聽Socket...
               if ((Listen = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0,WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
               {
                printf("WSASocket() failed with error %d\n", WSAGetLastError());
                return;
               }

               InternetAddr.sin_family = AF_INET;
               InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);
               InternetAddr.sin_port = htons(PORT);

               // 把監(jiān)聽socket和綁定TCP/IP協(xié)議棧做一個綁定...
               if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)
               {
                printf("bind() failed with error %d\n", WSAGetLastError());
                return;
               }

               // 設(shè)置一次能接受5個連接
               if (listen(Listen, 5) == SOCKET_ERROR)
               {
                printf("listen() failed with error %d\n", WSAGetLastError());
                return;
               }

               //
               // 下面是服務(wù)器程序標(biāo)準(zhǔn)的循環(huán)了...
               //
               while(TRUE)
               {
                if ((Accept = WSAAccept(Listen, NULL, NULL, NULL, 0)) == SOCKET_ERROR)
                {
                 printf("WSAAccept() failed with error %d\n", WSAGetLastError());
                 return;
                }

                //
                // 接收到一個連接就new一個新的socket并承攬下連接進來的socket的接下來的接待/處理工作
                // 為什么不用new呢?我想是因為用Windows提供的內(nèi)存管理函數(shù),這樣的話在業(yè)務(wù)量大的情況下
                // 出現(xiàn)內(nèi)存匱乏的情況下不至于直接就異常了,怎么著也就是該次的內(nèi)存分配失敗...
                //
                if ((PerHandleData = (LPPER_HANDLE_DATA) GlobalAlloc(GPTR, sizeof(PER_HANDLE_DATA))) == NULL)
                {
                 printf("GlobalAlloc() failed with error %d\n", GetLastError());
                 return;
                }

                // Associate the accepted socket with the original completion port.

                printf("Socket number %d connected\n", Accept);
                PerHandleData->Socket = Accept;

                //
                // 把accept socket和IO完成端口關(guān)聯(lián)/綁定起來,并把accept socket傳給IOCP機制這樣的話
                // 當(dāng)調(diào)用GetQueuedCompletionStatus的時候就可以得到該值.
                //
                if (CreateIoCompletionPort((HANDLE) Accept, CompletionPort, (DWORD) PerHandleData,
                 0) == NULL)
                {
                 printf("CreateIoCompletionPort failed with error %d\n", GetLastError());
                 return;
                }

                // 下面這個結(jié)構(gòu)體也是會被傳到工作線程中去的,怎么傳呢?注意看下面紅色部分
                if ((PerIoData = (LPPER_IO_OPERATION_DATA) GlobalAlloc(GPTR,sizeof(PER_IO_OPERATION_DATA))) == NULL)
                {
                 printf("GlobalAlloc() failed with error %d\n", GetLastError());
                 return;
                }

                ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));
                PerIoData->BytesSEND = 0;
                PerIoData->BytesRECV = 0;
                PerIoData->DataBuf.len = DATA_BUFSIZE;
                PerIoData->DataBuf.buf = PerIoData->Buffer;

                Flags = 0;

                //
                // 在accept socket上面發(fā)起IO請求:WSARecv()以此激發(fā)IOCP工作機制..
                // 注意WSARecv的第五個參數(shù),也就是說我這時候告訴WSARecv的不僅僅是
                // PerIoData->Overlapped的地址同時也是PerIoData的地址...
                //
                if (WSARecv(Accept, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
                 &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
                {
                 if (WSAGetLastError() != ERROR_IO_PENDING)
                 {
                  printf("WSARecv() failed with error %d\n", WSAGetLastError());
                  return;
                 }
                }
               }
              }

              //
              // 工作線程
              //
              DWORD WINAPI ServerWorkerThread(LPVOID CompletionPortID)
              {
               HANDLE CompletionPort = (HANDLE) CompletionPortID;
               DWORD BytesTransferred;
               LPOVERLAPPED Overlapped;
               LPPER_HANDLE_DATA PerHandleData;
               LPPER_IO_OPERATION_DATA PerIoData;
               DWORD SendBytes, RecvBytes;
               DWORD Flags;

               while(TRUE)
               {

                //
                // 這個函數(shù)解釋清楚了整個機制也就明白了...
                // 首先我們看CompletionPort應(yīng)該沒有問題,是從創(chuàng)建線程的主線程傳過來的.
                // BytesTransferred指明了這次IO傳輸了多少Bytes...
                // PerHandleData是CreateIoCompletionPort的時候穿過來的,MSDN里面叫CompletionKey.
                // 請注意PerIoData這個參數(shù),本來按照MSDN的解釋如下:
                // "Pointer to a variable that receives the address of the OVERLAPPED structure that was
                // specified when the completed I/O operation was started."
                // 其實在這這個lpOverlapped傳過來的就是上面WSARecv傳入的overlapped的地址...
                // 這下明白了吧:主線程借著overlapped的名聲傳了一個結(jié)構(gòu)體指針過來..:-)
                //
                if (GetQueuedCompletionStatus(CompletionPort, &BytesTransferred,
                 (LPDWORD)&PerHandleData, (LPOVERLAPPED *) &PerIoData, INFINITE) == 0)
                {
                 printf("GetQueuedCompletionStatus failed with error %d\n", GetLastError());
                 return 0;
                }


                //
                // First check to see if an error has occured on the socket and if so
                // then close the socket and cleanup the SOCKET_INFORMATION structure
                // associated with the socket.
                //

                if (BytesTransferred == 0)
                {
                 printf("Closing socket %d\n", PerHandleData->Socket);

                 if (closesocket(PerHandleData->Socket) == SOCKET_ERROR)
                 {
                  printf("closesocket() failed with error %d\n", WSAGetLastError());
                  return 0;
                 }

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

                //
                // Check to see if the BytesRECV field equals zero. If this is so, then
                // this means a WSARecv call just completed so update the BytesRECV field
                // with the BytesTransferred value from the completed WSARecv() call.
                //

                if (PerIoData->BytesRECV == 0)
                {
                 PerIoData->BytesRECV = BytesTransferred;
                 PerIoData->BytesSEND = 0;
                }
                else
                {
                 PerIoData->BytesSEND += BytesTransferred;
                }

                if (PerIoData->BytesRECV > PerIoData->BytesSEND)
                {

                 //
                 // Post another WSASend() request.
                 // Since WSASend() is not gauranteed to send all of the bytes requested,
                 // continue posting WSASend() calls until all received bytes are sent.
                 //
                 ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

                 PerIoData->DataBuf.buf = PerIoData->Buffer + PerIoData->BytesSEND;
                 PerIoData->DataBuf.len = PerIoData->BytesRECV - PerIoData->BytesSEND;

                 if (WSASend(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &SendBytes, 0,
                  &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
                 {
                  if (WSAGetLastError() != ERROR_IO_PENDING)
                  {
                   printf("WSASend() failed with error %d\n", WSAGetLastError());
                   return 0;
                  }
                 }
                }
                else
                {
                 PerIoData->BytesRECV = 0;

                 // Now that there are no more bytes to send post another WSARecv() request.
                 Flags = 0;
                 ZeroMemory(&(PerIoData->Overlapped), sizeof(OVERLAPPED));

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

                 if (WSARecv(PerHandleData->Socket, &(PerIoData->DataBuf), 1, &RecvBytes, &Flags,
                  &(PerIoData->Overlapped), NULL) == SOCKET_ERROR)
                 {
                  if (WSAGetLastError() != ERROR_IO_PENDING)
                  {
                   printf("WSARecv() failed with error %d\n", WSAGetLastError());
                   return 0;
                  }
                 }
                }
               }
              }

            posted on 2007-10-09 11:57 聶文龍 閱讀(1733) 評論(0)  編輯 收藏 引用 所屬分類: net work
            久久伊人色| 亚洲国产另类久久久精品黑人| 久久综合久久综合九色| 99久久精品国产毛片| 婷婷久久五月天| 久久99国内精品自在现线| 久久久久久青草大香综合精品| 婷婷综合久久中文字幕蜜桃三电影| 色综合久久中文色婷婷| 国产亚洲美女精品久久久2020| 91精品无码久久久久久五月天| 人妻无码αv中文字幕久久琪琪布| 国产成人久久精品区一区二区| 久久久中文字幕日本| AV色综合久久天堂AV色综合在| 久久九九久精品国产| 国产叼嘿久久精品久久| 亚洲成色WWW久久网站| 久久久久久午夜精品| 久久精品亚洲福利| 国产精品久久久久久久午夜片| 久久夜色精品国产噜噜亚洲AV| 久久久久国产精品麻豆AR影院| 91精品国产9l久久久久| 中文无码久久精品| 久久婷婷人人澡人人爽人人爱| 日韩亚洲国产综合久久久| 青青草原综合久久| 久久777国产线看观看精品| 久久久久女人精品毛片| 伊人久久精品无码av一区| 中文字幕无码久久久| 久久人人爽人人人人爽AV | 久久久综合香蕉尹人综合网| 亚洲国产成人久久精品影视| 久久精品水蜜桃av综合天堂| 无码超乳爆乳中文字幕久久| 久久婷婷五月综合97色| 国产精品久久久久久久久免费| 99久久人妻无码精品系列| 九九久久99综合一区二区|