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。
1
ngx_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
#endif
19
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
}else
30
rc = connect(s, pc->sockaddr, pc->socklen);
31
#else
32
rc = connect(s, pc->sockaddr, pc->socklen);
33
#endif
34
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_PENDING
43
#endif
44
#endif
45
)
{
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。
地址變量定義如下。
1
static struct sockaddr_in sin;
2
#if (NGX_HAVE_INET6)
3
static struct sockaddr_in6 sin6;
4
#endif
5
static ngx_addr_t local_addr;
sin對應IPv4,sin6對應IPv6,作為bind的套接字本地地址。
sin和sin6在啟動iocp事件模塊時調用ngx_iocp_init初始化。
1
static 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
#endif
13
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實現。
1
ngx_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
else if(AF_INET6 == family)
{
12
sa = &sin6;
13
len = sizeof(struct sockaddr_in6);
14
}
15
#endif
16
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
春秋十二月 閱讀(7625)
評論(1) 編輯 收藏 引用 所屬分類:
Opensrc