如何使用 Winsock 傳送多位元組 out-of-band 的資料


摘要


有時您必須傳送緊急的 out-of-band (OOB) 資料到您的接收器。接收器,則任何使用者或任何接收資料的應用程式。您要讓此 OOB 資料視為較高優先順序的資料,比您可能會傳送任何標準資料。如果您想要傳送的 OOB 資料是一個位元組,您可以使用選取的函式,以尋找 OOB 資料。您可以使用 [接收函式來讀取資料。

不過,在 「 傳輸控制通訊協定 」 (TCP),OOB 資料區塊永遠是一個位元組。因此,如果您傳送多位元組 OOB 資料時,就會擷取 OOB 資料的最後一個位元組。剩餘的資料都會被視為就像一般的資料一樣。

本文使用範例程式碼來描述如何使用 Microsoft Windows 通訊端 (Winsock) 來傳送多位元組 OOB 資料。

簡介


本文說明如何使用 Winsock 傳送多個位元組 (字元) out-of-band 的資料。範例應用程式被建立在 Microsoft Visual C++。

在 Microsoft Windows 通訊端層級不直接支援這個 OOB 機制,因為您必須實作這個應用程式層級的 OOB 機制。這不是,則為 true 的 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變數所傳送的資料不是,則為 true 的 OOB 資料,您必須擁有一個方法用以告訴伺服器的通訊端是應該在開始傳送資料之前,送出的資料種類。下列範例程式碼說明如何傳送通知將傳送的資料種類的相關伺服器初始字元。使用"U"OOB 資料,並使用標準資料"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. 下列範例程式碼說明如何呼叫接受的函式,以使 [等到進行連入連線嘗試的接聽程式通訊端︰
    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 處理常式。在這個處理常式中,您可以使用 [選取的函式來偵測資料抵達時。當資料抵達時,請依照下列步驟執行︰
    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 到 4 的 c,直到所有待處理的資料被讀取。
  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.cpp 的範例程式碼

回到頁首

顯示用戶端應用程式,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.cpp 的範例程式碼

注意您必須包含 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;
else
printf("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.exe 4444啟動伺服器,連接埠 4444 上。

    伺服器應用程式會顯示下列訊息,然後等待用戶端︰

    伺服器已經準備妥當 !
  2. 在不同的 [命令提示字元] 視窗中,輸入
    myClient IPAddress 4444啟動用戶端。

    注意版面配置區IPAddress是伺服器的 IP 位址的預留位置。
  3. 伺服器會顯示類似下列的訊息︰

    Waiting.Waiting*..*


    當您收到的上一封郵件時,您必須在用戶端輸入aBcDeFgHi ,伺服器會繼續之前的 10 秒內項目。
約 10 秒鐘之後,伺服器顯示以下內容︰
[OOB]: BBBBBBBBBB
[OOB]: DDDDDDDDDD
[OOB]: FFFFFFFFFF
[OOB]: HHHHHHHHHH

[標準模式]: aaaaaaaaaa
[標準模式]: cccccccccc
[標準模式]: eeeeeeeeee

[標準模式]: gggggggggg
[標準模式]: iiiiiiiiii
回到頁首

參考


如需詳細資訊,請造訪下列 Microsoft 開發人員網路 (MSDN) 網站︰如需其他資訊,請按一下下面的文件編號,檢視 「 Microsoft 知識庫 」 中的文件:

331756的 Ioctlsocket 函式無法偵測內嵌 out-of-band 的資料

回到頁首