服务器套接字无法正常工作 - "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 ©Singleton) = 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
然后写一些文字然后回车
出现了很多反模式。
过度使用指针。
过度使用 new
(没有任何 delete
,保证泄漏)
析构函数声称“服务器已关闭”,但它实际上并没有做任何事情来实现这一点。
两步初始化(InitXXXX 函数)。首先,你显然应该喜欢初始化列表
ServerSocket()
: acceptor_(InitAcceptor()), socket_(InitSocket())
{ }
并且您需要将InitAcceptor
/InitSocket
设为私有实现。
我会忘记 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 一起使用(因此得名)。
现场演示
我试图将我的服务器套接字分离成单例。这是代码:
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 ©Singleton) = 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
然后写一些文字然后回车
出现了很多反模式。
过度使用指针。
过度使用
new
(没有任何delete
,保证泄漏)析构函数声称“服务器已关闭”,但它实际上并没有做任何事情来实现这一点。
两步初始化(InitXXXX 函数)。首先,你显然应该喜欢初始化列表
ServerSocket() : acceptor_(InitAcceptor()), socket_(InitSocket()) { }
并且您需要将
InitAcceptor
/InitSocket
设为私有实现。我会忘记 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 一起使用(因此得名)。