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

时间:2011-10-28 13:46:00

标签: c++ multithreading sockets send

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

2 个答案:

答案 0 :(得分:1)

我已经明白了!客户端不是多线程的原因是创建客户端连接的程序是在互斥锁中进行的 - 因此它不会创建新的连接,直到旧的连接从服务器收到回复,因此服务器似乎只是单线程!所以总之我上面的服务器程序很好,这在客户端是一个问题 - 抱歉浪费你的时间 - 我甚至没有考虑过这种可能性,直到我通过在客户端放置线程来完全重写程序结构,然后揭示了这个问题。

感谢您的帮助!

答案 1 :(得分:0)

您的套接字阻止!这意味着他们将在返回之前等待操作完成。

这就是你如何使套接字无阻塞:

bool nonblock(int sock)
{
    int flags;

    flags = fcntl(sock, F_GETFL, 0);
    flags |= O_NONBLOCK;

    return (fcntl(sock, F_SETFL, flags) == 0);
}

现在函数acceptreadwrite都会在套接字阻塞时返回错误,将errno变量设置为EWOULDBLOCK或者可能EAGAIN

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