服务器和客户端套接字连接问题重新。send(), accept() 和多线程

Server & client socket connection issue re. send(), accept() and multi-threading

本文关键字:send accept 多线程 套接字 客户端 连接 问题 服务器      更新时间:2023-10-16

我正在用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;
}

我明白了!客户端不是多线程的原因是,创建客户端连接的程序是在互斥对象中创建的——因此,在旧连接收到服务器的回复之前,它不会创建新连接,因此服务器看起来只是单线程的!所以简而言之,我上面的服务器程序是好的,这是一个问题在客户端-抱歉浪费你的时间-我甚至没有考虑到这种可能性,直到我完全重做了程序结构,把线程放在客户端,然后揭示了这个问题。

谢谢你的帮助!

您的套接字阻塞!这意味着它们将等待操作完成后再返回。

这是如何使套接字非阻塞:

bool nonblock(int sock)
{
    int flags;
    flags = fcntl(sock, F_GETFL, 0);
    flags |= O_NONBLOCK;
    return (fcntl(sock, F_SETFL, flags) == 0);
}

现在函数accept, readwrite都将返回一个错误,如果套接字将阻塞,设置errno变量为EWOULDBLOCKEAGAIN

如果你想等待一个套接字准备好读写,你可以使用select函数。对于侦听套接字(您执行accept的套接字),当可以接受新连接时,它将准备好读取。