Résumé
Parfois, vous devez envoyer des données hors-bande urgentes à vos destinataires. Les destinataires sont des utilisateurs ou des applications qui reçoivent les données. Vous voulez que ces données OOB soient traitées en tant que données de priorité supérieure par rapport aux données normales que vous pouvez envoyer. Si les données OOB que vous souhaitez envoyer ne sont qu’un octet, vous pouvez utiliser la fonction Select pour chercher des données OOB. Vous pouvez utiliser la fonction recv pour lire les données. Néanmoins, dans le protocole TCP (Transmission Control Protocol), le bloc de données OOB est toujours un octet. Par conséquent, si vous envoyez des données OOB sur plusieurs octets, seul le dernier octet des données OOB est récupéré. Les données restantes sont traitées comme des données normales. Cet article utilise un exemple de code illustrant la façon d’envoyer des données OOB de plusieurs octets à l’aide de Microsoft Windows Socket (Winsock).
DANS CETTE TÂCHE
INTRODUCTION
Cet article explique comment envoyer des données de plusieurs octets (car) en utilisant Winsock. Les exemples d’applications sont créés dans Microsoft Visual C++. Étant donné que ce mécanisme OOB n’est pas directement pris en charge au niveau du socket Microsoft Windows, vous devez implémenter ce mécanisme OOB au niveau de l’application. Ce n’est pas vrai pour les données OOB. Dans cet exemple d’application, vous créez deux sockets du côté client, également connu sous le nom d’expéditeur, pour envoyer des données OOB et des données normales. Sur le côté serveur, également appelé côté du destinataire, vous utilisez deux sockets pour traiter les données dans deux threads. Un thread est destiné aux données OOB. L’autre thread est destiné aux données normales. Pour simuler le mécanisme OOB, vous devez synchroniser ces deux threads. Assurez-vous que le thread qui traite les données OOB est prioritaire par rapport au thread qui traite les données normales.Remarque Cet exemple d’application décrit une façon d’envoyer des données OOB de plusieurs octets. Il est possible que vous deviez modifier le code pour que le code fonctionne dans votre environnement.Retour au début
Créer une application cliente
-
Créer une application cliente. Dans cette application cliente, l’exemple de code suivant décrit 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 des données OOB. La variable myNormalSocket est utilisée pour envoyer des données normales.
-
Étant donné que les données envoyées par la variable myOOBSocket n’ont pas de véritables données OOB, vous devez disposer d’un moyen d’indiquer au serveur le type de données que le socket doit envoyer avant de commencer à envoyer des données. L’exemple de code suivant décrit comment envoyer un caractère initial pour informer le serveur du type de données qui sera envoyé. Utilisez « U » pour les données OOB et utilisez « N » pour les 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.
-
L’exemple de code suivant décrit comment rendre l’application cliente surveiller l’entrée de l’utilisateur. L’application cliente envoie des caractères en majuscules en tant que données OOB. Dupliquez chaque caractère d’entrée pour composer une chaîne sur 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 en haut
Créer une application serveur
-
Sur le côté serveur, l’exemple de code suivant décrit comment créer un socket d’écouteur pour contrôler le port 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.
-
L’exemple de code suivant décrit comment appeler la fonction Accept pour que le socket d’écoute réponde aux tentatives de connexion entrantes :
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....}
-
Pour simuler le mécanisme OOB, vous devez synchroniser ces deux threads. Dans cet exemple d’application, utilisez un objet Event global. Ajoutez l’instruction suivante en haut du fichier.
//Create a manual-reset event object. hRecvEvent=CreateEvent(NULL,TRUE,TRUE,"RecvEvent");
-
Créez le gestionnaire OOB exécuté par un thread séparé. Dans ce gestionnaire, vous utilisez la fonction Select pour détecter à quel moment les données arrivent. Lorsque les données arrivent, procédez comme suit :
-
Appelez la fonction ResetEvent pour définir l’objet événement hRecvEvent sur l’état non signalé.
ResetEvent(hRecvEvent);
Le gestionnaire de données normal qui s’exécute dans un autre thread est bloqué.
-
Appelez la fonction RevC pour lire les données.
recv(remoteSocket,(char *)&buffer,sizeof(buffer) - 1,0);
-
Étant donné qu’il est possible d’envoyer plus de données que le tampon ne peut en contenir, appelez de nouveau la fonction recv , ainsi que 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);
Appliquez l’une des méthodes suivantes, selon que les données sont-elles en attente ou non :
-
Si aucune donnée n’est en attente d’être lue, appelez la fonction SetEvent pour affecter à l’objet d’événement spécifié l’état signalé.
SetEvent(hRecvEvent);
Lorsque vous procédez ainsi, le thread de gestionnaire de données normal peut reprendre. Le thread de gestionnaire de données normal attend le thread de l’événement hRecvEvent avant la reprise du thread de gestionnaire de données normal.
-
Si les données sont toujours en attente de lecture, appelez de nouveau la fonction recv pour lire les données restantes. Recherchez ensuite de nouveau les données en attente.
-
Répétez les étapes 4a à 4c jusqu’à ce que toutes les données en attente aient été lues.
-
-
Créez le gestionnaire de données normal. Ce gestionnaire est semblable au gestionnaire de données OOB sauf que le gestionnaire de données normal appelle la fonction WaitForSingleObject lorsque le gestionnaire de données normal détecte que les données ont été reçues.
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 de l’application serveur, MyServer. cpp en haut
Afficher l’exemple de code de l’application cliente MyClient. cpp
Remarque Vous devez inclure une référence au fichier de bibliothèque Winsock, Ws2_32. lib, pour effectuer la compilation du code.
#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;}
Afficher l’exemple de code de l’application serveur, MyServer. cpp
Remarque Vous devez inclure une référence au fichier de bibliothèque Winsock, Ws2_32. lib, pour effectuer la compilation du code.
#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;}
Test de l’exemple
-
À l’invite de commandes, tapez MyServer. exe 4444 pour démarrer le serveur sur le port 4444. l’application serveur affiche le message suivant, puis attend les clients :
Le serveur est prêt !
-
Dans une autre fenêtre d’invite de commandes, tapezMyClient IPAddress 4444 pour démarrer le client. Remarque L’espace réservé IPAddress est un espace réservé à l’adresse IP du serveur.
-
Le serveur affiche un message semblable à ce qui suit :
Waiting.Waiting*..*Lorsque vous recevez le message précédent, vous devez taper aBcDeFgHi sur le client dans un délai de 10 secondes avant la continuation du serveur.
Après 10 secondes, le serveur affiche ce qui suit :
[OOB] : BBBBBBBBBB [OOB] : DDDDDDDDDD [OOB] : FFFFFFFFFF [OOB] : HHHHHHHHHH [normal] : aaaaaaaaaa [normal] : cccccccccc [normal] : eeeeeeeeee [normal] : gggggggggg [normal] : iiiiiiiiiiRetour au début
Références
Pour plus d’informations, reportez-vous aux sites Web Microsoft Developer Network (MSDN) suivants :
Fonction de données hors-bandehttp://msdn2.Microsoft.com/en-us/library/ms740102.aspxindépendante du protocole pour les fonctions Winsockhttp://msdn2.Microsoft.com/en-us/library/ms741394.aspxPour plus d’informations, cliquez sur le numéro ci-dessous pour afficher l’article correspondant dans la Base de connaissances Microsoft :
331756 La fonction ioctlsocket ne peut pas détecter les données Inline hors-banderetour en haut