简体   繁体   English

提升异步tcp客户端

[英]boost async tcp client

I've just started working with boost. 我刚刚开始使用boost。 I'm writting TCP client-server with async sockets. 我正在使用异步套接字编写TCP客户端服务器。

The task is the following: 任务如下:

  1. Client send to server a number 客户端向服务器发送一个号码
  2. Client can send another nubmer before receiving server's answer. 客户端可以在收到服务器的答案之前发送另一个nubmer。
  3. Server receives a number, do some computing with it and send back the result to client. 服务器接收一个号码,用它做一些计算并将结果发送回客户端。
  4. Multiple clients can be connected to server. 多个客户端可以连接到服务器。

Now works the following 现在工作如下

  • send a number from client to sever 从客户端向服务器发送号码
  • server recieves a number in current thread and computes right in the OnReceive handler (I know this is bad...but how I should start a new thread to do computing in parallel) 服务器接收当前线程中的数字并在OnReceive处理程序中计算(我知道这很糟糕......但我应该如何开始一个新的线程来并行执行计算)
  • server sends answer back but client already disconnected 服务器发回应答但客户端已断开连接

How can allow client to input numbers from keyboard and to wait an answer from the server at the same time? 如何允许客户端从键盘输入数字并同时等待服务器的回答?

And why does my client not wait for the answer from sever? 为什么我的客户不等待服务器的答案?

The client code: 客户端代码:

using boost::asio::ip::tcp;

class TCPClient
{
    public:
        TCPClient(boost::asio::io_service& IO_Service, tcp::resolver::iterator EndPointIter);
        void Close();

    private:
        boost::asio::io_service& m_IOService;
        tcp::socket m_Socket;

        string m_SendBuffer;
        static const size_t m_BufLen = 100;
        char m_RecieveBuffer[m_BufLen*2];

        void OnConnect(const boost::system::error_code& ErrorCode, tcp::resolver::iterator EndPointIter);
        void OnReceive(const boost::system::error_code& ErrorCode);
        void OnSend(const boost::system::error_code& ErrorCode);
        void DoClose();
};

TCPClient::TCPClient(boost::asio::io_service& IO_Service, tcp::resolver::iterator EndPointIter)
: m_IOService(IO_Service), m_Socket(IO_Service), m_SendBuffer("")
{
    tcp::endpoint EndPoint = *EndPointIter;

    m_Socket.async_connect(EndPoint,
        boost::bind(&TCPClient::OnConnect, this, boost::asio::placeholders::error, ++EndPointIter));
}

void TCPClient::Close()
{
    m_IOService.post(
        boost::bind(&TCPClient::DoClose, this));
}
void TCPClient::OnConnect(const boost::system::error_code& ErrorCode, tcp::resolver::iterator EndPointIter)
{
    cout << "OnConnect..." << endl;
    if (ErrorCode == 0)
    {
        cin >> m_SendBuffer;
        cout << "Entered: " << m_SendBuffer << endl;
        m_SendBuffer += "\0";

        m_Socket.async_send(boost::asio::buffer(m_SendBuffer.c_str(),m_SendBuffer.length()+1),
            boost::bind(&TCPClient::OnSend, this,
            boost::asio::placeholders::error));
    } 
    else if (EndPointIter != tcp::resolver::iterator())
    {
        m_Socket.close();
        tcp::endpoint EndPoint = *EndPointIter;

        m_Socket.async_connect(EndPoint, 
            boost::bind(&TCPClient::OnConnect, this, boost::asio::placeholders::error, ++EndPointIter));
    }
}

void TCPClient::OnReceive(const boost::system::error_code& ErrorCode)
{
    cout << "receiving..." << endl;
    if (ErrorCode == 0)
    {
        cout << m_RecieveBuffer << endl;

        m_Socket.async_receive(boost::asio::buffer(m_RecieveBuffer, m_BufLen),
            boost::bind(&TCPClient::OnReceive, this, boost::asio::placeholders::error));
    } 
    else 
    {
        cout << "ERROR! OnReceive..." << endl;
        DoClose();
    }
}

void TCPClient::OnSend(const boost::system::error_code& ErrorCode)
{
    cout << "sending..." << endl;
    if (!ErrorCode)
    {
        cout << "\""<< m_SendBuffer <<"\" has been sent" << endl;
        m_SendBuffer = "";

        m_Socket.async_receive(boost::asio::buffer(m_RecieveBuffer, m_BufLen),
            boost::bind(&TCPClient::OnReceive, this, boost::asio::placeholders::error));
    }
    else
    {
        cout << "OnSend closing" << endl;
        DoClose();
    }

}

void TCPClient::DoClose()
{
    m_Socket.close();
}

int main()
{
    try 
    {
        cout << "Client is starting..." << endl;
        boost::asio::io_service IO_Service;

        tcp::resolver Resolver(IO_Service);

        string port = "13";
        tcp::resolver::query Query("127.0.0.1", port);

        tcp::resolver::iterator EndPointIterator = Resolver.resolve(Query);

        TCPClient Client(IO_Service, EndPointIterator);

        cout << "Client is started!" << endl;

        cout << "Enter a query string " << endl;

        boost::thread ClientThread(boost::bind(&boost::asio::io_service::run, &IO_Service));

        Client.Close();
        ClientThread.join();
    } 
    catch (exception& e)
    {
        cerr << e.what() << endl;
    }

    cout << "\nClosing";
    getch();
}

Here is output from console 这是控制台的输出

Client is starting...
Client is started!
OnConnect...
12
Entered: 12
sending...
"12" has been sent
receiving...
ERROR! OnReceive...

Closing

Server part 服务器部分

class Session
{
    public:
        Session(boost::asio::io_service& io_service)
            : socket_(io_service)
        {
            dataRx[0] = '\0';
            dataTx[0] = '\0';
        }

        tcp::socket& socket()
        {
            return socket_;
        }

        void start()
        {
            socket_.async_read_some(boost::asio::buffer(dataRx, max_length),
                boost::bind(&Session::handle_read, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred));
        }

        void handle_read(const boost::system::error_code& error, size_t bytes_transferred)
        {
            cout << "reading..." << endl;
            cout << "Data: " << dataRx << endl;

            if (!error)
            {
                if (!isValidData())
                {
                    cout << "Bad data!" << endl;
                    sprintf(dataTx, "Bad data!\0");
                    dataRx[0] = '\0';
                }
                else
                {
                    sprintf(dataTx, getFactorization().c_str());
                    dataRx[0] = '\0';
                }

                boost::asio::async_write(socket_,
                    boost::asio::buffer(dataTx, max_length*2),
                    boost::bind(&Session::handle_write, this,
                    boost::asio::placeholders::error));
            }
            else
            {
                delete this;
            }
        }

        void handle_write(const boost::system::error_code& error)
        {
            cout << "writing..." << endl;
            if (!error)
            {
                cout << "dataTx sent: " << dataTx << endl;
                dataTx[0] = '\0';

                socket_.async_read_some(boost::asio::buffer(dataRx, max_length),
                    boost::bind(&Session::handle_read, this,
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));
            }
            else
            {
                delete this;
            }
        }

        string getFactorization() const
        {
            //Do something
        }

        bool isValidData()
        {
            locale loc; 
            for (int i = 0; i < strlen(dataRx); i++)
                if (!isdigit(dataRx[i],loc))
                    return false;

            return true;
        }

    private:
        tcp::socket socket_;
        static const size_t max_length = 100;
        char dataRx[max_length];
        char dataTx[max_length*2];
};

class Server
{
    public:
        Server(boost::asio::io_service& io_service, short port)
            : io_service_(io_service),
            acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
        {
            Session* new_session = new Session(io_service_);
            acceptor_.async_accept(new_session->socket(),
                boost::bind(&Server::handle_accept, this, new_session,
                boost::asio::placeholders::error));
        }

        void handle_accept(Session* new_session, const boost::system::error_code& error)
        {
            if (!error)
            {
                new_session->start();
                new_session = new Session(io_service_);
                acceptor_.async_accept(new_session->socket(),
                    boost::bind(&Server::handle_accept, this, new_session,
                    boost::asio::placeholders::error));
            }
            else
            {
                delete new_session;
            }
        }

    private:
        boost::asio::io_service& io_service_;
        tcp::acceptor acceptor_;
};

int main(int argc, char* argv[])
{
    cout << "Server is runing..." << endl;
    try
    {
        boost::asio::io_service io_service;

        int port = 13;
        Server s(io_service, port);
        cout << "Server is run!" << endl;
        io_service.run();
    }
    catch (boost::system::error_code& e)
    {
        std::cerr << e << "\n";
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

Server's ouput 服务器的输出

Server is runing...
Server is run!
reading...
Data: 12
writing...
dataTx sent: 13    //just send back received ++number
reading...
Data:

Your help will be very appreciated 非常感谢您的帮助

======== ========

Added 添加

Ok, I understand. 好的,我明白了。 But check ErrorCode == boost::asio::error::eof does not works... What have I done wrong? 但检查ErrorCode == boost :: asio :: error :: eof不起作用......我做错了什么?

else if (ErrorCode == boost::asio::error::eof)
{
    cout << "boost::asio::error::eof in OnReceive!" << endl;
}
else 
{
    cout << "ERROR! OnReceive..." << ErrorCode << endl;
    DoClose();
}

The print out is ERROR! OnReceive...system:10009 打印输出ERROR! OnReceive...system:10009 ERROR! OnReceive...system:10009 it seems to be my comparison is incorrect ERROR! OnReceive...system:10009似乎是我的比较是不正确的

======== ========

Added 添加

I found the root cause. 我找到了根本原因。 I've stated use async_receive (instead of async_read_some ) and swaped the lines in main to 我说使用async_receive (而不是async_read_some )和swaped该行main

ClientThread.join();
Client.Close();

Now it works fine! 现在它工作正常!

Now I'm trying to read and write data from/to socket at the same time (because the client should be able to sent additional requests before answer from the server is recieved. 现在我正在尝试同时从/向套接字读取和写入数据(因为客户端应该能够在收到服务器的回复之前发送其他请求。

In OnConnect function I create boost threads: OnConnect函数中,我创建了boost线程:

boost::thread addMsgThread(boost::bind(&TCPClient::addMsgLoop, this));
boost::thread receivingThread(boost::bind(&TCPClient::startReceiving, this));
boost::thread sendingThread(boost::bind(&TCPClient::startSending, this));

with inplementation 与实施

void TCPClient::startReceiving()
{
    cout << "receiving..." << endl;
    m_RecieveBuffer[0] = '\0';
    m_Socket.async_receive(boost::asio::buffer(m_RecieveBuffer, m_BufLen),
        boost::bind(&TCPClient::receivingLoop, this, boost::asio::placeholders::error)); //runtime error here
    cout << "m_RecieveBuffer = " << m_RecieveBuffer << endl;
}

void TCPClient::receivingLoop(const boost::system::error_code& ErrorCode)
{
    cout << "receiving..." << endl;
    if (ErrorCode == 0)
    {
        cout << "m_RecieveBuffer = " << m_RecieveBuffer << endl;

        m_Socket.async_receive(boost::asio::buffer(m_RecieveBuffer, m_BufLen),
            boost::bind(&TCPClient::receivingLoop, this, boost::asio::placeholders::error));
    }
    else 
    {
        cout << "ERROR! receivingLoop..." << ErrorCode << endl;
        DoClose();
    }
}

void TCPClient::addMsgLoop()
{
    while (true)
    {
        string tmp;
        cin >> tmp;

        cout << "Entered: " << tmp << endl;
        tmp += "\0";

        try
        {
            msgQueue.push(tmp);
        }
        catch(exception &e)
        {
            cerr << "Canno add msg to send queue... " << e.what() << endl;
        }
    }
}

The issue is the same with both receive and send threads: runtime error (writing access violation somewhere in boost libraries). receivesend线程的问题是相同的:运行时错误(在boost库中的某处写入访问冲突)。

void TCPClient::startReceiving()
{
     ...
     m_Socket.async_receive(); //runtime error here
}

In sequent version all works fine (but I don't know how to implement multiple sending before answer). 在后续版本中一切正常(但我不知道如何在回答之前实现多个发送)。 Can anybody tell me how to fix the issue or how implement this by another way? 任何人都可以告诉我如何解决问题或如何通过另一种方式实现这个问题? May be pooling can help but I'm now sure that it is good way. 可能是汇集可以帮助,但我现在肯定这是好方法。

boost::asio::ip::tcp::socket::async_read_some as the name suggests is not guaranteed to read complete data. boost :: asio :: ip :: tcp :: socket :: async_read_some顾名思义不保证读取完整数据。 It sets error object to boost::asio::error::eof when client is finished writing. 当客户端完成写入时,它将error对象设置为boost::asio::error::eof

The error you are getting is because of this: 你得到的错误是因为:

server part 服务器部分

        if (!error)
        {
            ...
        }
        else
        {
            delete this;
        }

In else block, you are assuming that this is a error case and closing the connection. else块中,您假设这是一个错误情况并关闭连接。 This is not always the case. 这并非总是如此。 Before else you need to check for error == boost::asio::error::eof . else之前你需要检查error == boost::asio::error::eof

Apart from this in read handler, you should keep collecting whatever is read in a buffer till you hit error == boost::asio::error::eof . 除了读取处理程序中的这个,你应该继续收集缓冲区中读取的内容,直到你遇到error == boost::asio::error::eof Only then you should validate read data and write back to client. 只有这样,您才应验证读取数据并写回客户端。

Take a look at HTTP server 1 , 2 , 3 implementation in examples section. 看一看HTTP服务器123在实施例子一节。

Update: Answer to updated question 更新:回答更新的问题

You have thread synchronization issue with the updated code. 您有更新的代码的线程同步问题。

  1. msgQueue is simultaneously accessed from two or more threads without any lock. 同时从两个或多个线程访问msgQueue而没有任何锁定。
  2. Read and write on the same socket can be called simultaneously. 可以同时调用同一个套接字上的读写。

If I understood your problem correctly, you want to: 如果我理解你的问题,你想:

  1. take user input and send that to server. 获取用户输入并将其发送到服务器。
  2. Keep receiving server's response simultaneously. 继续同时接收服务器的响应。

You can use two boost::asio::io_service::strands for the two tasks. 您可以为这两个任务使用两个boost :: asio :: io_service :: strand When using Asio, strands are the way to synchronize your tasks. 使用Asio时,strands是同步任务的方式。 Asio makes sure that tasks posted in a strand are executed synchronously. Asio确保在一个链中发布的任务是同步执行的。

  1. In strand1 post a send task that looks like: read_user_input -> send_to_server -> handle_send -> read_user_input strand1发布一个类似于以下read_user_input -> send_to_server -> handle_send -> read_user_inputsend任务: read_user_input -> send_to_server -> handle_send -> read_user_input

  2. In strand2 post a read task that looks like: read_some -> handle_read -> read_some strand2发布一个read任务,如下所示: read_some -> handle_read -> read_some

This will make sure msgQueue is not accessed simultaneously from two threads. 这将确保不会从两个线程同时访问msgQueue Use two sockets for read and write to server, to make sure simultaneous read and write is not called on the same socket. 使用两个套接字对服务器进行读写操作,以确保不会在同一个套接字上调用同时读写。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM