如何通过使用 Winsock 发送多个字节的带外数据


概要


有时您必须向您接收者发送紧急带外 (OOB) 数据。接收器是任何用户或任何应用程序接收数据。您希望该 OOB 数据视为比任何普通的数据,您可能会发送优先级更高的数据。如果您想要发送的 OOB 数据是 1 个字节,可以使用选择函数寻找 OOB 数据。接收函数可用于读取数据。

但是,在传输控制协议 (TCP) 的 OOB 数据块始终是一个字节。因此,如果您发送多个字节 OOB 数据,只有最后一个字节的 OOB 数据检索。其余的数据被视为普通数据。

本文使用的示例代码来描述如何通过使用 Microsoft Windows 套接字 (Winsock) 发送多个字节 OOB 数据。

简介


本文介绍如何通过使用 Winsock 发送多个字节 (字符) 的带外数据。在 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 数据,必须有一种方法,来判断哪种类型的套接字是应该在开始发送数据之前发送的数据的服务器。下面的代码示例说明如何发送通知有关何种数据将发送服务器初始字符。"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,4c 通过。
  5. 创建普通的数据处理程序。只是普通的数据处理程序调用WaitForSingleObject函数,当普通的数据处理程序检测到已收到数据时,此处理程序类似于 OOB 数据处理程序。
    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 ip 地址4444启动客户端。

    注意Ip 地址的占位符是服务器的 IP 地址的占位符。
  3. 服务器将显示类似于以下的消息︰

    Waiting.Waiting*..*


    当您收到上一封邮件时,必须在 10 秒内服务器继续之前客户端上键入aBcDeFgHi
约 10 秒钟后,服务器将显示如下︰
[OOB]: BBBBBBBBBB
[OOB]: DDDDDDDDDD
[OOB]: FFFFFFFFFF
[OOB]: HHHHHHHHHH

[法]: aaaaaaaaaa
[法]: cccccccccc
[法]: eeeeeeeeee

[法]: gggggggggg
[法]: iiiiiiiiii
返回页首

参考资料


有关其他信息,请访问下面的 Microsoft 开发人员网络 (MSDN) Web 站点︰有关其他信息,请单击下面的文章编号,以查看 Microsoft 知识库中相应的文章:

331756 Ioctlsocket 函数无法检测到内联带外数据

返回页首