2015-08-04 3 views
1

Мне нужно написать клиентское приложение асинхронного сокета WIN32, которое подключается к серверу. Я могу запустить программу, подключиться к серверу, получить и продолжить полученные данные, но когда я пытаюсь отправить сообщение на сервер, на сервере ничего не происходит. У меня нет сообщения об ошибке сокета, и функция send() возвращает правильные отправленные байты. Но сервер выглядит так, будто ничего не получает. Моя функция send() вызывается в инструкции FD_READ, в первом случае. Я попытался переместить его в другие операторы (WM_CREATE, FD_WRITE, FD_CONNECT), но по-прежнему остается той же проблемой. Я попытался найти, что не так, посмотри на многие форумы с одного месяца, но я не нашел проблемы. Любая помощь будет очень оценена. Вот пример моего кода (я пропустил НЕРАСПРОСТРАНЕНИИ интересные части):C++ win32 async socket client send() issue

#include <windows.h> 
#include <fstream> 
#include <sstream> 
#include<process.h> 

#pragma comment(lib,"ws2_32.lib") 

using namespace std; 

#define IDC_BUTTON_CONNECT 101 // Button identifiers 

#define IDC_EDIT_IP 102 // Edit/Text box identifiers 
#define IDC_EDIT_PORT 103 
#define IDC_EDIT_DEBUG 104 
#define WM_SOCKET 105 // Socket messages structure identifier 

HWND hWnd; 
HWND hEditIp; 
HWND hEditPort; 
HWND hDebug; 

HWND hButtonConnect; 

HANDLE hReadMutex; 

SOCKET Socket = NULL; 
SOCKADDR_IN SockAddr; 
char *ip = ""; 
char *port = ""; 
bool connectStatus = FALSE; 
char readBuffer[5000]; 
char id[32]; 
char version[256]; 

LPSTR statusText = TEXT("Connecting..."); 

LRESULT CALLBACK WinProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam); 

// WinMain... 

// appendTextToEdit function... 

// incoming data processing thread (not used yet)... 

LRESULT CALLBACK WinProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam) 
{ 
    switch(msg) 
    { 
     case WM_CREATE: 
     { 
     // Get IP and port from file "config.ini"...   

     //Create windows for IP, Port and Debug... 

     // Create "connect" button... 


     WSADATA WsaDat; // Winsock initialization... 
     int nResult=WSAStartup(MAKEWORD(2,2),&WsaDat); 
     if(nResult!=0) 
     { 
      statusText=TEXT("Winsock initialization failed");     
      InvalidateRect(hWnd, NULL, TRUE); 
      break; 
     } 

     Socket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); // Creating socket... 
     if(Socket==INVALID_SOCKET) 
     { 
      statusText=TEXT("Socket creation failed");     
      InvalidateRect(hWnd, NULL, TRUE); 
      break; 
     } 

     nResult=WSAAsyncSelect(Socket,hWnd,WM_SOCKET,(FD_CLOSE|FD_READ|FD_WRITE|FD_CONNECT)); // Select AsyncSocket events... 
     if(nResult) 
     { 
      statusText=TEXT("WSAAsyncSelect failed");     
      InvalidateRect(hWnd, NULL, TRUE); 
      break; 
     }   

     // Set up our socket address structure... 
     SockAddr.sin_addr.s_addr=inet_addr(ip); 
     SockAddr.sin_port=htons(atoi(port)); 
     SockAddr.sin_family=AF_INET;   

     connect(Socket,(LPSOCKADDR)(&SockAddr),sizeof(SockAddr)); // Connexion command    
    } 
    break; 

    case WM_PAINT: 
    { 
     // painting/updating the window... 
    } 
    break; 

    case WM_COMMAND: 
     switch(LOWORD(wParam)) 
     { 
      case IDC_BUTTON_CONNECT: 
      {     
       if(connectStatus==FALSE) // Avoid to reconnect when already online... 
       { 
        // Window repaint with updated text (statusText = global variable) 
        statusText=TEXT("Connecting...");     
        InvalidateRect(hWnd, NULL, TRUE);     

        // Retrieve edit box datas and store to variables and file... 
        // Saving data to "config.ini" file... 

        // Reinitializing adress structure with new host adress and port from edit boxes, and reconnect attempt... 
        SockAddr.sin_addr.s_addr=inet_addr(ip); 
        SockAddr.sin_port=htons(atoi(port)); 
        SockAddr.sin_family=AF_INET;  

        connect(Socket,(LPSOCKADDR)(&SockAddr),sizeof(SockAddr)); 
       } 
       break; 
      } 
      break;   
     } 
     break;   

    case WM_SOCKET: 
    {   
     switch(WSAGETSELECTEVENT(lParam)) 
     { 
      case FD_CONNECT: 
      { 
       connectStatus = TRUE; 
       statusText=TEXT("Connected");    
      } 

      case FD_READ: 
      { 
       appendTextToEdit(hDebug, "FD_READ event...\n"); 

       char *rawVariable = nullptr;      
       char *next_token = nullptr;       

       recv(Socket, readBuffer, sizeof(readBuffer), 0);    

       rawVariable = readBuffer;    

       if(strstr(rawVariable, "id=") != NULL) // Identifying message sent by the server (starts with id, version, ...) and store the values to variables (working fine) 
       { 
        char *label = strtok_s(rawVariable, "=", &next_token); 
        char *pId = strtok_s(NULL, "\n", &next_token); 
        rawVariable = strtok_s(NULL, "", &next_token); 
        strcpy_s(id, pId); 

        char message[256] = "Qh57=1"; // Setting up message structure... (the server should do an action when it receives "Qh57=1\r\n" but it doesn't...) here is my problem... 
        appendTextToEdit(hDebug, "Message sent : "); 
        appendTextToEdit(hDebug, message); 
        char *endline = "\r\n"; 
        strcat_s(message, endline); 

        string messageString(message);   
        int messageLength = ((messageString.length()) - 2); // Get outgoing string length (-2)       
        int sent = send(Socket, message , messageLength , 0);    

        char buffer [33]; // Display number of bytes sent... 
        _itoa_s(sent,buffer,10);      
        appendTextToEdit(hDebug, " ("); 
        appendTextToEdit(hDebug, buffer); 
        appendTextToEdit(hDebug, " bytes sent)\n"); 
       } 

       if(strstr(rawVariable, "version") != NULL) 
       { 
        char *label = strtok_s(rawVariable, "=", &next_token);  
        char *pVersion = strtok_s(NULL, "\n", &next_token); 
        rawVariable = strtok_s(NULL, "", &next_token);  
        strcpy_s(version, pVersion);  
       }          

       appendTextToEdit(hDebug, "End of FD_READ\n");   
      } 
      break; 

      case FD_WRITE: 
      { 
       appendTextToEdit(hDebug, "FD_WRITE event...\n"); 

       appendTextToEdit(hDebug, "End of FD_WRITE\n"); 
      } 
      break; 

      case FD_CLOSE: 
      { 
       statusText = "Disconnected from server"; 
       connectStatus = FALSE; 
       InvalidateRect(hWnd, NULL, TRUE); 
//    SendMessage(hWnd, WM_DESTROY, NULL, NULL); // Final version...     
      } 
      break; 
     } 
     break; 
    } 
    break; 

    case WM_DESTROY: 
    { 
     closesocket(Socket); 
     WSACleanup(); 
     PostQuitMessage(0); 
     return 0; 
    } 
    break; 
} 
return DefWindowProc(hWnd,msg,wParam,lParam); 
} 
+0

Как вы знаете, что с сервером нет проблем? – Amit

+0

^^^ Что сказал Wireshark? –

+0

В обработчике 'WM_SOCKET' вы не проверяете ошибки сокета. 'WSAGETSELECTEVENT()' сообщает вам, какая операция вызвала событие, а 'WSAGETSELECTERROR()' сообщает вам, была ли операция успешной или нет. Например, если 'connect()' fail, вы получите событие FD_CONNECT с кодом ошибки, отличным от 0. Вы также не выполняете проверку ошибок на 'recv()' или 'send()' вообще, ваш синтаксический анализ данных «rawVariable» является неполным, вы фактически не отправляете разрыв строки в конце вашего исходящего сообщения и т. д. –

ответ

3

Проблема заключается в том, что вы отправляете неполное сообщение на сервер, поэтому он не собирается ничего делать. За свои собственные комментарии в коде:

the server should do an action when it receives "Qh57=1\r\n" but it doesn't... 

Он никогда не получает \r\n, потому что вы никогда не посылать его:

char message[256] = "Qh57=1"; 
... 
char *endline = "\r\n"; 
strcat_s(message, endline); // <-- you do append a CRLF... 

string messageString(message);   
int messageLength = ((messageString.length()) - 2); // <-- but you subtract the CRLF from the message length... 
int sent = send(Socket, message , messageLength , 0); // <-- so the CRLF is not sent! 

Не вычитать CRLF от длины сообщения:

int messageLength = messageString.length(); 

BTW, используя std:string, просто для вычисления длины сообщения является пустой тратой памяти. Вы можете использовать strlen() вместо: гнездо подключения

int messageLength = strlen(message); 
+0

Спасибо, ТАК МНОГО! Он работает сейчас. У меня была проблема под глазами из-за начальника и не видела этого, спасибо еще раз. – JP744

+0

Пожалуйста, позвольте мне задать еще один вопрос. Почему вы говорите, что мой разбор rawVariable является неполным? Я также думаю, что есть проблема, потому что иногда я имею значение pId или pVariable, которые являются неправильными (другие значения rawVariable подстроки). Понятно, что rawVariable - длинная строка вроде: id = 3 \ nversion = 10.0.5 \ nQh57 = 0 \ nQs323 = -999999 \ n, и иногда я имею, например, значение pVersion = -999999, но это должно be 10.0.5 – JP744

+0

Это неполное, потому что TCP является потоком байтов, а не ориентированным на сообщения. 'recv()' возвращает все, что находится в буфере приема сокета * до * запрошенного количества байтов, что означает, что он может вернуть меньшее количество байтов, чем запрошено. Единственной гарантией, которую предоставляет 'recv()', является то, что * каждый байт будет возвращен как минимум * 1 байт (если ошибка не возникает). И буфер, который вы читаете, также не является нулевым. Но ваш код предполагает оба случая, что неверно. Вы можете получить 'd = 3 \ nve' на одном' recv() ', затем' rsion = 10.0.5 \ nQh' в следующий раз, затем '57 = 0 \ nQs323 = -999999 \ n' в следующий раз. См. Вопрос? –

0

Поток ничего не знает о границах сообщения, не знаю. Он просто переносит байты, подобно соединению RS232. Если вы внимательно прочитали документацию recv, вы обнаружите, что успешный вызов recv может «возвращать» (= копировать в буфер) где угодно между одним байтом и указанным размером буфера - в зависимости от того, сколько данных уже получено. recv s Возвращаемое значение скажет вам, сколько было скопировано. Если последует больше данных, оно будет возвращено путем следующих вызовов: recv. Это означает, что если сервер отправляет id=3\nversion=10.0.5\nQh57=0\nQs323=-999999\n, ваш первый звонок recv может вернуть id=3\nversion=10.0.5\nQh, а второй может затем вернуться 57=0\nQs323=-999999\n. Или вы можете получить его в 3 куска. Или 4. Или даже один байт за раз.

Самый простой способ справиться с этим, который, к сожалению, имеет очень плохую производительность, состоит в том, чтобы повторно называть recv с размером буфера одного байта. Пока вы не увидите, что получили все сообщение (в вашем примере это будет после 4 \n символов получено).

Предпочтительный способ был бы что-то вроде:

  1. Зова recv с большим буфером
  2. Append однако количество байт recv возвращаются в ваше «сообщение сборки буфера»
  3. Если «сообщение сборки буфера» содержит хотя бы одно полное сообщение: процесс и удалить его. Повторяйте до тех пор, пока все готовые сообщения не будут обработаны и удалены.
  4. Goto (1)