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 #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。
地址变量定义如下。
1static struct sockaddr_in sin;
2#if (NGX_HAVE_INET6)
3static struct sockaddr_in6 sin6;
4#endif
5static 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#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实现。
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 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
春秋十二月 阅读(7504)
评论(1) 编辑 收藏 引用 所属分类:
Opensrc