nginx iocp(1):tcp非同步連線
阿新 • • 發佈:2018-12-27
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
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標誌。
非同步連線對端
2{
3 int rc;
4 ngx_int_t event;
5 ngx_err_t err;
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
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