Cómo enviar datos de varios bytes fuera de banda con Winsock


Resumen


A veces debe enviar los datos urgentes de fuera de banda (OOB) a sus receptores. Receptores son los usuarios o las aplicaciones que reciben los datos. Desea que estos datos OOB tratados como datos de mayor prioridad que los datos normales que puede enviar. Si los datos OOB que desea enviar están un byte, puede utilizar la función Seleccionar para buscar los datos OOB. Puede utilizar la función recv para leer los datos.

Sin embargo, en el protocolo de Control de transmisión (TCP), el bloque de datos OOB siempre es un byte. Por lo tanto, si envía datos de varios bytes OOB, se recupera sólo el último byte de los datos OOB. Los datos restantes se tratan como datos normales.

Este artículo utiliza el código de ejemplo para describir cómo enviar datos de varios bytes OOB mediante Microsoft Windows Sockets (Winsock).

INTRODUCCIÓN


En este artículo se describe cómo enviar datos de varios bytes (caracteres) fuera de banda con Winsock. Las aplicaciones de ejemplo se crean en Microsoft Visual C++.

Dado que este mecanismo OOB no se admite directamente en el nivel de socket de Microsoft Windows, debe implementar este mecanismo OOB en el nivel de aplicación. Esto no es true datos OOB. En esta aplicación de ejemplo, se crean dos zócalos en el lado del cliente, también conocido como el lado del remitente, para enviar datos de normal y hay datos OOB. En el servidor, también conocido como el lado del receptor, utilice dos zócalos para procesar los datos en dos subprocesos. Un subproceso es para datos OOB. El otro subproceso es para datos normales.


Para simular el mecanismo OOB, debe sincronizar estos dos subprocesos. Asegúrese de que el subproceso que procesa datos OOB tiene mayor prioridad que el subproceso que procesa datos normales.

Nota: Esta aplicación de ejemplo describe una forma de enviar datos OOB de varios bytes. Tendrá que revisar el código para que el código funcione en su entorno.

Volver al principio

Crear una aplicación cliente

  1. Crear una aplicación cliente. En esta aplicación de cliente, el código de ejemplo siguiente describe cómo crear dos sockets:
    SOCKET myOOBSocket = INVALID_SOCKET;
    SOCKET myNormalSocket = INVALID_SOCKET;

    myOOBSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    myNormalSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    La variable myOOBSocket se utiliza para enviar datos OOB. La variable myNormalSocket se utiliza para enviar datos normales.
  2. Dado que los datos que envía la variable myOOBSocket no están true datos OOB, debe tener una manera de indicar al servidor qué tipo de datos que se supone que el socket para enviar antes de empezar a enviar datos. El siguiente código de ejemplo describe cómo enviar un carácter inicial para notificar al servidor acerca de qué tipo de datos que se enviarán. Utilizar "U" para datos OOB y utilizar "N" para datos normales.
    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. El siguiente código de ejemplo describe cómo hacer que la aplicación cliente supervisar la entrada del usuario. La aplicación cliente envía los caracteres en mayúsculas como datos OOB. Duplicar cada carácter de entrada para redactar una cadena de varios bytes.
    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);
    }
    ...
    }
Mostrar el código de ejemplo para la aplicación de cliente Myclient.cpp

Volver al principio

Crear una aplicación de servidor

  1. En el servidor, el código de ejemplo siguiente describe cómo crear un socket de escucha para supervisar el puerto que se utiliza para la comunicación:
    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. El código de ejemplo siguiente describe cómo llamar a la función Aceptar para realizar el socket de escucha espere los intentos de conexión entrante:
    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. Para simular el mecanismo OOB, debe sincronizar estos dos subprocesos. En esta aplicación de ejemplo, utilice un objeto de evento global para ello. Colocar la siguiente instrucción al principio del archivo.
    //Create a manual-reset event object. 
    hRecvEvent=CreateEvent(NULL,TRUE,TRUE,"RecvEvent");
  4. Cree el controlador OOB ejecutado por un subproceso independiente. En este controlador, utilice la función Seleccionar para detectar cuando llegan datos. Cuando llegan datos, siga estos pasos:
    1. Llame a la función ResetEvent para establecer el objeto de evento hRecvEvent al estado no señalado.
      ResetEvent(hRecvEvent);
      Esto impide que el controlador de datos normal que se ejecuta en otro subproceso.
    2. Llame a la función de r para leer los datos.
      recv(remoteSocket,(char *)&buffer,sizeof(buffer) - 1,0);
    3. Porque pueden enviar más datos que puede contener el búfer, llame a la función recv nuevo, junto con el indicador MSG_PEEK , para determinar si los datos están disponibles en el cable.
      recv(remoteSocket,(char *)&buffer,sizeof(buffer)-1,MSG_PEEK);
      Utilice cualquiera de los métodos siguientes, dependiendo de si hay datos pendientes de leer o no:
      • Si no hay datos pendientes de leerse, llame a la función SetEvent para establecer el objeto de evento especificado en el estado señalado.
        SetEvent(hRecvEvent);
        Al hacerlo, puede reanudar el subproceso de controlador de datos normal. El subproceso de controlador de datos normal se espera para el objeto de evento hRecvEvent antes de que se reanuda el subproceso de controlador de datos normal.
      • Si datos se sigue pendiente que se leerán, llame a la función recv para leer los datos restantes. A continuación, vuelva a observar datos pendientes.
    Repita los pasos 4a a 4c hasta que se hayan leído todos los datos pendientes.
  5. Crear el controlador de datos normal. Este controlador es similar al controlador de datos OOB excepto que el controlador de datos normal llama a la función WaitForSingleObject cuando el controlador de datos normal detecta que ha recibido los datos.
    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.
Mostrar el código de ejemplo para la aplicación de servidor Myserver.cpp

Volver al principio

Mostrar el código de ejemplo para la aplicación de cliente Myclient.cpp

Nota: Debe incluir una referencia al archivo de la biblioteca de Winsock, Ws2_32.lib, para que se compile el código.
#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;
}
Volver al principio

Mostrar el código de ejemplo para la aplicación de servidor Myserver.cpp

Nota: Debe incluir una referencia al archivo de la biblioteca de Winsock, Ws2_32.lib, para que se compile el código.
#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;
}
Volver al principio

Probar el ejemplo

  1. En el símbolo del sistema, escriba Myserver.exe 4444 para iniciar el servidor en el puerto 4444.

    La aplicación de servidor muestra el siguiente mensaje y espera a que los clientes:

    ¡El servidor está listo!
  2. En una ventana diferente del símbolo del sistema, escriba
    myClient direcciónIP 4444 para iniciar al cliente.

    Nota: La dirección IP del marcador de posición es un marcador de posición para la dirección IP del servidor.
  3. El servidor muestra un mensaje similar al siguiente:

    Waiting.Waiting*..*


    Cuando reciba el mensaje anterior, debe escribir aBcDeFgHi en el cliente dentro de 10 segundos antes de que el servidor continúe.
Después de unos 10 segundos, el servidor muestra lo siguiente:
[OOB]: BBBBBBBBBB
[OOB]: DDDDDDDDDD
[OOB]: FFFFFFFFFF
[OOB]: HHHHHHHHHH

[Normal]: aaaaaaaaaa
[Normal]: cccccccccc
[Normal]: eeeeeeeeee

[Normal]: gggggggggg
[Normal]: iiiiiiiiii
Volver al principio

Referencias


Para obtener información adicional, visite los siguientes sitios Web de Microsoft Developer Network (MSDN):Para obtener información adicional, haga clic en el número de artículo siguiente para verlo en Microsoft Knowledge Base:

331756 Ioctlsocket la función no puede detectar los datos fuera de banda en línea

Volver al principio