ข้ามไปที่เนื้อหาหลัก
การสนับสนุน
ลงชื่อเข้าใช้

วิธีการส่งข้อมูลที่ไม่อยู่ในแถบหลายไบต์โดยใช้ Winsock

สรุป

ในบางครั้งคุณต้องส่งข้อมูลที่ไม่อยู่ในวงดนตรี (OOB) ไปยังผู้รับของคุณ ผู้รับเป็นผู้ใช้หรือแอปพลิเคชันใดๆที่ได้รับข้อมูล คุณต้องการให้ข้อมูล OOB นี้ถือว่าเป็นข้อมูลที่มีลำดับความสำคัญสูงกว่าข้อมูลทั่วไปที่คุณอาจส่ง ถ้าข้อมูล OOB ที่คุณต้องการส่งเป็นหนึ่งไบต์คุณสามารถใช้ฟังก์ชัน select เพื่อค้นหาข้อมูล OOB ได้ คุณสามารถใช้ฟังก์ชัน recv เพื่ออ่านข้อมูลได้ อย่างไรก็ตามในการส่งตัวควบคุมโพรโทคอล (TCP) การบล็อกข้อมูล OOB จะเป็นหนึ่งไบต์เสมอ ถ้าคุณส่งข้อมูล OOB หลายไบต์เฉพาะไบต์สุดท้ายของข้อมูล OOB จะถูกดึงมา ข้อมูลที่เหลือจะถือว่าเหมือนกับข้อมูลปกติ บทความนี้ใช้โค้ดตัวอย่างเพื่ออธิบายวิธีการส่งข้อมูล OOB หลายไบต์โดยใช้ Microsoft Windows Socket (Winsock)

ในกรณีนี้

ข้อความนำ

บทความนี้จะอธิบายวิธีการส่งข้อมูลแบบหลายไบต์ (อักขระ) ที่ไม่อยู่ในแถบข้อมูลโดยใช้ Winsock แอปพลิเคชันตัวอย่างจะถูกสร้างขึ้นใน Microsoft Visual c ++ เนื่องจากกลไกการ OOB นี้ไม่ได้รับการสนับสนุนโดยตรงที่ระดับ Microsoft Windows socket คุณต้องใช้กลไกการ 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 กลับไปยังด้านบน

สร้างแอปพลิเคชันเซิร์ฟเวอร์

  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 ที่ดำเนินการโดยเธรดที่แยกต่างหาก ในตัวจัดการนี้คุณสามารถใช้ฟังก์ชัน select เพื่อตรวจหาเมื่อข้อมูลมาถึง เมื่อข้อมูลมาถึงให้ทำตามขั้นตอนต่อไปนี้:

    1. เรียกใช้ฟังก์ชัน ResetEvent เพื่อตั้งค่าวัตถุเหตุการณ์ hRecvEvent ไปยังสถานะที่ไม่มีการอ้างถึง

      ResetEvent(hRecvEvent);

      ซึ่งจะเป็นการบล็อกตัวจัดการข้อมูลปกติที่ดำเนินการในเธรดอื่น

    2. เรียกใช้ฟังก์ชัน revc เพื่ออ่านข้อมูล

      recv(remoteSocket,(char *)&buffer,sizeof(buffer) - 1,0);
    3. เนื่องจากข้อมูลเพิ่มเติมอาจถูกส่งไปยังที่ไม่สามารถกดบัฟเฟอร์ได้ให้เรียกใช้ฟังก์ชัน recv อีกครั้งพร้อมกับตั้งค่าสถานะ MSG_PEEK เพื่อระบุว่าข้อมูลใดบ้างที่พร้อมใช้งานบนลวด

      recv(remoteSocket,(char *)&buffer,sizeof(buffer)-1,MSG_PEEK);

      ใช้วิธีการใดวิธีการหนึ่งต่อไปนี้โดยขึ้นอยู่กับว่ามีการอ่านข้อมูลอยู่หรือไม่:

      • ถ้าไม่มีข้อมูลอยู่ในระหว่างการอ่านให้เรียกใช้ฟังก์ชัน SetEvent เพื่อตั้งค่าวัตถุเหตุการณ์ที่ระบุไปยังสถานะ

        SetEvent(hRecvEvent);

        เมื่อคุณทำเช่นนี้เธรดตัวจัดการข้อมูลปกติสามารถดำเนินการต่อได้ เธรดตัวจัดการข้อมูลปกติรอให้วัตถุเหตุการณ์ hRecvEvent ก่อนที่จะดำเนินการเธรดตัวจัดการข้อมูลตามปกติ

      • ถ้าข้อมูลยังคงอยู่ในระหว่างการอ่านให้เรียกใช้ฟังก์ชัน recv อีกครั้งเพื่ออ่านข้อมูลที่เหลือ จากนั้นให้ดูข้อมูลที่ค้างอยู่อีกครั้ง

    ทำซ้ำขั้นตอนที่4a ผ่าน4c จนกว่าจะมีการอ่านข้อมูลที่ค้างอยู่ทั้งหมด

  5. สร้างตัวจัดการข้อมูลปกติ ตัวจัดการนี้จะคล้ายกับตัวจัดการข้อมูล OOB ยกเว้นว่าตัวจัดการข้อมูลปกติจะเรียกใช้ฟังก์ชัน 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 กลับไปยังด้านบน

แสดงโค้ดตัวอย่างสำหรับแอปพลิเคชันไคลเอ็นต์ Myclient

หมายเหตุ คุณต้องใส่การอ้างอิงไปยังไฟล์ไลบรารี 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

หมายเหตุ คุณต้องใส่การอ้างอิงไปยังไฟล์ไลบรารี 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;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;}

กลับไปด้านบน

ทดสอบตัวอย่าง

  1. ที่พร้อมท์คำสั่งให้พิมพ์ Myserver ๔๔๔๔ เพื่อเริ่มต้นเซิร์ฟเวอร์บนพอร์ต4444แอปพลิเคชันเซิร์ฟเวอร์จะแสดงข้อความต่อไปนี้แล้วรอให้ไคลเอ็นต์:

    เซิร์ฟเวอร์พร้อมแล้ว!

  2. ในหน้าต่างพร้อมท์คำสั่งอื่นให้พิมพ์MyClient IPAddress ๔๔๔๔ เพื่อเริ่มการทำงานของไคลเอ็นต์ หมายเหตุ ตัวแทน IPAddress เป็นตัวยึดสำหรับที่อยู่ IP ของเซิร์ฟเวอร์

  3. เซิร์ฟเวอร์จะแสดงข้อความที่คล้ายกับข้อความต่อไปนี้:

    Waiting.Waiting*..*เมื่อคุณได้รับข้อความก่อนหน้านี้คุณต้องพิมพ์ aBcDeFgHi บนไคลเอ็นต์ภายใน10วินาทีก่อนที่เซิร์ฟเวอร์จะดำเนินการต่อไป

หลังจากผ่านไป10วินาทีเซิร์ฟเวอร์จะแสดงสิ่งต่อไปนี้:

[OOB]: BBBBBBBBBB [OOB]: DDDDDDDDDD [OOB]: FFFFFFFFFF เป็นเลข [OOB]: HHHHHHHHHH [ปกติ]: aaaaaaaaaa [ปกติ]: cccccccccc [ปกติ]: eeeeeeeeee [ปกติ]: ggggggggggกลับไปด้านบน

อ้างอิง

สำหรับข้อมูลเพิ่มเติมโปรดเยี่ยมชมเว็บไซต์ของ Microsoft สำหรับนักพัฒนาเครือข่าย (MSDN) ต่อไปนี้:

โพรโทคอล-http://msdn2.microsoft.com/en-us/library/ms740102.aspxข้อมูลที่ไม่อยู่ในวงดนตรี Winsock ฟังก์ชันhttp://msdn2.microsoft.com/en-us/library/ms741394.aspxสำหรับข้อมูลเพิ่มเติมให้คลิกหมายเลขบทความต่อไปนี้เพื่อดูบทความในฐานความรู้ของ Microsoft:

๓๓๑๗๕๖ ฟังก์ชัน Ioctlsocket ไม่สามารถตรวจหาข้อมูลที่ไม่อยู่ในแถบแบบอินไลน์ได้กลับสู่ด้านบน

ต้องการความช่วยเหลือเพิ่มเติมหรือไม่

ขยายทักษะของคุณ
สำรวจการฝึกอบรม
รับฟีเจอร์ใหม่ก่อนใคร
เข้าร่วม Microsoft Insider

ข้อมูลนี้เป็นประโยชน์หรือไม่

ขอบคุณสำหรับคำติชมของคุณ

ขอขอบคุณสำหรับคำติชมของคุณ! เราคิดว่าอาจเป็นประโยชน์ที่จะให้คุณได้ติดต่อกับหนึ่งในตัวแทนฝ่ายสนับสนุน Office ของเรา

×