كيفية إرسال البيانات خارج النطاق متعددة البايت باستخدام Winsock


ملخص


في بعض الأحيان يجب إرسال البيانات (خارج النطاق) خارج النطاق العاجلة للاستقبال الخاص بك. تم استقبال أي مستخدمين أو أية تطبيقات تلقي البيانات. تحتاج هذه البيانات خارج النطاق معاملتها كبيانات أولوية أعلى من أي بيانات عادية قد يرسل. إذا كانت البيانات خارج النطاق الذي تريد إرسال بايت واحد، يمكنك استخدام الدالة حدد للبحث عن البيانات خارج النطاق. يمكنك استخدام الدالة تلقي لقراءة البيانات.

ومع ذلك، في بروتوكول تحكم الإرسال (TCP)، كتلة البيانات خارج النطاق دائماً بايت واحد. لذلك، إذا قمت بإرسال بيانات خارج النطاق متعددة البايت، يتم استرداد البايت الأخير فقط من البيانات خارج النطاق. يتم التعامل مع بقية البيانات مثل بيانات عادية.

تستخدم هذه المقالة نموذج التعليمات البرمجية لوصف كيفية إرسال البيانات خارج النطاق متعددة البايت باستخدام Microsoft Windows مأخذ التوصيل (Winsock).

مقدمة


توضح هذه المقالة كيفية إرسال البيانات خارج النطاق (أحرف) بايت متعددة باستخدام Winsock. يتم إنشاء نماذج التطبيقات في Microsoft Visual c + +.

لأنه لا يتم دعم هذه الآلية خارج النطاق مباشرة على مستوى مأخذ توصيل Microsoft Windows، يجب تطبيق هذه الآلية خارج النطاق على مستوى التطبيق. هذا غير صحيح البيانات خارج النطاق. في هذا التطبيق، يمكنك إنشاء مأخذ توصيل جهازي على الجهاز العميل، المعروف أيضا الجانب المرسل، لإرسال البيانات خارج النطاق وبيانات عادية. على جانب الملقم، المعروف أيضا الجانب المتلقي، يمكنك استخدام مأخذ توصيل جهازي لمعالجة البيانات في اثنين من مؤشرات الترابط. مؤشر ترابط واحد للبيانات خارج النطاق. مؤشر ترابط آخر لبيانات عادية.


لمحاكاة إليه خارج النطاق، يجب مزامنة مؤشرات الترابط بين هذه. تأكد من أن مؤشر الترابط يقوم بمعالجة البيانات خارج النطاق له أولوية أعلى من مؤشر الترابط يقوم بمعالجة بيانات عادية.

ملاحظة: يصف هذا التطبيق طريقة لإرسال البيانات خارج النطاق متعدد البايت. قد يلزم تنقيح القانون لجعل التعليمات البرمجية تعمل في البيئة الخاصة بك.

العودة إلى أعلى

إنشاء تطبيق عميل

  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);
    يتم استخدام متغير ميوبسوكيت لإرسال البيانات خارج النطاق. يتم استخدام متغير مينورمالسوكيت لإرسال بيانات عادية.
  2. لأن البيانات التي يرسلها المتغير ميوبسوكيت غير حقيقية للبيانات خارج النطاق، يجب أن يكون لديك طريقة لمعرفة أنواع البيانات مأخذ التوصيل من المفترض أن يرسل قبل البدء بإرسال بيانات الملقم. نموذج التعليمات البرمجية التالية توضح كيفية إرسال حرف أولى لإعلام الخادم حول نوع البيانات التي سيتم إرسالها. استخدام "U" للبيانات خارج النطاق واستخدام "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. نموذج التعليمات البرمجية التالية توضح كيفية إنشاء تطبيق عميل مراقبة الإدخال من المستخدم. تطبيق العميل بإرسال الأحرف الكبيرة كبيانات خارج النطاق. تكرار كل حرف الإدخال لإنشاء سلسلة أحرف متعددة البايت.
    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. لمحاكاة إليه خارج النطاق، يجب مزامنة مؤشرات الترابط بين هذه. في هذا التطبيق، استخدم كائن الحدث عمومية للقيام بذلك. ضع العبارة التالية في الجزء العلوي من الملف.
    //Create a manual-reset event object. 
    hRecvEvent=CreateEvent(NULL,TRUE,TRUE,"RecvEvent");
  4. إنشاء معالج خارج النطاق التي يتم تنفيذها بواسطة مؤشر ترابط منفصل. في هذا المعالج، يمكنك استخدام الدالة حدد للكشف عن عندما تصل البيانات. عندما تصل البيانات، اتبع هذه الخطوات:
    1. استدعاء دالة ريسيتيفينت لتعيين كائن الحدث هريكفيفينت إلى الحالة غير الإشارة.
      ResetEvent(hRecvEvent);
      وهذا يمنع معالج البيانات العادية التي يتم تنفيذها في مؤشر ترابط آخر.
    2. استدعاء دالة ريفك لقراءة البيانات.
      recv(remoteSocket,(char *)&buffer,sizeof(buffer) - 1,0);
    3. لأنه قد يتم إرسال بيانات أكثر مما يمكن أن تحتويه المخزن المؤقت، استدعاء دالة تلقي مرة أخرى، التعاون مع العلامة MSG_PEEK ، لتحديد ما إذا كانت أي بيانات متوفرة على السلك.
      recv(remoteSocket,(char *)&buffer,sizeof(buffer)-1,MSG_PEEK);
      استخدم أي من الطرق التالية، تبعاً لما إذا كان هناك بيانات المعلقة قراءة أم لا:
      • في حالة معلقة قراءة أية بيانات، استدعاء دالة سيتيفينت لتعيين كائن الحدث المحدد لحالة إشارية.
        SetEvent(hRecvEvent);
        عند القيام بذلك، يمكن استئناف مؤشر ترابط معالج بيانات عادية. انتظار مؤشر ترابط معالج البيانات العادية كائن الحدث هريكفيفينت قبل استئناف مؤشر ترابط معالج بيانات عادية.
      • إذا البيانات هو ما زال معلقاً لقراءة استدعاء تلقي الدالة مرة أخرى لقراءة البيانات المتبقية. ثم انظر مرة أخرى انتظار البيانات.
    كرر الخطوات 4 أ حتى ج 4 حتى تتم قراءة كافة البيانات المعلقة.
  5. إنشاء معالج بيانات عادية. يشبه هذا المعالج معالج البيانات خارج النطاق إلا أن معالج بيانات عادية باستدعاء الدالة 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 تشغيل العميل.

    ملاحظة: عنوان Ip عنصر نائب لعنوان IP للخادم.
  3. يعرض الملقم رسالة مشابهة لما يلي:

    Waiting.Waiting*..*


    عندما تتلقى الرسالة السابقة، يجب عليك كتابة أبكديفغي على العميل خلال 10 ثوان قبل متابعة الملقم.
بعد حوالي 10 ثواني، يعرض الملقم ما يلي:
[خارج النطاق]: ببببببببب
[خارج النطاق]: ددددددددد
[خارج النطاق]: FFFFFFFFFF
[خارج النطاق]: ههههههههه

[عادي]: ااااااااا
[عادي]: كككككككككك
[عادي]: أ

[عادي]: ججججججججج
[عادي]: ييييييييي
العودة إلى أعلى

المراجع


لمزيد من المعلومات، قم بزيارة مواقع ويب شبكة مطوري Microsoft (MSDN) التالية:
بروتوكول مستقل عن البيانات خارج النطاق
http://msdn2.microsoft.com/en-us/library/ms740102.aspx

دالات Winsock
http://msdn2.microsoft.com/en-us/library/ms741394.aspx
للحصول على معلومات إضافية، انقر فوق رقم المقالة التالي لعرضها في "قاعدة معارف Microsoft":

331756 دالة "إيوكتلسوكيت" لا يمكن الكشف عن البيانات خارج النطاق المضمن

العودة إلى أعلى