Проблема с winsock recv () и accept () - PullRequest
2 голосов
/ 25 декабря 2009

У меня проблемы с приложением сокета, я программирую на C ++. Я занимаюсь программированием с Bloodshed Dev-Cpp для Windows XP. Я создал класс для обработки всех передач сообщений и имею клиентскую и серверную программу, которые оба используют этот класс для обработки своих сервисов. Само приложение очень простое, единственное, что у меня есть для этого, - заставить все это работать.

Клиент для отправки сообщений работает так, как я ожидал. Если мой сервер работает, он не имеет ошибок при отправке сообщения. Если он не работает, он передаст ошибку. Но мой сервер постоянно принимает странные тарабарщины. Это всегда одни и те же данные. Когда он получает сообщение, эффект отсутствует. Если мой клиент попытается идентифицировать сервер, он вернется назад.

Я включил свой исходный код здесь. Компоновщик также вводит два дополнительных параметра: -lwsock32 и включение библиотеки libws2_32.a, которая поставляется с Dev-Cpp.

Вот заголовок для моего класса Messager:

#ifndef MESSAGER
#define MESSAGER

#include <string>

class Messager{
    private:
        int sendSocket;
        int listenSocket;

    public:
        void init(void);
        bool connect(std::string ip, std::string port);
        bool bind(std::string port);
        void listen(void);
        void send(std::string message);
        std::string receive(void);
};
#endif

Это мои определения для класса Messager:

#include "Messager.h"
#include <winsock2.h>
#include <sys/types.h>
#include <ws2tcpip.h>
#include <windows.h>

void Messager::init(void){
    WSADATA wsaData;

    WSAStartup(MAKEWORD(1,1), &wsaData);
}

bool Messager::connect(std::string ip, std::string port){
    struct addrinfo hints;
    struct addrinfo *res;
    bool success = false;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    getaddrinfo(ip.c_str(), port.c_str(), &hints, &res);

    sendSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

    success = ::connect(sendSocket, res->ai_addr, res->ai_addrlen) != -1;

    freeaddrinfo(res);

    return success;
}

bool Messager::bind(std::string port){
    struct addrinfo hints, *res;

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    getaddrinfo(NULL, port.c_str(), &hints, &res);

    listenSocket = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

    if(listenSocket == INVALID_SOCKET){
        return false;
    }

    if(::bind(listenSocket, res->ai_addr, res->ai_addrlen) == -1){
        return false;
    }

    return true;
}

void Messager::listen(void){
    ::listen(listenSocket, 10);
}

int Messager::send(std::string message){
    const std::string terminator = "\r\n";
    std::string realMessage;
    int size = 0;
    int totalSent = 0;

    realMessage = message;
    realMessage += terminator;

    size = realMessage.size();

    totalSent = ::send(sendSocket, realMessage.c_str(), size, 0);

    if(totalSent == 0 || totalSent == -1){
        return 0; // There must be an error, 0 means it is an error
    }

    // This statement keeps adding the results of ::send to totalSent until it's the size of the full message
    for(totalSent = 0; totalSent < size; totalSent += ::send(sendSocket, realMessage.c_str(), size, 0));

    return totalSent;
}

// This function has been updated a lot thanks to @Luke
std::string Messager::receive(void){
    const int bufferSize = 256;
    const std::string terminator = "\r\n";
    char buffer[bufferSize];
    int i = 0;
    int received = 0;
    std::string tempString;
    size_t term = 0;

    for(i = 0; i < bufferSize; i++){
        buffer[i] = 0;
    }

    received = ::recv(listenSocket, buffer, bufferSize, 0);
    tempString = buffer;
    term = tempString.find(terminator);

    if(term != -1){ // Already have line
        line = tempString;
    }

    while(received != -1 && received != 0){ // While it is receiving information...
        // Flush the buffer
        for(i = 0; i < bufferSize; i++){
            buffer[i] = 0;
        }

        ::recv(listenSocket, buffer, bufferSize, 0);
        tempString += buffer;
        term = tempString.find(terminator);

        if(term != -1){ // Found terminator!
            return tempString;
        }
    }

    throw 0; // Didn't receive any information.  Throw an error
}

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

Сервер:

  • messager.init ()
  • messager.bind ()
  • messager.listen ()
  • messager.receive () <- включает accept () </li>

Клиент:

  • messager.init ()
  • messager.connect ()
  • messager.send ()

Заранее спасибо.

Ответы [ 4 ]

3 голосов
/ 25 декабря 2009

Я вижу две проблемы.

  1. Вы не можете безопасно использовать оператор присваивания строк в Message :: receive (). Оператор присваивания полагается на символьный массив, заканчивающийся NULL, и в этом случае это не так. Вероятно, он заполняется кучей мусорных данных. Вы должны получить количество фактически принятых символов (то есть возвращаемое значение recv ()) и использовать метод string :: assign () для заполнения строкового объекта.
  2. Нет кода, который бы гарантировал, что все данные были отправлены или получены. recv () вернется, как только появятся какие-либо данные; вам действительно нужно зацикливаться, пока вы не получите все сообщение. Для простых текстовых данных обычно люди используют пару CR-LF для обозначения конца строки. Вы продолжаете вызывать recv () и буферизировать результаты, пока не увидите эту пару CR-LF, а затем вернете эту строку вызывающей стороне. Вы также должны зацикливаться на send (), пока весь ваш буфер не будет отправлен.

Обычно это выглядит примерно так (это все из памяти, поэтому, возможно, есть несколько незначительных ошибок, но это суть):

bool Message::Receive(std::string& line)
{
    // look for the terminating pair in the buffer
    size_t term = m_buffer.find("\r\n");
    if(term != -1)
    {
        // already have a line in the buffer
        line.assign(m_buffer, 0, term); // copy the line from the buffer
        m_buffer.erase(0, term + 2); // remove the line from the buffer
        return true;
    }
    // no terminating pair in the buffer; receive some data over the wire
    char tmp[256];
    int count = recv(m_socket, tmp, 256);
    while(count != -1 && count != 0)
    {
        // successfully received some data; buffer it
        m_buffer.append(tmp, count);
        // see if there is now a terminating pair in the buffer
        term = m_buffer.find("\r\n");
        if(term != -1)
        {
            // we now have a line in the buffer
            line.assign(m_buffer, 0, term); // copy the line from the buffer
            m_buffer.erase(0, term + 2); // remove the line from the buffer
            return true;
        }
        // we still don't have a line in the buffer; receive some more data
        count = recv(m_socket, tmp, 256);
    }
    // failed to receive data; return failure
    return false;
}
1 голос
/ 25 декабря 2009

Два предложения:

  • проверьте возвращаемые значения всех функций сокетов, которые вы вызываете
  • ditch DevC ++ - он чертовски глючит и больше не разрабатывается - вместо него используйте http://www.codeblocks.org/.
1 голос
/ 25 декабря 2009

Я был бы немного обеспокоен вашим receive кодом. Он создает буфер char* для приема данных, но фактически не выделяет для него памяти.

Теперь я не могу сказать, вызываете ли вы WinSock recv, поскольку вы явно не говорите ::recv, но я думаю, вам нужно либо:

  • выделить некоторое пространство сначала malloc (id recv хочет буфер); или
  • передать адрес указателя буфера (если recv выделяет свой собственный буфер).

Я на самом деле удивлен, что это не вызывает дамп ядра, поскольку значение buffer может быть установлено на что угодно при вызове recv.

Что-то вроде этого может быть лучше:

char *Messager::receive(void){
    int newSocket = 0;
    struct sockaddr_storage *senderAddress;
    socklen_t addressSize;
    char *buffer;

    addressSize = sizeof senderAddress;
    newSocket = accept(listenSocket, (struct sockaddr *)&senderAddress,
        &addressSize);

    buffer = new char[20];
    recv(newSocket, buffer, 20, 0);

    return buffer;

}

Но вы должны помнить, что клиент этой функции отвечает за освобождение буфера, когда он закончил с ним.

0 голосов
/ 25 декабря 2009

В вашей функции receive локальный buffer никогда ни к чему не инициализируется, поэтому вы в конечном итоге читаете ваше сообщение в некоторой случайной памяти и, вероятно, вызываете повреждение или сбой. Вы, вероятно, хотите char buffer[MAX_MSG_LENGTH]; вместо char *buffer

...