首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >服务器和客户端套接字连接问题。send()、accept()和多线程

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

Stack Overflow用户
提问于 2011-10-28 21:46:00
回答 2查看 2.5K关注 0票数 2

我正在用C++设计一个服务器程序来接收多个客户端连接,并将它们传递到线程中,但是我遇到了一个僵局。

套接字连接都工作得很好,多线程几乎也是如此。请看我下面的代码(它编译并运行良好)。

我已经试着把它缩减到最基本的部分,让你更容易理解,占用你最少的时间。我已经注释了代码,以帮助您了解问题所在,然后我将在底部详细描述问题。如果你能帮助我,我将不胜感激!

代码语言:javascript
运行
复制
#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()返回到该行:

代码语言:javascript
运行
复制
server.accept(*client);

然后等待前一个连接通过send()将其结果发送回客户端,然后它才会接受新的连接-即服务器在接受新的客户端之前等待线程中发生的事情!我不希望它这样做--我希望它将客户端连接发送到线程,然后直接接受更多客户端连接,并将它们传递给更多线程!

如果你想知道为什么我在这里创建了一个指向套接字的指针...

代码语言:javascript
运行
复制
ServerSocket *client = new ServerSocket();

..。如果我没有创建指针,则线程调用的recv()函数无法从客户端接收数据,这似乎是由于线程浅复制客户端套接字连接,垃圾收集器不理解线程,认为客户端连接在传递给线程后将不再使用,因此在线程中调用recv()之前将其销毁。因此,使用在堆上创建的指针,这是有效的。无论如何,当我使用fork()而不是线程(这意味着我不需要在堆上创建套接字)重新编写代码时,我仍然遇到服务器无法接受新客户端的相同问题。

我想我需要以某种方式更改服务器设置,这样它就不会在接受新的设置之前等待客户端发送(),然而,尽管我在谷歌上搜索了很多次,我仍然不知所措!

下面是相关的套接字连接代码,以防万一(服务器和客户端都在同一个机器上,因此通过本地UNIX套接字连接):

代码语言:javascript
运行
复制
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;
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2011-11-15 00:44:29

我已经弄明白了!客户端不是多线程的原因是创建客户端连接的程序是在互斥中创建的-因此它不会创建新的连接,直到旧的连接从服务器接收到响应,因此服务器看起来只是单线程的!所以简而言之,我上面的服务器程序是好的,这是一个客户端的问题-抱歉浪费了您的时间-我甚至没有考虑这种可能性,直到我将线程放在客户端,从而完全重新设计了程序结构,这就暴露了问题。

谢谢你的帮助!

票数 1
EN

Stack Overflow用户

发布于 2011-11-11 01:53:18

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

下面是如何使套接字成为非阻塞的:

代码语言:javascript
运行
复制
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操作的套接字),当可以接受新连接时,它将准备好读取。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7930101

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档