服务器与客户端套接字连接问题。 send()、accept() 和多线程
我正在用 C++ 设计一个服务器程序来接收多个客户端连接并将它们传递到线程中,但是我已经陷入了僵局。
套接字连接都工作正常,多线程也是如此 - 几乎。请参阅下面的我的代码(它编译并运行良好)。
我已尽力将其精简为必要的内容,以便您轻松掌握并占用最少的时间。我对代码进行了注释,以帮助您了解问题所在,然后我在底部详细描述问题。如果您能帮助我,我将非常感激!
#include <vector>
#include <boost/thread.hpp>
#include "unix_serverSocket.h"
#include "server.h"
extern const string socketAddress;
void do_stuff(ServerSocket *client)
{
string in;
string out;
try
{
/* Gets input until the client closes the connection, then throws an exception, breaking out of the loop */
while (true)
{
*client >> in; /* Receives data from client socket connection */
/* Assume the input is processed fine and returns the result into 'out' */
sleep(3); /* I've put sleep() here to test it's multithreading properly - it isn't */
*client << out; /* Returns result to client - send() is called here */
/* If I put sleep() here instead it multithreads fine, so the server is waiting for send() before it accepts a new client */
}
}
catch (SocketException &)
{
delete client;
return;
}
}
int main()
{
try
{
ServerSocket server(socketAddress);
while (true)
{
ServerSocket *client = new ServerSocket();
/* See below */
server.accept(*client);
boost::thread newThread(do_stuff, client);
}
}
catch (SocketException &e)
{
cout << "Error: " << e.description() << endl;
}
return 0;
}
将客户端套接字连接传递给线程后,main() 返回 该行:
server.accept(*client);
但随后等待前一个连接将其结果发送回 客户端在接受新连接之前通过 send() 发送消息 - 即服务器正在等待 在线程接受新客户端之前发生一些事情!我不 希望它这样做 - 我希望它将客户端连接发送到线程然后接受 立即更多客户端连接并将它们传递到更多线程!
如果你想知道为什么我在这里创建了一个指向套接字的指针......
ServerSocket *client = new ServerSocket();
如果我不创建指针那么线程调用的recv()函数无法从客户端接收数据,这似乎是由于线程浅复制客户端套接字连接和垃圾收集器不理解线程并认为客户端连接在传递给线程后将不再使用,因此在调用 recv() 之前销毁它线。因此使用在堆上创建的指针是有效的。无论如何,当我使用 fork() 而不是线程重新编写代码时(这意味着我不需要在堆上创建套接字),我仍然遇到同样的问题,服务器无法接受新客户端。
我想我需要以某种方式更改服务器设置,以便它在接受新设置之前不会等待客户端发送(),但是尽管进行了很多谷歌搜索,我仍然不知所措!
下面是相关的套接字连接代码,以防有帮助(服务器和客户端都在同一个机器上,因此通过本地 UNIX 套接字进行连接):
class Socket
{
private:
int sockfd;
struct sockaddr_un local;
public:
Socket();
virtual ~Socket();
bool create();
bool bind(const string &);
bool listen() const;
bool accept(Socket &) const;
bool send(const string &) const;
int recv(string &) const;
void close();
bool is_valid() const
{
return sockfd != -1;
}
};
bool Socket::create()
{
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (!is_valid())
{
return false;
}
int reuseAddress = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*) &reuseAddress, sizeof(reuseAddress)) == -1)
{
return false;
}
return true;
}
bool Socket::bind(const string &socketAddress)
{
if (!is_valid())
{
return false;
}
local.sun_family = AF_UNIX;
strcpy(local.sun_path, socketAddress.c_str());
unlink(local.sun_path);
int len = strlen(local.sun_path) + sizeof(local.sun_family);
int bind_return = ::bind(sockfd, (struct sockaddr *) &local, len);
if (bind_return == -1)
{
return false;
}
return true;
}
bool Socket::listen() const
{
if (!is_valid())
{
return false;
}
int listen_return = ::listen(sockfd, MAXCLIENTCONNECTIONS);
if (listen_return == -1)
{
return false;
}
return true;
}
bool Socket::accept(Socket &socket) const
{
int addr_length = sizeof(local);
socket.sockfd = ::accept(sockfd, (sockaddr *) &local, (socklen_t *) &addr_length);
if (socket.sockfd <= 0)
{
return false;
}
else
{
return true;
}
}
int Socket::recv(string &str) const
{
char buf[MAXRECV + 1];
str = "";
memset(buf, 0, MAXRECV + 1);
int status = ::recv(sockfd, buf, MAXRECV, 0);
if (status == -1)
{
cout << "status == -1 errno == " << errno << " in Socket::recv" << endl;
return 0;
}
else if (status == 0)
{
return 0;
}
else
{
str = buf;
return status;
}
}
bool Socket::send(const string &str) const
{
int status = ::send(sockfd, str.c_str(), str.size(), MSG_NOSIGNAL);
if (status == -1)
{
return false;
}
else
{
return true;
}
}
class ServerSocket : private Socket
{
public:
ServerSocket(const string &);
ServerSocket() {};
virtual ~ServerSocket();
void accept(ServerSocket &);
const ServerSocket & operator << (const string &) const;
const ServerSocket & operator >> (string &) const;
};
ServerSocket::ServerSocket(const string &socketAddress)
{
if (!Socket::create())
{
throw SocketException("Could not create server socket");
}
if (!Socket::bind(socketAddress))
{
throw SocketException("Could not bind to port");
}
if (!Socket::listen())
{
throw SocketException("Could not listen to socket");
}
}
void ServerSocket::accept(ServerSocket &socket)
{
if (!Socket::accept(socket))
{
throw SocketException("Could not accept socket");
}
}
const ServerSocket & ServerSocket::operator << (const string &str) const
{
if (!Socket::send(str))
{
throw SocketException("Could not write to socket");
}
return *this;
}
const ServerSocket & ServerSocket::operator >> (string &str) const
{
if (!Socket::recv(str))
{
throw SocketException("Could not read from socket");
}
return *this;
}
I'm designing a server program in C++ to receive multiple client connections and pass them into threads, however I've reached an impasse.
The socket connections all work fine, as does the multi-threading - almost. Please see my code below (it compiles and runs fine).
I've tried to pare it down to the essentials for you to make it easy to follow and take up the least of your time. I've commented the code to help you see where the problem is, then I describe the problem in detail at the bottom. If you can help me then I would be very grateful!
#include <vector>
#include <boost/thread.hpp>
#include "unix_serverSocket.h"
#include "server.h"
extern const string socketAddress;
void do_stuff(ServerSocket *client)
{
string in;
string out;
try
{
/* Gets input until the client closes the connection, then throws an exception, breaking out of the loop */
while (true)
{
*client >> in; /* Receives data from client socket connection */
/* Assume the input is processed fine and returns the result into 'out' */
sleep(3); /* I've put sleep() here to test it's multithreading properly - it isn't */
*client << out; /* Returns result to client - send() is called here */
/* If I put sleep() here instead it multithreads fine, so the server is waiting for send() before it accepts a new client */
}
}
catch (SocketException &)
{
delete client;
return;
}
}
int main()
{
try
{
ServerSocket server(socketAddress);
while (true)
{
ServerSocket *client = new ServerSocket();
/* See below */
server.accept(*client);
boost::thread newThread(do_stuff, client);
}
}
catch (SocketException &e)
{
cout << "Error: " << e.description() << endl;
}
return 0;
}
After a client socket connection has been passed to a thread, main() gets back to
the line:
server.accept(*client);
but then waits for the previous connection to send its result back to the
client via send() before it will accept a new connection - i.e. the server is waiting
for something to happen in the thread before it will accept a new client! I don't
want it to do this - I want it to send the client connection to a thread then accept
more client connections straight away and pass them into more threads!
In case you're wondering why I created a pointer to the socket here...
ServerSocket *client = new ServerSocket();
... if I don't create a pointer then the recv() function called by the thread fails to receive data from the client, which seems to be due to the thread shallow copying the client socket connection and the garbage collector not understanding threads and thinking the client connection is no longer going to be used after it has been passed to the thread and so destroying it before recv() is called in the thread. Hence using a pointer created on the heap, which worked. Anyway, when I reworked the code using fork() instead of threads (which meant I didn't need to create the socket on the heap), I still had the same problem with the server not being able to accept new clients.
I guess I need to change the server settings somehow so that it doesn't wait for a client to send() before accepting a new one, however despite much Googling I'm still at a loss!
Here's the relevant socket connection code in case it helps (the server and clients are all on the same box and thus connecting via local UNIX sockets):
class Socket
{
private:
int sockfd;
struct sockaddr_un local;
public:
Socket();
virtual ~Socket();
bool create();
bool bind(const string &);
bool listen() const;
bool accept(Socket &) const;
bool send(const string &) const;
int recv(string &) const;
void close();
bool is_valid() const
{
return sockfd != -1;
}
};
bool Socket::create()
{
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (!is_valid())
{
return false;
}
int reuseAddress = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*) &reuseAddress, sizeof(reuseAddress)) == -1)
{
return false;
}
return true;
}
bool Socket::bind(const string &socketAddress)
{
if (!is_valid())
{
return false;
}
local.sun_family = AF_UNIX;
strcpy(local.sun_path, socketAddress.c_str());
unlink(local.sun_path);
int len = strlen(local.sun_path) + sizeof(local.sun_family);
int bind_return = ::bind(sockfd, (struct sockaddr *) &local, len);
if (bind_return == -1)
{
return false;
}
return true;
}
bool Socket::listen() const
{
if (!is_valid())
{
return false;
}
int listen_return = ::listen(sockfd, MAXCLIENTCONNECTIONS);
if (listen_return == -1)
{
return false;
}
return true;
}
bool Socket::accept(Socket &socket) const
{
int addr_length = sizeof(local);
socket.sockfd = ::accept(sockfd, (sockaddr *) &local, (socklen_t *) &addr_length);
if (socket.sockfd <= 0)
{
return false;
}
else
{
return true;
}
}
int Socket::recv(string &str) const
{
char buf[MAXRECV + 1];
str = "";
memset(buf, 0, MAXRECV + 1);
int status = ::recv(sockfd, buf, MAXRECV, 0);
if (status == -1)
{
cout << "status == -1 errno == " << errno << " in Socket::recv" << endl;
return 0;
}
else if (status == 0)
{
return 0;
}
else
{
str = buf;
return status;
}
}
bool Socket::send(const string &str) const
{
int status = ::send(sockfd, str.c_str(), str.size(), MSG_NOSIGNAL);
if (status == -1)
{
return false;
}
else
{
return true;
}
}
class ServerSocket : private Socket
{
public:
ServerSocket(const string &);
ServerSocket() {};
virtual ~ServerSocket();
void accept(ServerSocket &);
const ServerSocket & operator << (const string &) const;
const ServerSocket & operator >> (string &) const;
};
ServerSocket::ServerSocket(const string &socketAddress)
{
if (!Socket::create())
{
throw SocketException("Could not create server socket");
}
if (!Socket::bind(socketAddress))
{
throw SocketException("Could not bind to port");
}
if (!Socket::listen())
{
throw SocketException("Could not listen to socket");
}
}
void ServerSocket::accept(ServerSocket &socket)
{
if (!Socket::accept(socket))
{
throw SocketException("Could not accept socket");
}
}
const ServerSocket & ServerSocket::operator << (const string &str) const
{
if (!Socket::send(str))
{
throw SocketException("Could not write to socket");
}
return *this;
}
const ServerSocket & ServerSocket::operator >> (string &str) const
{
if (!Socket::recv(str))
{
throw SocketException("Could not read from socket");
}
return *this;
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我已经想通了!客户端不是多线程的原因是创建客户端连接的程序是在互斥体中执行此操作 - 因此在旧连接收到服务器的答复之前它不会创建新连接,因此服务器似乎只能是单线程!简而言之,我上面的服务器程序很好,但这是客户端的问题 - 抱歉浪费了您的时间 - 我什至没有考虑这种可能性,直到我通过将线程放在客户端来完全重新设计程序结构,然后就暴露了这个问题。
感谢您的帮助!
I've figured it out! The reason the clients weren't multithreading was that the program creating the client connections was doing so within a mutex - hence it wouldn't create a new connection until the old one had received a reply from the server, and thus the server appeared to be only single-threading! So in short my server program above was fine and it was a problem at the client end - sorry for wasting your time - I didn't even consider the possibility until I completely reworked the program structure by putting the threading at the client end instead, which then revealed the issue.
Thanks for all your help!
您的套接字阻塞!这意味着他们将等待操作完成后再返回。
这就是使套接字成为非阻塞的方法:
现在,如果套接字阻塞,则函数
accept
、read
和write
都将返回错误,将errno
变量设置为EWOULDBLOCK
或可能的EAGAIN
。如果您想等待套接字准备好进行读取或写入,您可以使用函数
select
。对于侦听套接字(您接受
的套接字),当可以接受新连接时,它将准备好读取。Your sockets are blocking! This means that they will wait for the operation to finish before returning.
This is how you make a socket non-blocking:
Now the functions
accept
,read
andwrite
will all return an error if the socket would block, setting theerrno
variable toEWOULDBLOCK
or possiblyEAGAIN
.If you want to wait for a socket to be ready for reading or writing, you can use the function
select
. For listening sockets (the one you doaccept
on) it will be ready to read when a new connection can be accepted.