L’envoi de données out-of-band de plusieurs octets à l’aide de Winsock


Résumé


Parfois vous devez envoyer des données d’urgentes out-of-band (OOB) à vos destinataires. Les récepteurs sont des utilisateurs ou des applications qui reçoivent les données. Que ces données prêtes à l’emploi traitées comme des données de priorité plus élevée que vous pouvez envoyer toutes les données normales. Si les données prêtes à l’emploi que vous souhaitez envoyer sont un octet, vous pouvez utiliser la fonction Sélectionner pour rechercher les données prêtes à l’emploi. Vous pouvez utiliser la fonction de réception pour lire les données.

Toutefois, en protocole TCP (Transmission Control), le bloc de données prêtes à l’emploi est toujours un seul octet. Par conséquent, si vous envoyez des données prêtes à l’emploi de plusieurs octets, seulement le dernier octet des données prêtes à l’emploi est récupéré. Le reste des données sont traitées comme des données normales.

Cet article utilise des exemples de code pour décrire comment envoyer des données prêtes à l’emploi de plusieurs octets à l’aide de Microsoft Windows Socket (Winsock).

INTRODUCTION


Cet article explique comment envoyer des données de plusieurs octets (chars) out-of-band à l’aide de Winsock. Les exemples d’applications sont créés dans Microsoft Visual C++.

Ce mécanisme prêtes à l’emploi n’est pas directement pris en charge au niveau du support de Microsoft Windows, vous devez implémenter ce mécanisme prêtes à l’emploi au niveau de l’application. Ce n’est pas réelle des données prêtes à l’emploi. Dans cet exemple d’application, vous créez deux sockets côté client, également connu sous le nom côté expéditeur, pour envoyer des données de normale et les données prêtes à l’emploi. Côté serveur, également appelé du côté récepteur, deux sockets vous permet de traiter les données dans deux threads. Un seul thread est pour les données prêtes à l’emploi. L’autre thread est de données normales.


Pour simuler le mécanisme prêtes à l’emploi, vous devez synchroniser ces deux threads. Assurez-vous que le thread qui traite les données prêtes à l’emploi a une priorité plus élevée que le thread qui traite les données de la normale.

Remarque Cet exemple d’application décrit un moyen d’envoyer des données prêtes à l’emploi de plusieurs octets. Vous devrez peut-être modifier le code pour que le code fonctionne dans votre environnement.

Retour au début

Créer une application cliente

  1. Créer une application cliente. Dans cette application cliente, l’exemple de code suivant explique comment créer deux 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 est utilisée pour envoyer les données prêtes à l’emploi. La variable myNormalSocket est utilisée pour envoyer des données normales.
  2. Parce que les données qui l’envoie de la variable myOOBSocket ne sont pas réelle des données prêtes à l’emploi, vous devez disposer d’un moyen pour indiquer au serveur quel genre de données que le socket est supposée pour envoyer l’avant de commencer à envoyer des données. L’exemple de code suivant explique comment envoyer un caractère initial pour notifier au serveur sur le type de données qui seront envoyées. « U » pour les données prêtes à l’emploi et « N » pour données 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. L’exemple de code suivant explique comment créer l’application cliente à surveiller l’entrée de l’utilisateur. L’application cliente envoie des caractères majuscules en tant que les données prêtes à l’emploi. Dupliquer tous les caractères d’entrée pour composer une chaîne de plusieurs octets.
    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);
    }
    ...
    }
Afficher l’exemple de code pour l’application cliente, Myclient.cpp

Retour au début

Créer une application serveur

  1. Côté serveur, l’exemple de code suivant explique comment créer un socket d’écoute pour surveiller le port qui est utilisé pour la communication :
    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. L’exemple de code suivant explique comment appeler la fonction Accepter de faire attendre à des tentatives de connexion entrante du socket d’écoute :
    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. Pour simuler le mécanisme prêtes à l’emploi, vous devez synchroniser ces deux threads. Dans cet exemple d’application, utilisez un objet d’événement global pour cela. Ajoutez l’instruction suivante en haut du fichier.
    //Create a manual-reset event object. 
    hRecvEvent=CreateEvent(NULL,TRUE,TRUE,"RecvEvent");
  4. Créez le gestionnaire prêtes à l’emploi qui est exécuté par un thread séparé. Dans ce gestionnaire, vous utilisez la fonction Sélectionner pour détecter l’arrivée des données. Lorsque des données arrivent, procédez comme suit :
    1. Appelez la fonction ResetEvent pour définir l’objet événement hRecvEvent à l’état non signalé.
      ResetEvent(hRecvEvent);
      Cela empêche le Gestionnaire de données normal qui est exécuté dans un autre thread.
    2. Appelez la fonction revc pour lire les données.
      recv(remoteSocket,(char *)&buffer,sizeof(buffer) - 1,0);
    3. Car plus de données peuvent être envoyées que peut en contenir la mémoire tampon, appelez la fonction recv à nouveau, avec l’indicateur MSG_PEEK , pour déterminer si des données sont disponibles sur le réseau.
      recv(remoteSocket,(char *)&buffer,sizeof(buffer)-1,MSG_PEEK);
      Utilisez une des méthodes suivantes, selon que donnée en attente pour être lue ou non :
      • Si aucune donnée n’est en attente pour la lecture, appelez la fonction de SetEvent pour définir l’objet événement spécifié à l’état signalé.
        SetEvent(hRecvEvent);
        Lorsque vous effectuez cette opération, le thread de gestionnaire de données de normale peut reprendre. Le thread de gestionnaire de données normales attend que l’objet événement hRecvEvent avant la reprise de la thread de gestionnaire de données normales.
      • Si données est toujours en attente à lire, appelez la fonction de réception pour lire les données restantes. Puis, recherchez à nouveau les données en attente.
    Répétez les étapes 4 a à 4c jusqu'à ce que toutes les données en attente ont été lues.
  5. Créez le Gestionnaire de données normales. Ce gestionnaire est similaire au Gestionnaire de données prêtes à l’emploi, mais que le Gestionnaire de données de la normale appelle la fonction WaitForSingleObject lorsque le Gestionnaire de données de la normale détecte que les données sont arrivé.
    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.
Afficher l’exemple de code pour l’application serveur, Myserver.cpp

Retour au début

Afficher l’exemple de code pour l’application cliente, Myclient.cpp

Remarque Vous devez inclure une référence au fichier bibliothèque Winsock, Ws2_32.lib, pour que le code à compiler.
#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;
}
Retour au début

Afficher l’exemple de code pour l’application serveur, Myserver.cpp

Remarque Vous devez inclure une référence au fichier bibliothèque Winsock, Ws2_32.lib, pour que le code à compiler.
#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;
}
Retour au début

Test de l’exemple

  1. À l’invite de commande, tapez Myserver.exe 4444 pour démarrer le serveur sur le port 4444.

    L’application serveur affiche le message suivant et attend ensuite pour les clients :

    Le serveur est prêt !
  2. Dans une autre fenêtre de l’invite de commande, tapez
    myClient IPAddress 4444 pour démarrer le client.

    Remarque L’adresse IP est un emplacement réservé pour l’adresse IP du serveur.
  3. Le serveur affiche un message semblable au suivant :

    Waiting.Waiting*..*


    Lorsque vous recevez le message précédent, vous devez taper l’expression abcdefghicde sur le client dans les 10 secondes avant que le serveur continue.
Après environ 10 secondes, le serveur affiche ce qui suit :
[PRÊTES À L’EMPLOI] : BBBBBBBBBB
[PRÊTES À L’EMPLOI] : DDDDDDDDDD
[PRÊTES À L’EMPLOI] : FFFFFFFFFF
[PRÊTES À L’EMPLOI] : HHHHHHHHHH

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

[Normal] : gggggggggg
[Normal] : iiiiiiiiii
Retour au début

Références


Pour plus d’informations, visitez les sites Web de Microsoft Developer Network (MSDN) suivant :
Données d’out-of-band indépendantes du protocole
http://msdn2.microsoft.com/en-us/library/ms740102.aspx

Fonctions de Winsock
http://msdn2.microsoft.com/en-us/library/ms741394.aspx
Pour plus d’informations, cliquez sur le numéro ci-dessous pour afficher l’article correspondant dans la Base de connaissances Microsoft :

331756 Ioctlsocket de la fonction ne peut pas détecter les données d’out-of-band en ligne

Retour au début