服务器套接字无法正常工作 - "accept is already open"

Server socket doesn't work properly - "accept is already open"

我试图将我的服务器套接字分离成单例。这是代码:

ServerSocket.h

#pragma once
#include <asio.hpp>
#include <iostream>

using asio::ip::tcp;

class ServerSocket
{
public:
    ServerSocket(ServerSocket& otherSingleton) = delete;
    void operator=(const ServerSocket& copySingleton) = delete;

    tcp::acceptor* InitAcceptor();
    tcp::socket* InitSocket();
    void StartServerSocket();
    void SendData(std::string);
    std::array<char, 5000> RecieveData();

    static ServerSocket* GetInstance();
private:
    static ServerSocket* instance;

    tcp::acceptor* acceptor;
    tcp::socket* socket;
    asio::io_context io_context;

    ServerSocket() {
        acceptor = InitAcceptor();
        socket = InitSocket();
    }

    ~ServerSocket()
    {
        std::cout << "Server closed";
    }
};

ServerSocket.cpp

#include "ServerSocket.h"

tcp::acceptor* ServerSocket::InitAcceptor()
{
    try
    {
        tcp::acceptor* acceptor = new tcp::acceptor(io_context, tcp::endpoint(tcp::v4(), 27015));

        return acceptor;
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}

tcp::socket* ServerSocket::InitSocket()
{
    try
    {
        tcp::socket* socket = new tcp::socket(io_context);

        return socket;
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}

void ServerSocket::StartServerSocket()
{
    try
    {
        std::cout << "Server started";
        for (;;)
        {
            acceptor->accept(*socket);
        };
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}

std::array<char, 5000> ServerSocket::RecieveData()
{
    try {
        std::array<char, 5000> buf;
        asio::error_code error;

        size_t len = socket->read_some(asio::buffer(buf), error);
        buf[len] = '[=12=]';
        return buf;
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
}

ServerSocket* ServerSocket::instance(nullptr);

ServerSocket* ServerSocket::GetInstance()
{
    if (instance == nullptr)
    {
        instance = new ServerSocket();
    }
    return instance;
}

服务器套接字启动,我得到:

Server started

当客户端连接时,我得到:

accept: Already open 

然后服务器停止。

我认为错误来自接受器在 for 函数中。但根据文档,它应该以这种方式工作。 (或者至少我是这么理解的 - https://think-async.com/Asio/asio-1.20.0/doc/asio/tutorial/tutdaytime2.html

我尝试删除 for 循环,如下所示:

try
    {
        std::cout << "Server started";
        acceptor->accept(*socket);
    }

现在没有问题了。但是服务器没有保持连接打开。客户端连接一次,发送数据,服务器停止运行.

据我从文档中了解到,如果我在 for(;;) 中设置接受器,它应该是 运行 - 但它在我的情况下不起作用。

那么,如何在我的实现中保持我的套接字打开?我希望它 运行 用于多个 SendData - 我希望它能够与客户端通信,只要客户端已连接。

谢谢。

//编辑:

这是客户端代码:

#include <iostream>
#include <asio.hpp>
#include "../../cereal/archives/json.hpp"

using asio::ip::tcp;

int main(int argc, char* argv[])
{

    try
    {
        if (argc != 2)
        {
            std::cerr << "Usage: client <host>" << std::endl;
            return 1;
        }

        // Socket Parameters
        const unsigned port = 27015;
        auto ip_address = asio::ip::make_address_v4(argv[1]);
        auto endpoint = tcp::endpoint{ ip_address, port };

        // Creating and Connecting the Socket
        asio::io_context io_context;
        auto resolver = tcp::resolver{ io_context };
        auto endpoints = resolver.resolve(endpoint);

        auto socket = tcp::socket{ io_context };
        asio::connect(socket, endpoints);

        std::array<char, 5000> buf;
        std::cout << "Message to server: ";

        asio::error_code ignored_error;

        std::string username = "test", password = "mihai";
        std::stringstream os;
        {
            cereal::JSONOutputArchive archive_out(os);
            archive_out(
                CEREAL_NVP(username),
                CEREAL_NVP(password)
            );
        }

        asio::write(socket, asio::buffer(os.str()), ignored_error);
        return 0;
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        return 1;
    }

和Communication.h负责从客户端捕获操作并将其发送到服务器

#pragma once
#include <iostream>
#include "DBUser.h"
#include "DBPost.h"

class Communication
{
public:
    enum class Operations {
        eLogin,
        eRegister
    };
    void ExecuteOperation(Operations operation,const std::array<char, 5000>& buffer);
};

.cpp

#include "Communication.h"

void Communication::ExecuteOperation(Operations operation,const std::array<char, 5000>& buffer)
{
    DBUser* user= DBUser::getInstance();
    switch (operation)
    {
    case Communication::Operations::eLogin:
    {
        std::string username, password;
        std::stringstream is(buffer.data());
        {
            cereal::JSONInputArchive archive_in(is);
            archive_in(username,password);
        }
        try
        {
            user->LoginUser(username, password);
        }
        catch (const std::exception& e)
        {
            std::cout << e.what();
        }
        break;
    }
    case Communication::Operations::eRegister:
    {
        std::string username, password;
        std::stringstream is(buffer.data());
        {
            cereal::JSONInputArchive archive_in(is);
            archive_in(username, password);
        }
        try
        {
            user->CreateUser(username, password);
        }
        catch (const std::exception& e)
        {
            std::cout << e.what();
        }
        break;
    }
    }
}

主要

#include <iostream>
#include <pqxx/pqxx>
#include "DBLink.h"
#include "DBUser.h"
#include "DBPost.h"
#include "../Logging/Logging.h"
#include <iostream>
#include <string>
#include <asio.hpp>
#include "ServerSocket.h"
#include "Communication.h"

int main()
{
    ServerSocket* test = ServerSocket::GetInstance();
    test->StartServerSocket();

    std::array<char, 5000> buf = test->RecieveData();
    Communication communicationInterface;
    communicationInterface.ExecuteOperation(Communication::Operations::eRegister, buf);
system("pause");
}

根据问题中的服务器代码编辑完整的工作示例:

// main.cxx
#include "ServerSocket.hxx"
#include <boost/asio.hpp>
#include <iostream>
#include <string>
using boost::asio::ip::tcp;

int
main ()
{
  ServerSocket *test = ServerSocket::GetInstance ();
  test->StartServerSocket ();
  std::cout << std::endl;
  while (auto msg = test->RecieveData ())
    {
      std::cout << msg.value ();
    }
}
// ServerSocket.hxx
#pragma once
#include <boost/asio.hpp>
#include <iostream>
#include <optional>

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

class ServerSocket
{
public:
  ServerSocket (ServerSocket &otherSingleton) = delete;
  void operator= (const ServerSocket &copySingleton) = delete;

  tcp::acceptor *InitAcceptor ();
  tcp::socket *InitSocket ();
  void StartServerSocket ();
  void SendData (std::string);
  std::optional<std::string> RecieveData ();

  static ServerSocket *GetInstance ();

private:
  static ServerSocket *instance;

  tcp::acceptor *acceptor;
  tcp::socket *socket;
  boost::asio::io_context io_context;

  ServerSocket ()
  {
    acceptor = InitAcceptor ();
    socket = InitSocket ();
  }

  ~ServerSocket () { 
    delete socket;
    delete acceptor;
    std::cout << "Server closed"; }
};
// ServerSocket.cxx
#include "ServerSocket.hxx"
#include <optional>

tcp::acceptor *
ServerSocket::InitAcceptor ()
{
  try
    {
      return new tcp::acceptor (io_context, tcp::endpoint (tcp::v4 (), 27015));
    }
  catch (std::exception &e)
    {
      std::cerr << e.what () << std::endl;
    }
  return nullptr;
}

tcp::socket *
ServerSocket::InitSocket ()
{
  try
    {
      return new tcp::socket (io_context);
    }
  catch (std::exception &e)
    {
      std::cerr << e.what () << std::endl;
    }
  return nullptr;
}

void
ServerSocket::StartServerSocket ()
{
  try
    {
      std::cout << "Server started";
      acceptor->accept (*socket);
    }
  catch (std::exception &e)
    {
      std::cerr << e.what () << std::endl;
    }
}

std::optional<std::string>
ServerSocket::RecieveData ()
{
  try
    {
      char data[5000];
      for (;;)
        {
          boost::system::error_code error;
          size_t length = socket->read_some (boost::asio::buffer (data), error);
          if (error == boost::asio::error::eof) return std::nullopt; // Connection closed cleanly by peer.
          else if (error)
            throw boost::system::system_error (error); // Some other error.
          return std::string{ data, length };
        }
    }
  catch (std::exception &e)
    {
      std::cerr << e.what () << std::endl;
    }
  return {};
}

ServerSocket *ServerSocket::instance (nullptr);

ServerSocket *
ServerSocket::GetInstance ()
{
  if (instance == nullptr)
    {
      instance = new ServerSocket ();
    }
  return instance;
}

请注意,服务器还存在一些问题:

  • 错误处理
  • 不止一个连接
  • 如果操作成功,服务器不会发送消息
  • 如果断开客户端,服务器将关闭
  • 我们可以将一些指针替换为可选的,不需要写“new”
  • 做个普通的就可以了class不要写成单例

如果你想测试服务器,你可以运行

telnet localhost 27015

然后写一些文字然后回车

出现了很多反模式。

  1. 过度使用指针。

  2. 过度使用 new(没有任何 delete,保证泄漏)

  3. 析构函数声称“服务器已关闭”,但它实际上并没有做任何事情来实现这一点。

  4. 两步初始化(InitXXXX 函数)。首先,你显然应该喜欢初始化列表

    ServerSocket()
     : acceptor_(InitAcceptor()), socket_(InitSocket())
    { }
    

    并且您需要将InitAcceptor/InitSocket设为私有实现。

  5. 我会忘记 Singleton,它在 99% 的情况下都是反模式的,但我想这 几乎 值得商榷。

在您的 StartServerSocket 中,您有一个循环一直重复使用相同的 socket。当然,它已经连接了。您需要单独的套接字实例:

    for (;;) {
        acceptor_->accept(*socket_);
    };

Simplify/Fix

#include <boost/asio.hpp>
#include <iostream>

namespace asio = boost::asio;
using asio::ip::tcp;

struct Listener {
    void Start()
    {
        std::cout << "Server started";
        for (;;) {
            auto socket = acceptor_.accept();
            std::cout << "Accepted connection from " << socket.remote_endpoint()
                      << std::endl;
        };
    }

    static Listener& GetInstance() {
        static Listener s_instance{27015}; // or use weak_ptr for finite lifetime
        return s_instance;
    }

  private:
    asio::io_context ioc_; // order of declaration is order of init!
    tcp::acceptor    acceptor_;

    Listener(uint16_t port) : acceptor_{ioc_, tcp::endpoint{tcp::v4(), port}} {}
};

int main() {
    try {
        Listener::GetInstance().Start();
    } catch (std::exception const& e) {
        std::cerr << e.what() << std::endl;
    }
}

现在您可以将 socket 个实例交给一个线程。我同意其他评论者的观点,即每个请求一个线程可能 也是 一种反模式,您应该考虑将异步 IO 与 Asio 一起使用(因此得名)。

现场演示