可以“随时”终止的socket线程

很多人在刚学socket时,都是在线程中connect,然后while(flag) read();要停止这个线程时将falg置false,再wait,甚至直接termination(这种方式终止线程的安全隐患不在这里论述)。

一般情况下的确没什么问题,但拿到一个真正的项目中时,就不太好了,这样写就很可能出现这样的情况:网络情况不好时,connect和read可能需要很长时间才能返回,将flag置false时,可能还需要等待很久(最大等待时间和平台有关),也许长达几分钟,特别是在GUI线程中去停止这个线程时,很可能会造成界面卡死。

那么该如何解决这种问题呢?我的做法是,在connect之前,将socket置为非阻塞模式,这时再使用connect时,会立即返回,我们判断它的返回值,如果没有连接成功(内核还在继续为我们建立连接),即使用select去监测它,发现它连接成功时,再不愿成阻塞模式。

这样做还不够,因为select也是阻塞函数,但select是可以监测多个文件描述符的,我们可以创建一个pipe(还有eventfd之类),让select同时监测pipe和socket,在我们需要停止connect时,向pipe的写端写入数据,此时select就会被唤醒,当我们发现是由pipe唤醒select时,即直接释放相关资源后退出线程。

对于read部分,我们一样使用select同时监测pipe和socket,使用同样的方法退出。

这种方法相对可以更快更安全的终止线程,示例代码如下(注意,以下代码纯记事本写的,可能存在错误,仅供参考):

int MyThread::connect()

{

    int fd;

    /// 此处略过部分代码 ///

    

    unsigned long ul = 1;

    ioctl(fd, FIONBIO, &ul);		/// 设置为非阻塞模式

    if (connect(fd, (struct sockaddr *)&raddr, sizeof(struct sockaddr_in)) == -1)

    {

    	int error = 0;

    	int max_fd = ( fd > _pipe_fd[0] ? fd : _pipe_fd[0] ) + 1;

    	

    	fd_set set;

    	FD_ZERO(&set);

    	FD_SET(fd, &set);

    

    	fd_set rset;

    	FD_ZERO(&rset);

    	FD_SET(_pipe_fd[0], &rset);	/// 将pipe读端加入select,便于线程外随时退出select

    	

    	if ( ! (error = (select(max_fd, &rset, &set, NULL, NULL) <= 0) ) )		/// error == 0  !error == 1表示没发生错误,进一步检查错误

    	{

    		if (FD_ISSET(fd, &set))

    		{

    			int len = 0;

    			getsockopt(fd + 1, SOL_SOCKET, SO_ERROR, (char*)&error, &len);

    		}

    		else if (FD_ISSET(_pipe_fd[0], &rset))

    		{

    		    /// 接收到退出命令

    		    /// 如果退出后还需要复用到pipe,此处建议将pipe里的东西read出来以清空

    			error = -2;

    		}

    		else

    		{

    			/// 如果发生错误,或者由pipe唤醒,则终止连接

    			error = -1;

    		}

    	}

    	if (error)

    	{

    		close(fd);

    		return -1;

    	}

    }

    ul = 0;

    ioctl(sockfd, FIONBIO, &ul);	/// 还原为阻塞模式

    return fd;

}



void MyThread::run()

{

    int fd = connect();

    if (fd < 0)

    {

        return;

    }

    

    fd_set rset;

    int ret;

    int max_fd = ( fd > _pipe_fd[0] ? fd : _pipe_fd[0] ) + 1;

    while (true)

    {

        FD_ZERO(&rset);

        FD_SET(fd, &rset);

        FD_SET(_pipe_fd[0], &rset);

        

        ret = select(fd + 1, &rset, NULL, NULL, NULL);

        if (ret < 0)

        {

            break;

        }

        if (ret)

        {

            if (FD_ISSET(_pipe_fd[0], &rset))

            {

                break;

            }

            if (FD_ISSET(fd, &rset))

            {

                read();

            }

        }

    }

    close(fd);

}

  

以种方法只是我目前的处理方式,应该还有更好的方式(有很多优秀的库目前我还没有去深入学习)。

高手们可以飘过了,不过最好留下点指导吧。

你可能感兴趣的:(socket)