【问题标题】:Server & client socket connection issue re. send(), accept() and multi-threading服务器和客户端套接字连接问题重新。 send()、accept() 和多线程
【发布时间】:2011-12-17 07:45:23
【问题描述】:

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

【问题讨论】:

  • “服务器”上的 accept() 调用似乎有点“关闭”。在我使用过的语言/库中,accept() 不接受任何参数并返回一个套接字指针/实例/任何东西。不寻常的是(尽管并非不可想象),accept 取而代之的是一个引用参数。你的套接字库中的 accept() 真的是这样工作的吗?我的意思是,需要用 new() 创建一个套接字实例并将其传递给 accept() 有点不寻常。
  • 请显示调用accept()并启动线程的代码。
  • 如果您多次收到此信息,我们深表歉意!请参见上面 - 我已经编辑了我的原始帖子以包含一些套接字代码。至于启动线程的原因,这只是 Boost 库中的 boost::thread 构造函数,它在构造函数中第一个参数给出的函数处启动线程,然后将任何进一步的参数作为参数传递给该函数 - 有我的头文件中不再隐藏我自己的线程代码,它们都在 中。也可能应该提到它是一个 UNIX 套接字连接。谢谢
  • 您确定您的 C++ 环境中存在垃圾收集器吗? 99% 的 C++ 程序配置为在没有垃圾收集器的情况下运行。
  • 是的,我检查了套接字连接是否被破坏以及它们何时被破坏,这就是导致我在堆上创建连接的原因。

标签: c++ multithreading sockets send


【解决方案1】:

我想通了!客户端不是多线程的原因是创建客户端连接的程序是在互斥体中这样做的 - 因此它不会创建新连接,直到旧连接收到来自服务器的回复,因此服务器似乎只能是单线程!所以简而言之,我上面的服务器程序很好,但在客户端是个问题——很抱歉浪费你的时间——我什至没有考虑这种可能性,直到我通过将线程放在客户端来完全重新设计程序结构,然后揭示了这个问题。

感谢您的帮助!

【讨论】:

    【解决方案2】:

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

    这就是使套接字非阻塞的方式:

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

    现在函数acceptreadwrite 都会在套接字阻塞时返回错误,将errno 变量设置为EWOULDBLOCKEAGAIN

    如果您想等待套接字准备好进行读取或写入,您可以使用函数select。对于侦听套接字(您在 accept 上所做的那个),它将在可以接受新连接时准备好读取。

    【讨论】:

    • 感谢您的回复 - 解锁套接字并使用 select() 并不是我真正想要的,因为这将是多路复用而不是多线程。但是,我终于找到了解决方案 - 见下文!
    猜你喜欢
    • 2021-03-31
    • 2016-12-21
    • 1970-01-01
    • 1970-01-01
    • 2014-06-04
    • 1970-01-01
    • 2014-04-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多