C-Unix Sockets - 非阻塞读取

9
我正在尝试创建一个简单的客户端-服务器聊天程序。在客户端,我会开启另一个线程来读取任何来自服务器的传入数据。问题是,当用户从主线程注销时,我想要优雅地终止第二个线程。我尝试使用共享变量“running”来终止,但是,socket read() 命令是一条阻塞命令,因此如果我执行 while(running == 1),服务器必须发送一些内容,然后 read() 才会返回并再次检查 while 条件。我正在寻找一种方法(仅使用常见的 Unix sockets)来进行非阻塞读取,基本上某种形式的 peek() 将起到作用,这样我就可以不断检查循环以查看是否完成。
下面是读取线程循环,现在它没有任何共享变量的互斥体,但我计划稍后添加! ;)
void *serverlisten(void *vargp)
{
    while(running == 1)
    {
        read(socket, readbuffer, sizeof(readbuffer));
        printf("CLIENT RECIEVED: %s\n", readbuffer);
    }
    pthread_exit(NULL);
}
4个回答

10

您可以像另一个帖子中建议的那样使套接字不可阻塞,并使用 select 等待具有超时的输入,代码如下:

fd_set         input;
FD_ZERO(&input);
FD_SET(sd, &input);
struct timeval timeout;
timeout.tv_sec  = sec;
timeout.tv_usec = msec * 1000;
int n = select(sd + 1, &input, NULL, NULL, &timeout);
if (n == -1) {
    //something wrong
} else if (n == 0)
    continue;//timeout
if (!FD_ISSET(sd, &input))
   ;//again something wrong
//here we can call not blockable read

在这个上下文中,sd+1是什么意思?我像这样运行它,它可以工作,但它会循环大约20次,然后停止,直到创建新的输入,但它确实像我想要的那样优雅地终止了,唯一的问题就是20次。它几乎就像是在超时期间运行一样。 - will
您可以在终端中键入“man select”以获取select函数的详细描述。作为第一个参数,select需要最大值加1的描述符。因为在这里我们只有一个描述符,所以我只写了(sd + 1)。 - fghj
不理解关于20次的注释。我的代码在你第一个问题的上下文中应该像这样工作:1)检查标志(是否退出?)2)休眠直到输入或超时3)读取一些内容4)转到(1)。因此,它会有一些延迟才能退出。但是,您应该处理另一端关闭连接的情况,然后选择将控制权返回给您,但“读取”将读取0字节,并且您会得到繁忙循环。因此,您应该检查“读取”的结果,并在其为零时退出循环。 - fghj
我解决了,谢谢,问题在于当没有输入时我在循环内打印输出,这是一个刷新问题,现在已经解决了,感谢你的帮助 :) - will

8
fcntl(socket, F_SETFL, O_NONBLOCK);

或者,如果您有其他标志:

int x;
x=fcntl(socket ,F_GETFL, 0);
fcntl(socket, F_SETFL, x | O_NONBLOCK);

然后检查read的返回值以查看是否有可用数据。

注意:通过谷歌搜索可以得到许多完整示例。

您还可以使用阻塞套接字,并使用带有超时的select进行“查看”。这似乎更适合在此处使用,以避免繁忙等待。


2
最好的方法可能是消除额外的线程,使用select()poll()在一个线程中处理所有内容。
如果您想保留线程,可以调用shutdown()并使用SHUT_RDWR关闭套接字连接,唤醒所有阻塞在其上的线程,但仍保持文件描述符有效。在加入读取器线程后,您可以关闭套接字。请注意,这仅适用于套接字,而不适用于其他类型的文件描述符。

1
寻找带有选项SO_RCVTIMEO的函数setsockopt

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