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

3

我想使用非阻塞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个回答

5

当您需要同时处理多个连接的TCP服务器的一般方法:

  • 将监听套接字设置为非阻塞
  • 将其添加到select(2)poll(2) 读取事件集中
  • 进入select(2)/poll(2)循环
  • 唤醒时检查它是否是监听套接字,然后
    • accept(2)
    • 检查失败(此时客户端可能已经放弃连接尝试)
    • 使新创建的客户端套接字非阻塞,并将其添加到轮询事件集中
  • 否则,如果是其中一个客户端套接字
    • 消耗输入,处理它
    • 注意EAGAIN错误代码 - 这不是真正的错误,而是没有输入的指示 现在
    • 如果读取零字节 - 客户端关闭连接,close(2)客户端套接字,从事件集中删除它
  • 重新初始化事件集(省略这一步是使用select(2)的常见错误)
  • 重复循环

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


3
每当我运行它时,服务器仍然在等待我发送一些内容,然后才会读取和输出客户端发送的内容。
嗯,这就是你编写的方式。你在标准输入/输出上阻塞,只有在这之后才能发送/接收数据。
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

获取更多信息。

谢谢,我会研究fork()和select()。每当我将所有AF_UNIX更改为AF_INET时,客户端尝试连接时会出现无效参数错误。我只需要在服务器端进行更改吗? - Sterling
好的,你需要将服务器和客户端都改为AF_INET。两者之间的区别很大——一个是本地(或Unix)套接字,另一个是网络套接字,具体来说是IP套接字。一定要看一下我发布的Beej指南,然后检查你的代码进行比较。希望这能有所帮助! - Josh
那就是我想表达的意思 - 我把它们都改了。我现在正在阅读链接。 - Sterling

3

我认为您需要尽早设置非阻塞模式(即获取套接字,然后将其设置为非阻塞模式)

同时检查fcntl是否成功设置了它


+1 表示“检查 fcntl 设置是否有效”。始终检查系统调用的返回值。一定要检查。 - Nemo
我在接收调用后立即将其设置为非阻塞。如果我将服务器套接字sock设置为非阻塞,当我尝试调用accept时,它会给我一个“资源暂时不可用”的错误。 - Sterling

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

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

我建议阅读一下Beej关于select的文章。它看起来有点吓人,但如果你花点时间理解它,它实际上非常有用且简单易用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接