简体   繁体   English

如何在已经阻塞的情况下将C中的TCP Server从阻塞模式更改为非阻塞模式?或者如何正确关闭阻塞的TCP Server?

[英]How to change TCP Server In C from Blocking Mode to Non-Blocking Mode when it's already blocking Or How to shutdown a blocking TCP Server properly?

I have no problems with running the TCP Server and I like the fact that it's in blocking to avoid useless loops and sleeping code and useless cpu cycles. 我在运行TCP Server时没有问题,我喜欢它处于阻塞状态,以避免无用的循环和休眠代码以及无用的cpu周期。

The problem happens when shutting it down in Linux environment, it stays on, until the connected user sends something, then it turns off. 在Linux环境中关闭它时会发生问题,它一直存在,直到所连接的用户发送了一些东西,然后它才关闭。

I figured it's because it's blocking even though the endless while loop is set to exit. 我认为这是因为即使无尽的while循环设置为退出,它也会阻塞。 But when it's blocking changing the socket id's to NON_BLOCKING doesn't help at all, most likely has to be set to NON_BLOCKING before the block occurs. 但是,当它阻止将套接字ID更改为NON_BLOCKING完全没有帮助时,很可能必须在发生阻止之前将其设置为NON_BLOCKING。

#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h> /* Added for the nonblocking socket */

#define LISTEN_MAX 10       /* Maximum clients that can queue up */
#define LISTEN_PORT 32000
#define MAX_COMMANDS_AT_ONCE 4000
#define NANO_SECOND_MULTIPLIER  1000000  // 1 millisecond = 1,000,000 Nanoseconds

//Global so I can access these where I shut the threads off.
int listenfd, connfd; //sockets that must be set to non-blocking before exiting

int needQuit(pthread_mutex_t *mtx)
{
    switch(pthread_mutex_trylock(mtx)) {
        case 0: /* if we got the lock, unlock and return 1 (true) */
        pthread_mutex_unlock(mtx);
        return 1;
        case EBUSY: /* return 0 (false) if the mutex was locked */
        return 0;
    }
    return 1;
}

/* this is run on it's own thread */
void *tcplistener(void *arg)
{
    pthread_mutex_t *mx = arg;
    //keyboard event.

    SDLKey key_used;
    struct timespec ts;
    //int listenfd,connfd,
    int n,i, ans;
    struct sockaddr_in servaddr,cliaddr;
    socklen_t clilen;
    pid_t     childpid;
    char mesg[MAX_COMMANDS_AT_ONCE];

    listenfd=socket(AF_INET,SOCK_STREAM,0);

    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
    servaddr.sin_port=htons(LISTEN_PORT);
    int option = 1;
    if(setsockopt(listenfd, SOL_SOCKET,SO_REUSEADDR,(char*)&option,sizeof(option)) < 0)
    {
        printf("setsockopt failed\n");
        close(listenfd);
    }
    bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));

    listen(listenfd,LISTEN_MAX);

    while( !needQuit(mx) )
    {
        clilen=sizeof(cliaddr);
        connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&clilen);

        while( !needQuit(mx) )
        {
            n = recv(connfd,mesg,MAX_COMMANDS_AT_ONCE,0);

            if(n == 0 || n == -1) break;
            //...Do Stuff here with mesg...
        }
    }
    close(connfd);
}

close(connfd);
close(listenfd);
return NULL;
}

int main(int argc, char *argv[])
{
    /* this variable is our reference to the thread */
    pthread_t tcp_listener_thread;
    pthread_mutex_t mxq; /* mutex used as quit flag */

    /* init and lock the mutex before creating the thread.  As long as the
    mutex stays locked, the thread should keep running.  A pointer to the
    mutex is passed as the argument to the thread function. */
    pthread_mutex_init(&mxq,NULL);
    pthread_mutex_lock(&mxq);

    /* create a hread which executes tcplistener(&x) */
    if(pthread_create(&tcp_listener_thread, NULL, tcplistener, &mxq)) {
        fprintf(stderr, "Error creating TCP Listener thread\n");
        //clear thread for tcp listener on exit.
        /* unlock mxq to tell the thread to terminate, then join the thread */
        fcntl(listenfd, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state  */
        fcntl(connfd, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state    */

        pthread_mutex_unlock(&mxq);
        pthread_join(tcp_listener_thread,NULL);
        pthread_cancel(tcp_listener_thread);
        pthread_exit(NULL);
        return 0;
    }

    //End of the TCP Listener thread.


    // Waits 500 milliseconds before shutting down
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 500 * NANO_SECOND_MULTIPLIER;
    nanosleep((&ts, NULL);

    //Forces a shutdown of the program and thread.
    //clear thread for tcp listener on exit.
    /* unlock mxq to tell the thread to terminate, then join the thread */
    fcntl(listenfd, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state  */
    fcntl(connfd, F_SETFL, O_NONBLOCK); /* Change the socket into non-blocking state    */
    pthread_mutex_unlock(&mxq);
    pthread_join(tcp_listener_thread,NULL);
    pthread_cancel(tcp_listener_thread);
    pthread_exit(NULL);
    return 0;
}

I tried the fix EJP suggested like so, still hanging.. I've made connfd and listernfd both global scope 我尝试了EJP这样建议的修复程序,但仍未解决。.我已经将connfdlisternfd都设置为全局范围

pthread_mutex_unlock(&mxq); 
    close(connfd); //<- this
    close(listenfd); //<-- this
pthread_join(tcp_listener_thread,NULL);
pthread_cancel(tcp_listener_thread);
pthread_exit(NULL);

Your listener thread will indeed block in the accept(). 您的侦听器线程确实会阻塞accept()。

The nasty way to fix this (almost) is to send a signal to the listener thread with pthread_kill(). 解决此问题(几乎)的一种讨厌方法是使用pthread_kill()将信号发送到侦听器线程。 This causes accept() to return with errno == EINTR, which you test for and then return. 这将导致accept()返回errno == EINTR,对其进行测试然后返回。

However, that has a race condition: if the signal is received between testing the while (!needQuit(mx)) condition and entering the accept() then it'll be lost and the accept() will block again. 但是,这有一个竞态条件:如果在测试while(!needQuit(mx))条件与进入accept()之间接收到信号,则它将丢失,并且accept()将再次阻塞。

One correct way to solve this is to use something like select() and a pipe. 解决此问题的一种正确方法是使用诸如select()和管道的方法。 You select for read over the pipe and the socket. 您可以选择读取管道和承插口。 When the main thread wants the listener thread to exit it writes a byte to the pipe. 当主线程希望侦听器线程退出时,它将一个字节写入管道。 The listener thread's select() call returns either when a byte is readable from the pipe (in which case it exits) and/or when a client can be accepted. 侦听器线程的select()调用在从管道读取一个字节(在这种情况下退出)和/或可以接受客户端时返回。

Non-blocking sockets are primarily used to multiplex lots of sockets into one event loop (ie thread). 非阻塞套接字主要用于将大量套接字复用到一个事件循环(即线程)中。 That's a good idea for server scalability, but not necessary here. 对于服务器可伸缩性来说,这是一个好主意,但在此没有必要。

To unblock accept(), just close the listening socket. 要取消阻塞accept(),只需关闭监听套接字即可。 Make sure the code around accept() handles is correctly. 确保accept()句柄周围的代码正确。

To unblock recv(), shutdown the receiving socket for input. 要取消阻止recv(),请关闭接收套接字以进行输入。 That will cause recv() to return zero, which again must be handled correctly. 这将导致recv()返回零,这必须再次正确处理。 Or else just close the socket as above, which might be better if you want the receive code to know that you're closing the application. 否则,只需如上所述关闭套接字即可,如果您希望接收代码知道您正在关闭应用程序,则可能会更好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM