BSD套接字 - 如何使用非阻塞套接字?

时间:2011-07-14 20:25:29

标签: c++ sockets tcp bsd nonblocking

我正在尝试使用非阻塞TCP套接字。问题是它们仍在阻塞。代码如下 -

服务器代码 -

struct sockaddr name;
char buf[80];

void set_nonblock(int socket) {
    int flags;
    flags = fcntl(socket,F_GETFL,0);
    assert(flags != -1);
    fcntl(socket, F_SETFL, flags | O_NONBLOCK);
}

int main(int agrc, char** argv) {

    int sock, new_sd, adrlen;   //sock is this socket, new_sd is connection socket

    name.sa_family = AF_UNIX;
    strcpy(name.sa_data, "127.0.0.1");
    adrlen = strlen(name.sa_data) + sizeof(name.sa_family);

    //make socket
    sock = socket(AF_UNIX, SOCK_STREAM, 0);

    if (sock < 0) {
        printf("\nBind error %m", errno);
        exit(1);
    }

    //unlink and bind
    unlink("127.0.0.1");
    if(bind (sock, &name, adrlen) < 0)
        printf("\nBind error %m", errno);

    //listen
    if(listen(sock, 5) < 0)
        printf("\nListen error %m", errno);

    //accept
    new_sd = accept(sock, &name, (socklen_t*)&adrlen);
    if( new_sd < 0) {
        cout<<"\nserver accept failure "<<errno;
        exit(1);
    }

    //set nonblock
    set_nonblock(new_sd);

    char* in = new char[80];
    std::string out = "Got it";
    int numSent;
    int numRead;

    while( !(in[0] == 'q' && in[1] == 'u' && in[2] == 'i' && in[3] == 't') ) {

        //clear in buffer
        for(int i=0;i<80;i++)
            in[i] = ' ';

        cin>>out;
        cin.get();

        //if we typed something, send it
        if(strlen(out.c_str()) > 0) {
            numSent = send(new_sd, out.c_str(), strlen(out.c_str()), 0);
            cout<<"\n"<<numSent<<" bytes sent";
        }

        numRead = recv(new_sd, in, 80, 0);
        if(numRead > 0)
            cout<<"\nData read from client - "<<in;

     }   //end while

     cout<<"\nExiting normally\n";
     return 0;
}

客户端代码 -

struct sockaddr name;

void set_nonblock(int socket) {
    int flags;
    flags = fcntl(socket,F_GETFL,0);
    assert(flags != -1);
    fcntl(socket, F_SETFL, flags | O_NONBLOCK);
}

int main(int agrc, char** argv) {

    int sock, new_sd, adrlen;

    sock = socket(AF_UNIX, SOCK_STREAM, 0);

    if (sock < 0) {
        printf("\nserver socket failure %m", errno);
        exit(1);
    }

    //stuff for server socket
    name.sa_family = AF_UNIX;
    strcpy(name.sa_data, "127.0.0.1");
    adrlen = strlen(name.sa_data) + sizeof(name.sa_family);

    if(connect(sock, &name, adrlen) < 0) {
        printf("\nclient connection failure %m", errno);
        exit(1);
    }

    cout<<"\nSuccessful connection\n";

    //set nonblock
    set_nonblock(sock);

    std::string out;
    char* in = new char[80];
    int numRead;
    int numSent;


    while(out.compare("quit")) {

        //clear in
        for(int i=0;i<80;i++)
            in[i] = '\0';


        numRead = recv(sock, in, 80, 0);

        if(numRead > 0)
            cout<<"\nData read from server - "<<in;


        cout<<"\n";
        out.clear();
        cin>>out;
        cin.get();

        //if we typed something, send it
        if(strlen(out.c_str())) {
            numSent = send(sock, out.c_str(), strlen(out.c_str()), 0);
            cout<<"\n"<<numSent<<" bytes sent";
        }

    }   //end while


    cout<<"\nExiting normally\n";
    return 0;
}

每当我运行它时,服务器仍会等待我发送内容,然后才会读取并输出客户端发送的内容。我希望服务器或客户端能够在我键入消息后立即发送消息,然后让另一个读取并输出消息。我认为非阻塞套接字是答案,但也许我只是做错了什么?

另外,我使用的是一个文件而不是我的127.0.0.1地址作为sockaddr的数据。如果这不是应该如何正确使用,请随意这样说(它的工作原理如何以前使用文件,所以我只是保持这样)。

感谢任何帮助。

4 个答案:

答案 0 :(得分:4)

要在同一时间处理多个连接的TCP服务器的一般方法:

  • 使侦听套接字无阻塞
  • 将其添加到select(2)poll(2) 阅读事件集
  • 输入select(2) / poll(2)循环
  • 在唤醒时检查它是否是侦听套接字
    • accept(2)
    • 检查是否失败(客户端可能暂时放弃了连接尝试)
    • 使新创建的客户端套接字无阻塞,将其添加到轮询事件集
  • else,如果它是客户端套接字之一
    • 消费输入,处理它
    • 注意EAGAIN错误代码 - 这不是一个错误,但表示现在没有输入
    • 如果读取零字节 - 客户端关闭连接,close(2)客户端套接字,则将其从事件集
    • 中删除
  • 重新初始化事件集(省略这是select(2)
  • 的常见错误
  • 重复循环

客户端更简单,因为您只有一个套接字。处理许多连接的Web浏览器等高级应用程序通常会进行非阻塞connect(2)

答案 1 :(得分:3)

我认为你必须尽快设置非阻塞(即获取套接字然后将其设置为非阻塞)

还检查fcntl设置它实际上是否有效

答案 2 :(得分:3)

  

每当我运行它时,服务器仍会等待我发送内容,然后才会读取并输出客户端发送的内容。

嗯,这就是你写它的方式。你从stdin上阻止了IO,然后才发送/接收。

cin>>out;
cin.get();

此外,您正在使用本地套接字(AF_UNIX)在文件系统中创建一个特殊文件以进行进程间通信 - 这是一种与IP不同的机制,并且绝对不是您在问题中指出的TCP。我假设您可以将文件命名为 127.0.0.1 ,但这实际上没有意义并且意味着您的混淆,因为这是一个IP环回地址。您需要将AF_INET用于IP。

关于unix网络的优秀入门指南,我建议http://beej.us/guide/bgnet/

如果您希望显示收到的消息与您的cin语句无关,请fork()关闭单独的进程以处理您的网络IO,或使用单独的线程。

您可能对select()感兴趣。在我看来,非阻塞套接字通常是一个hack,正确使用select()或poll()通常更好的设计和更灵活(和更便携)。试试

  

man select_tut

了解更多信息。

答案 3 :(得分:0)

如果您想要非阻塞i / o,则需要使用select。您可以将stdin设置为它正在侦听的套接字之一,以及客户端套接字(只需将文件描述符1(stdin)添加到fd_set)。

http://beej.us/guide/bgnet/output/html/multipage/advanced.html

我建议阅读beej关于选择的内容。它看起来有点令人生畏,但是如果你花点时间把它包裹起来,它真的很有用而且很简单。