摘要

有時候,您必須傳送緊急的帶外(OOB)資料給您的接收器。 接收器是任何使用者或接收資料的任何應用程式。 您想要將此 OOB 資料視為比您可能傳送的任何一般資料都是更高優先順序的資料。 如果您想要傳送的 OOB 資料是一個位元組,您可以使用 select 函數來尋找 OOB 資料。 您可以使用 [ 接收 ] 函數來讀取資料。不過,在傳輸控制通訊協定(TCP)中,OOB 資料區塊永遠是一個位元組。 因此,如果您傳送多位元組的 OOB 資料,只會檢索 OOB 資料的最後一個位元組。 剩餘的資料就會視為一般資料。本文使用範例程式碼來描述如何使用 Microsoft Windows Socket (Winsock)傳送多位元組 OOB 資料。

本文內容

簡介

本文說明如何使用 Winsock 傳送多個位元組的帶外資料(chars)。 範例應用程式是在 Microsoft Visual c + + 中建立的。因為在 Microsoft Windows 通訊端層級不會直接支援此 OOB 機制,所以您必須在應用程式層級執行此 OOB 機制。 這不是真正的 OOB 資料。 在這個範例應用程式中,您會在用戶端建立兩個通訊端(又稱為寄件者端),以傳送 OOB 資料和一般資料。 在伺服器端(也稱為接收方),您可以使用兩個通訊端來處理兩個執行緒中的資料。 一個執行緒是用於 OOB 資料。 另一個執行緒就是一般資料。 若要模擬 OOB 機制,您必須同步處理這兩個執行緒。 請確定處理 OOB 資料的執行緒的優先順序高於處理一般資料的執行緒。注意: 這個範例應用程式說明傳送多位元組 OOB 資料的一種方式。 您可能必須修正程式碼,才能讓程式碼在您的環境中運作。回到頁首

建立用戶端應用程式

  1. 建立用戶端應用程式。 在此用戶端應用程式中,下列範例程式碼說明如何建立兩個通訊端:

    SOCKET myOOBSocket = INVALID_SOCKET;SOCKET myNormalSocket = INVALID_SOCKET;myOOBSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);myNormalSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

    MyOOBSocket變數是用來傳送 OOB 資料。 MyNormalSocket變數是用來傳送一般資料。

  2. 因為 myOOBSocket 變數所傳送的資料不是真正的 OOB 資料,所以您必須在開始傳送資料之前,告訴伺服器應該要傳送哪一種資料。 下列範例程式碼說明如何傳送初始字元,以通知伺服器要傳送哪種類型的資料。 針對 OOB 資料使用 "U",並針對一般資料使用 "N"。

    int nErr, nSize;nErr = connect(myNormalSocket, (SOCKADDR *)&remoteIp, sizeof(remoteIp));//Look for a potential error here.//"remoteIp" is the remote address.nSize = send(myNormalSocket, "N",1, 0);//Look for a potential error here.nErr = connect(myOOBSocket, (SOCKADDR *)&remoteIp, sizeof(remoteIp));//Look for a potential error here.nSize = send(myOOBSocket,"U",1, 0);//Look for a potential error here.
  3. 下列範例程式碼說明如何讓用戶端應用程式監視來自使用者的輸入。 用戶端應用程式將大寫字元作為 OOB 資料傳送。 重複每個輸入字元以組成多位元組字元串。

    for (;;) {int ch = _getch();_putch( ch );if (ch=='.') {shutdown(myOOBSocket, 2);        shutdown(myNormalSocket,2);break;}        char buf[10];        memset(buf, ch, sizeof(buf));        if (isupper(ch)) {nSize = send(myOOBSocket, buf, sizeof(buf), 0);//Do not use the MSG_OOB flag. Send it as normal data.} else {nSize = send(myNormalSocket, buf, sizeof(buf), 0);}...}

顯示用戶端應用程式的範例代碼,請 Myclient .cpp 回到頂端

建立伺服器應用程式

  1. 在伺服器端,下列範例程式碼說明如何建立監聽器通訊端來監視通訊所用的埠:

    int nErr;SOCKET myListener = INVALID_SOCKET;myListener = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);//Look for a potential return error here.nErr = bind(myListener,(SOCKADDR *)&localIp,sizeof(localIp));//Look for a potential return error here.//"localIp" is the local address of the server.nErr = listen(myListener, 5);//Look for a potential return error here.
  2. 下列範例程式碼說明如何呼叫 accept 函數,讓監聽器通訊端等待傳入的連線嘗試:

    for (;;) {struct sockaddr_in remoteIp;SOCKET remoteSocket = INVALID_SOCKET;int nAddrLen = sizeof(SOCKADDR);remoteSocket = accept(myListener, (SOCKADDR *)(&remoteIp), &nAddrLen);//"remoteIp" is a pointer to a buffer that receives //the address of the connecting entity.BYTE buffer[1+1];int recv_len=recv(remoteSocket,(char *)&buffer,1,0);//This supposes that the client sockets will send//"U" or "N" as the initial character.    buffer[recv_len] = '\0';if(strcmp((char *)buffer,"U")==0)hThread=CreateThread(0,0,OOBHandler,(LPVOID)remoteSocket, 0, &dwThreadId);//Create a new thread to process incoming OOB data. "OOBHandler" is a //pointer to the OOB data handler. else if(strcmp((char *)buffer,"N")==0)hThread=CreateThread(0,0,NormalHandler,(LPVOID)remoteSocket, 0, &dwThreadId);//Create a new thread to process incoming normal data. "NormalHandler" is //a pointer to the normal data handler....}
  3. 若要模擬 OOB 機制,您必須同步處理這兩個執行緒。 在這個範例應用程式中,請使用全域 事件 物件來執行這項作業。 將下列語句放在檔案的頂端。

    //Create a manual-reset event object. hRecvEvent=CreateEvent(NULL,TRUE,TRUE,"RecvEvent");
  4. 建立由個別執行緒執行的 OOB 處理常式。 在此處理程式中,您可以使用 select 函數來偵測資料何時到達。 當資料到達時,請遵循下列步驟:

    1. 呼叫 ResetEvent 函數,將 hRecvEvent 事件物件設定為非終止狀態。

      ResetEvent(hRecvEvent);

      這會封鎖在另一個執行緒中執行的一般資料處理程式。

    2. 呼叫 revc 函數來讀取資料。

      recv(remoteSocket,(char *)&buffer,sizeof(buffer) - 1,0);
    3. 因為可能會傳送超過緩衝區所能保留的資料,所以請再次呼叫 接收 函式,並與 MSG_PEEK 旗標一起判斷是否有任何資料可在網路上使用。

      recv(remoteSocket,(char *)&buffer,sizeof(buffer)-1,MSG_PEEK);

      根據是否有要讀取的資料,使用下列其中一種方法:

      • 如果沒有要讀取的資料,請呼叫 SetEvent 函數,將指定的事件物件設定為終止狀態。

        SetEvent(hRecvEvent);

        當您這麼做時,一般的資料處理程式執行緒可以繼續執行。 在正常的資料處理程式執行緒繼續進行之前,一般的資料處理程式執行緒會等待 hRecvEvent 事件物件。

      • 如果仍有要讀取的資料,請再次呼叫 接收 函數,以讀取剩餘的資料。 接著,請再次查看擱置中的資料。

    重複步驟4a 到4c,直到已讀取所有擱置中的資料為止。

  5. 建立一般的資料處理程式。 此處理程式與 OOB 資料處理程式類似,不同的是,一般資料處理程式會在一般資料處理程式偵測到已到達資料時,呼叫 WaitForSingleObject 函數。

    WaitForSingleObject(hRecvEvent,INFINITE);//You can set the interval parameter to a specific value if you do not want  //the normal data handler to wait for the OOB handler indefinitely when OOB data arrives continuously.

伺服器應用程式的範例代碼(Myserver)顯示回到頂端

顯示用戶端應用程式的範例代碼,Myclient .cpp

注意: 您必須包含對 Winsock 文件庫檔案的參照,Ws2_32 .lib,才能編譯器代碼。

#include <stdio.h>#include <stdlib.h>#include <conio.h>#include <assert.h>#include <winsock.h>main(int argc, char *argv[]){int nErr, nSize;WSADATA wsaData;SOCKET myOOBSocket = INVALID_SOCKET;SOCKET myNormalSocket = INVALID_SOCKET;unsigned short nPort; // Listen for the port number.struct sockaddr_in remoteIp;if (argc!=3){printf("TcpDemoC <RemoteIp> <Port>\n");return 0;}// The Init Socket API.nErr = WSAStartup(MAKEWORD(1,1),&wsaData);assert(nErr==0);nPort = (unsigned short)atol(argv[2]);myOOBSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);assert(myOOBSocket!=INVALID_SOCKET);myNormalSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);assert(myNormalSocket!=INVALID_SOCKET);// Connect to the remote address.remoteIp.sin_family = AF_INET;remoteIp.sin_port = htons(nPort);;remoteIp.sin_addr.S_un.S_addr = inet_addr(argv[1]);nErr = connect(myOOBSocket, (SOCKADDR *)&remoteIp, sizeof(remoteIp));if (nErr == SOCKET_ERROR) {printf("Connect failed because of %lX\n",  WSAGetLastError());goto Cleanup;}nSize = send(myOOBSocket,"U",1, 0);if (nSize == SOCKET_ERROR) {printf("Send failed because of %lX\n", WSAGetLastError());        goto Cleanup;}    nErr = connect(myNormalSocket, (SOCKADDR *)&remoteIp, sizeof(remoteIp));if (nErr == SOCKET_ERROR) {int error = WSAGetLastError();printf("Connect failed because of %lX\n", error);goto Cleanup;}    nSize = send(myNormalSocket, "N",1, 0);if (nSize == SOCKET_ERROR) {printf("Send failed because of %lX\n", WSAGetLastError());goto Cleanup;}printf("Read for input:\n");for (;;) {int ch = _getch();_putch( ch );if (ch=='.') {shutdown(myOOBSocket, 2);shutdown(myNormalSocket,2);break;}char buf[10];memset(buf, ch, sizeof(buf));if (isupper(ch)) {nSize = send(myOOBSocket, buf, sizeof(buf), 0);}else{nSize = send(myNormalSocket, buf, sizeof(buf), 0);}if (nSize == SOCKET_ERROR) {printf("Send failed because of %lX\n", WSAGetLastError());break;}}Sleep(1000);Cleanup:if (myOOBSocket!=INVALID_SOCKET)closesocket(myOOBSocket);if (myNormalSocket!=INVALID_SOCKET)closesocket(myNormalSocket);WSACleanup();return 0;}

回到頁首

顯示伺服器應用程式(Myserver)的範例程式碼。

注意: 您必須包含對 Winsock 文件庫檔案的參照,Ws2_32 .lib,才能編譯器代碼。

#include <windows.h>#include <stdio.h>#include <stdlib.h>#include <winsock.h>#include <assert.h>// Usage: myserver <port>HANDLE hRecvEvent; DWORD WINAPI OOBHandler(LPVOID lpParam){int nErr, nRecv_len;BYTE buffer[10 + 1];BOOL bClosing=FALSE;fd_set fdread;SOCKET remoteSocket=(SOCKET)lpParam;unsigned long ul=1;int nRet = ioctlsocket(remoteSocket, FIONBIO, &ul);if(SOCKET_ERROR==nRet){printf("Socket() failed: %d\n", WSAGetLastError());return 1;}printf("Waiting");for (int i=0; i<10; i++) {printf(".");Sleep(1000);}printf("The OOB handler is ready!\n");while (!bClosing){//Always clear the set before you call the select method.FD_ZERO(&fdread);//Add sockets to the sets.FD_SET(remoteSocket, &fdread);nErr=select(0,&fdread,0,0,0);if(nErr==SOCKET_ERROR){printf("Select() failed: %d\n",WSAGetLastError());return 1;}if(FD_ISSET(remoteSocket,&fdread)){ResetEvent(hRecvEvent);while(1){nRecv_len=recv(remoteSocket,(char *)&buffer,sizeof(buffer) - 1,0);                    if(nRecv_len==0){bClosing=TRUE;printf("The connection is closed!\n");break;}buffer[nRecv_len] = '\0';printf("[OOB]: %s\n", buffer);                    nRecv_len=recv(remoteSocket,(char *)&buffer,sizeof(buffer)-1,MSG_PEEK);if (nRecv_len==SOCKET_ERROR) {if(WSAGetLastError()==WSAEWOULDBLOCK)break;elseprintf("Recv() failed. Win32 error is 0x%lx\n", WSAGetLastError());}}SetEvent(hRecvEvent);} }return 0;}DWORD WINAPI NormalHandler(LPVOID lpParam){int nErr,nRecv_len;BYTE buffer[10 + 1];fd_set fdread;SOCKET remoteSocket=(SOCKET)lpParam;printf("Waiting");for (int i=0; i<10; i++) {        printf("*");Sleep(1000);}printf("Normal handler ready!\n");while(1) {//Always clear the set before you call the select method.FD_ZERO(&fdread);//Add sockets to the sets.FD_SET(remoteSocket, &fdread);nErr=select(0,&fdread,0,0,0);if(nErr==SOCKET_ERROR){printf("Select() failed: %d\n",WSAGetLastError());return 1;}if(FD_ISSET(remoteSocket,&fdread)){WaitForSingleObject(hRecvEvent,INFINITE);nRecv_len=recv(remoteSocket,(char *)&buffer,sizeof(buffer) - 1,0);            if (nRecv_len==SOCKET_ERROR) {printf("Recv() failed. Win32 error is 0x%lx\n", WSAGetLastError());return 1;}            if(nRecv_len==0){printf("Connection Closed!\n");break;}       buffer[nRecv_len] = '\0';printf("[Normal]: %s\n", buffer);}}return 0;}int main(int argc, char *argv[]){WSADATA wsaData;int nErr;SOCKET myListener = INVALID_SOCKET;struct sockaddr_in localIp;unsigned short nPort; DWORD dwThreadId;HANDLE hThread=NULL;if (argc!=2){printf("MyServer <Port>\n");return 0;}nPort = (unsigned short)atol(argv[1]);nErr = WSAStartup(MAKEWORD(2,0),&wsaData);assert(nErr==0);assert(wsaData.wVersion == MAKEWORD(2,0));myListener = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);assert(myListener!=INVALID_SOCKET);// Bind the listen socket to any local IP address.localIp.sin_family = AF_INET;localIp.sin_port = htons(nPort);;localIp.sin_addr.S_un.S_addr = INADDR_ANY;nErr = bind(myListener,(SOCKADDR *)&localIp,sizeof(localIp));assert(nErr!=SOCKET_ERROR);nErr = listen(myListener, 5);assert(nErr==0);//Create a manual-reset event object. hRecvEvent=CreateEvent(NULL,TRUE,TRUE,"RecvEvent");if (hRecvEvent == NULL) { printf("CreateEvent failed with error 0x%lx\n", GetLastError());return 1;}printf("The server is ready!\n");for (;;) {struct sockaddr_in remoteIp;SOCKET remoteSocket = INVALID_SOCKET;int nAddrLen = sizeof(SOCKADDR);remoteSocket = accept(myListener, (SOCKADDR *)(&remoteIp), &nAddrLen);if(remoteSocket == INVALID_SOCKET) {int error = WSAGetLastError();printf("Accept() failed. Win32 error is 0x%lx\n", GetLastError());goto Cleanup;} else {printf("Connected from %d.%d.%d.%d:%d\n", remoteIp.sin_addr.S_un.S_un_b.s_b1,remoteIp.sin_addr.S_un.S_un_b.s_b2,remoteIp.sin_addr.S_un.S_un_b.s_b3,remoteIp.sin_addr.S_un.S_un_b.s_b4,ntohs(remoteIp.sin_port));BYTE buffer[1+1];int nRecv_len=recv(remoteSocket,(char *)&buffer,1,0);if (nRecv_len==SOCKET_ERROR) {printf("Recv() failed. Win32 error is 0x%lx\n", WSAGetLastError());return 1;}    buffer[nRecv_len] = '\0';if(strcmp((char *)buffer,"U")==0)hThread=CreateThread(0,0,OOBHandler,(LPVOID)remoteSocket, 0, &dwThreadId);else if(strcmp((char *)buffer,"N")==0)hThread=CreateThread(0,0,NormalHandler,(LPVOID)remoteSocket, 0, &dwThreadId);           if(hThread==0){printf("CreateThread() failed: %d\n",GetLastError());return 1;}CloseHandle(hThread);}}closesocket(myListener);Cleanup:WSACleanup();return 0;}

回到頁首

測試範例

  1. 在命令提示字元中,輸入 Myserver 4444 來啟動埠4444上的伺服器。伺服器應用程式會顯示下列訊息,然後等待用戶端:

    伺服器已就緒!

  2. 在不同的 [命令提示字元] 視窗中,輸入MyClient IPAddress 4444 以啟動用戶端。 注意: 預留位置 [ IPAddress ] 是伺服器 IP 位址的預留位置。

  3. 伺服器會顯示如下所示的訊息:

    Waiting.Waiting*..*當您收到前一封郵件時,您必須在用戶端的10秒內輸入 aBcDeFgHi ,然後才能繼續進行。

大約10秒之後,伺服器會顯示下列資訊:

[OOB]: BBBBBBBBBB [OOB]: DDDDDDDDDD [OOB]: FFFFFFFFFF [OOB]: HHHHHHHHHH [Normal]: aaaaaaaaaa [Normal]: cccccccccc [normal]: eeeeeeeeee [Normal]: gggggggggg [normal]: iiiiiiiiii回到頁首

參考

如需其他資訊,請造訪下列 Microsoft 開發人員網路(MSDN)網站:

獨立通訊協定的帶外資料Http://msdn2.microsoft.com/en-us/library/ms740102.aspxWinsock 函數HTTP://msdn2.microsoft.com/en-us/library/ms741394.aspx如需其他資訊,請按一下下列文章編號,以查看 Microsoft 知識庫中的文章:

331756 Ioctlsocket 函數無法偵測內嵌帶外資料回到頂端

Need more help?

Expand your skills
Explore Training
Get new features first
Join Microsoft Insiders

Was this information helpful?

How satisfied are you with the translation quality?
What affected your experience?

Thank you for your feedback!

×