1. 程式人生 > >nginx iocp(1):tcp非同步連線

nginx iocp(1):tcp非同步連線

   iocp是Windows NT作業系統的一種高效IO模型,對應於Linux中的epoll和FreeBSD中的kqueue,nginx對ske(select、kqueue和epoll的首寫字母組合)的支援很好,但截止到1.6.2版本,還不支援iocp。由於ske都是反應器模式,即先註冊IO事件,當IO事件發生(讀寫通知)時,在其回撥內主動呼叫API來讀或寫資料;而iocp是前攝器模式,要先投遞IO操作,才能引發IO事件(完成通知)的發生,在其回撥內資料已被動由作業系統讀或寫完成。因此,iocp的特點決定了nginx對它的支援與ske有所不同。通過hg clone http://hg.nginx.org/nginx下載的nginx原始碼,雖然實現了iocp事件模組、非同步接受連線、部分非同步讀寫,但根本不能正常工作,而且不支援非同步連線和SCM服務控制,筆者在參考ske模組的實現基礎上,改進支援瞭如下特性:
      1. 非同步接受連線時的負載均衡
      2. 正反向代理的非同步連線
      3. 非同步聚合讀寫
      4. 域名解析時的UDP非同步接收
      5. 非同步檔案傳輸
      6. SCM服務控制
   由於2、4、6均為原創,其它幾點的思路皆源於ske模組的實現(只是平臺API不同),因此本文先闡述非同步連線的實現。為了相容select事件模組,所有iocp相關的程式碼使用NGX_HAVE_IOCP巨集和(或)NGX_USE_IOCP_EVENT標誌包圍,其中NGX_HAVE_IOCP巨集用於條件編譯,在WIN32平臺下,定義為1;當選擇的事件模組為iocp時,全域性變數ngx_event_flags才包含NGX_USE_IOCP_EVENT標誌。

非同步連線對端
   由ngx_event_connect_peer函式(這裡省去了與非同步連線無關的程式碼)實現,定義在event/ngx_event_connect.c中,因為connect不支援非同步連線事件的完成通知,所以要使用擴充套件API ConnectEx。   1ngx_int_t ngx_event_connect_peer(ngx_peer_connection_t *pc)
 2{
 3    int                rc;
 4    ngx_int_t          event;
 5    ngx_err_t          err;
 6    ngx_uint_t         level,family;
 7    ngx_socket_t       s;
 8    ngx_event_t       *rev, *wev;
 9    10    s = ngx_socket(family = pc->sockaddr->sa_family, SOCK_STREAM, 0);
11              
12    #if (NGX_HAVE_IOCP)13    if((pc->local==NULL||pc->local->sockaddr->sa_family 
!= family) 
14        && (ngx_event_flags & NGX_USE_IOCP_EVENT)){
15        if(ngx_iocp_set_localaddr(pc->log,family,&pc->local) != NGX_OK)
16            goto failed;    
17    }
18    #endif19       
20    
21    #if (NGX_HAVE_IOCP)22    if(ngx_event_flags&NGX_USE_IOCP_EVENT){        
23        LPWSAOVERLAPPED   ovlp;
24        ovlp = (LPWSAOVERLAPPED)&wev->ovlp;
25        ngx_memzero(ovlp,sizeof(WSAOVERLAPPED));
26        wev->ovlp.type = NGX_IOCP_CONNECT;
27        rc =ngx_connectex(s,pc->sockaddr,pc->socklen,NULL,0,NULL,ovlp) ?0 : -1;
28    
29    }
else30        rc = connect(s, pc->sockaddr, pc->socklen);
31    #else32      rc = connect(s, pc->sockaddr, pc->socklen);
33    #endif34    
35    if (rc ==-1{
36        err = ngx_socket_errno;
37        if (err != NGX_EINPROGRESS
38    #if (NGX_WIN32)39        /* Winsock returns WSAEWOULDBLOCK (NGX_EAGAIN) */40        && err != NGX_EAGAIN
41    #if (NGX_HAVE_IOCP)42        &&err !=WSA_IO_PENDING43    #endif44    #endif45            ){
46            
47            ngx_log_error(level, c->log, err, "connect() to %V failed", pc->name);
48            ngx_close_connection(c);
49            pc->connection = NULL;
50          
51            return NGX_DECLINED;
52        }
53    }
54    
55}
   呼叫ConnectEx前要先bind本地地址,不然發生WSAEINVAL錯誤;由於域名解析可能返回IPv6記錄,導致建立本地套接字的地址族為AF_INET6,因此bind時需要匹配IPv6地址,不然發生WSAEFAULT錯誤,導致nginx返回Internal Server Error錯誤給前端,因此繫結前要呼叫ngx_iocp_set_localaddr設定正確的本地地址,當且僅當pc->local為空或地址族不匹配時。

本地初始化與設定
   支援IPv6,實現在event/modules/ngx_iocp_module.c。
   地址變數定義如下。
1staticstruct sockaddr_in  sin;
2#if (NGX_HAVE_INET6)3staticstruct sockaddr_in6  sin6;
4#endif5static ngx_addr_t           local_addr;    sin對應IPv4,sin6對應IPv6,作為bind的套接字本地地址。

   sin和sin6在啟動iocp事件模組時呼叫ngx_iocp_init初始化。     1static ngx_int_t ngx_iocp_init(ngx_cycle_t *cycle, ngx_msec_t timer)
 2{
 3    
 4  sin.sin_family = AF_INET;
 5  sin.sin_port =0;
 6  sin.sin_addr.s_addr = INADDR_ANY;
 7    
 8#if (NGX_HAVE_INET6) 9    sin6.sin6_family = AF_INET6;
10    sin6.sin6_port =0;
11    sin6.sin6_addr = in6addr_any;
12#endif13    
14  local_addr.name.len =sizeof("INADDR_ANY"-1;
15  local_addr.name.data = (u_char *)"INADDR_ANY";    
16    
17}
   不論IP地址或埠,都指定為0,表示由系統自動分配出口IP地址和未佔用的埠。

   本地設定由ngx_iocp_set_localaddr實現。
 1ngx_int_t ngx_iocp_set_localaddr(ngx_log_t *log, in_port_t family, ngx_addr_t **local)
 2{
 3    struct sockaddr *sa;
 4    socklen_t len;
 5    
 6    if(AF_INET == family){            
 7        sa =&sin;
 8        len =sizeof(struct sockaddr_in);        
 9    }
10#if (NGX_HAVE_INET6)11    elseif(AF_INET6 == family){
12        sa =&sin6;
13        len =sizeof(struct sockaddr_in6);
14    }
15#endif16    else{
17        ngx_log_error(NGX_LOG_ALERT, log, 0"not supported address family");
18        return NGX_ERROR;        
19    }
20
21    local_addr.sockaddr = sa;
22    local_addr.socklen = len;23    *local =&local_addr;
24
25    return NGX_OK;
26}
   對於除IPv4和IPv6外的協議族,則記錄一個錯誤日誌。必要時也可擴充套件支援其它的協議族,例如NetBIOS(對應地址族為AF_NETBIOS),但要看ConnectEx是否支援。 posted on 2015-06-24 17:02 春秋十二月 閱讀(5750) 評論(1)  編輯 收藏 引用 所屬分類: Opensrc