Home > programming > lthread代码分析(四)

lthread代码分析(四)

 我们知道,一个调用阻塞socket的recv操作会一直等到有数据到达以后才会返回(采用了MSG_DONTWAIT标志的不算)。如果通过异步socket来实现,那么需要使用select或者epoll,然后通过类似回调的方式来告知有数据到达。有了lthread以后呢,这个步骤就可以由lthread来实现,而用户代码只需要像以前使用阻塞socket那样直接调用recv(这里要替换成lthread的recv函数了)就可以了。在代码逻辑上会比直接使用select/epoll来的简洁得多。我们来看看lthread里面关于socket部分的实现。

#define LTHREAD_RECV(x, y)                                  
x {                                                         
    ssize_t ret = 0;                                        
    lthread_t *lt = lthread_get_sched()->current_lthread;   
    while (1) {                                             
        if (lt->state & bit(LT_FDEOF))                      
            return -1;                                      
        ret = y;                                            
        if (ret == -1 && errno != EAGAIN)                   
            return -1;                                      
        if ((ret == -1 && errno == EAGAIN)) {               
            if (timeout)                                    
                _sched_lthread(lt, timeout);                
            _lthread_wait_for(lt, fd, LT_READ);             
            if (lt->state & bit(LT_EXPIRED))                
                return -2;                                  
        }                                                   
        if (ret >= 0)                                       
            return ret;                                     
    }                                                       
}

上面的宏定义里面,我们可以看出当一个接收数据操作(可能是recv也可能是recvfrom)被调用以后,如果遇到返回值为EAGAIN,那么就会调用_sched_lthread等待一个timeout时间。在等待的过程中如果该相关的fd上有可读事件发生,那么相关的_lthread就会被调用到。如果超时以后还没有事件到达,这个recv操作就会返回-2.

#define LTHREAD_SEND(x, y)                                  
x {                                                         
    ssize_t ret = 0;                                        
    ssize_t sent = 0;                                       
    lthread_t *lt = lthread_get_sched()->current_lthread;   
    while (sent != length) {                                
        if (lt->state & bit(LT_FDEOF))                      
            return -1;                                      
        ret = y;                                            
        if (ret == 0)                                       
            return sent;                                    
        if (ret > 0)                                        
            sent += ret;                                    
        if (ret == -1 && errno != EAGAIN)                   
            return -1;                                      
        if (ret == -1 && errno == EAGAIN)                   
            _lthread_wait_for(lt, fd, LT_WRITE);            
    }                                                       
    return sent;                                            
}                                                           

同理,发送数据也是一样的,当系统缓冲区能够容纳数据的时候,返回成功。否则等待相关fd的可写事件发生以后再向其缓冲区写入数据。

当然,上面的发送和接收表明,相关的socket必须是非阻塞的,否则无法达到这些效果。下面的lthread_socket函数就说明了这个问题

int
lthread_socket(int domain, int type, int protocol)
{
    int fd;
#if defined(__FreeBSD__) || defined(__APPLE__)
    int set = 1;
#endif

    if ((fd = socket(domain, type, protocol)) == -1) {
        perror("Failed to create a new socket");
        return -1;
    }
    /*把socket设置被非阻塞的*/
    if ((fcntl(fd, F_SETFL, O_NONBLOCK)) == -1) {
        close(fd);
        perror("Failed to set socket properties");
        return -1;
    }

#if defined(__FreeBSD__) || defined(__APPLE__)
    if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &set, sizeof(int)) == -1) {
        close(fd);
        perror("Failed to set socket properties");
        return -1;
    }
#endif

    return fd;
}

当然其他的还有accept和connect之类的操作基本上也是差不多的,只要遇到EAGAIN就会调用lthread调度器切换到其他的lthread上,同时监听fd感性却的事件。
有了这个lthread和socket的绑定,撰写concurrent的socket程序应该会简单很多的

Categories: programming Tags: ,
  1. No comments yet.
  1. No trackbacks yet.