源碼分析
在茫茫的源碼中,看到了幾個好像挺熟悉的名字(socket/UDP/shmem)。那就來看看這個文件吧!從簡單的開始~~~
src/os/unix/Ngx_socket.h&Ngx_socket.c
源碼如下(可用Source Insight來看源碼,不錯的選擇):
/* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #ifndef _NGX_SOCKET_H_INCLUDED_ #define _NGX_SOCKET_H_INCLUDED_ #include <ngx_config.h> #define NGX_WRITE_SHUTDOWN SHUT_WR typedef int ngx_socket_t; #define ngx_socket socket #define ngx_socket_n "socket()" #if (NGX_HAVE_FIONBIO) int ngx_nonblocking(ngx_socket_t s); int ngx_blocking(ngx_socket_t s); #define ngx_nonblocking_n "ioctl(FIONBIO)" #define ngx_blocking_n "ioctl(!FIONBIO)" #else #define ngx_nonblocking(s) fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK) #define ngx_nonblocking_n "fcntl(O_NONBLOCK)" #define ngx_blocking(s) fcntl(s, F_SETFL, fcntl(s, F_GETFL) & ~O_NONBLOCK) #define ngx_blocking_n "fcntl(!O_NONBLOCK)" #endif int ngx_tcp_nopush(ngx_socket_t s); int ngx_tcp_push(ngx_socket_t s); #if (NGX_LINUX) #define ngx_tcp_nopush_n "setsockopt(TCP_CORK)" #define ngx_tcp_push_n "setsockopt(!TCP_CORK)" #else #define ngx_tcp_nopush_n "setsockopt(TCP_NOPUSH)" #define ngx_tcp_push_n "setsockopt(!TCP_NOPUSH)" #endif #define ngx_shutdown_socket shutdown #define ngx_shutdown_socket_n "shutdown()" #define ngx_close_socket close #define ngx_close_socket_n "close() socket" #endif /* _NGX_SOCKET_H_INCLUDED_ */
其中,創建socket的相關函數如下:
typedef int ngx_socket_t;//這個應該是套接口的描述符了 #define ngx_socket socket //只是替換了名稱,仍然是socket,沒什么可怕的 #define ngx_socket_n "socket()"
接下來:
#if (NGX_HAVE_FIONBIO) //FIONBIO:設置/ 清除非阻塞I/O 標志式 //使用ioctl來設置 int ngx_nonblocking(ngx_socket_t s); int ngx_blocking(ngx_socket_t s); #define ngx_nonblocking_n "ioctl(FIONBIO)" #define ngx_blocking_n "ioctl(!FIONBIO)" #else
//使用fcntl(點擊查看詳情)設定非阻塞狀態 #define ngx_nonblocking(s) fcntl(s, F_SETFL, fcntl(s, F_GETFL) | O_NONBLOCK)//fcntl(s,F_GETEL)獲取文件鎖定狀態 #define ngx_nonblocking_n "fcntl(O_NONBLOCK)" #define ngx_blocking(s) fcntl(s, F_SETFL, fcntl(s, F_GETFL) & ~O_NONBLOCK) #define ngx_blocking_n "fcntl(!O_NONBLOCK)" #endif
然后看看Ngnix自己定義的阻塞與非阻塞函數:
int ngx_nonblocking(ngx_socket_t s) { int nb; nb = 1; return ioctl(s, FIONBIO, &nb);//FIONBIO:設置/清除非阻塞I/O 標志 } int ngx_blocking(ngx_socket_t s) { int nb; nb = 0; return ioctl(s, FIONBIO, &nb); }
ioctl:提供對連接到fd的設備驅動程序的屬性和操作的訪問。
而fcntl:用來設置和修改描述符的的屬性。
兩種方法都可以用來設置socket阻塞與非阻塞模式。
再接着掃代碼~:
int ngx_tcp_nopush(ngx_socket_t s);
//setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,(const void *) &tcp_nopush, sizeof(int));---->FREEBSD
//setsockopt(s, IPPROTO_TCP, TCP_CORK,(const void *) &cork, sizeof(int)); ----->linux
int ngx_tcp_push(ngx_socket_t s);
//setsockopt(s, IPPROTO_TCP, TCP_NOPUSH,(const void *) &tcp_nopush, sizeof(int));----->FREEBSD
//setsockopt(s, IPPROTO_TCP, TCP_CORK,(const void *) &cork, sizeof(int)); ----->linux
這個指令指定是否使用socket的TCP_NOPUSH(FreeBSD)或TCP_CORK(linux)選項,這個選項只在使用sendfile時有效。設置這個選項的將導致nginx試圖將它的HTTP應答頭封裝到一個包中。
所謂的cork就是塞子的意思,形象地理解就是用cork將連接塞住,使得數據先不發出去,等到拔去塞子后再發出去,而nodelay事實上是為了禁用Nagle算法,Nagle算法為了增加了網絡的吞吐量而犧牲了響應時間體驗。
在進行大量數據發送的時候可以置位TCP_CORK關閉Nagle算法保證網絡利用性,盡可能的進行數據的組包,以最大mtu傳輸。
src/os/unix/Ngx_dup_recv.c
/* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #include <ngx_config.h> #include <ngx_core.h> #include <ngx_event.h> #if (NGX_HAVE_KQUEUE) ssize_t ngx_udp_unix_recv(ngx_connection_t *c, u_char *buf, size_t size) { ssize_t n; ngx_err_t err; ngx_event_t *rev; rev = c->read; do { n = recv(c->fd, buf, size, 0); ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "recv: fd:%d %d of %d", c->fd, n, size); if (n >= 0) { if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { rev->available -= n; /* * rev->available may be negative here because some additional * bytes may be received between kevent() and recv() */ if (rev->available <= 0) { rev->ready = 0; rev->available = 0; } } return n; } err = ngx_socket_errno; if (err == NGX_EAGAIN || err == NGX_EINTR) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "recv() not ready"); n = NGX_AGAIN; } else { n = ngx_connection_error(c, err, "recv() failed"); break; } } while (err == NGX_EINTR); rev->ready = 0; if (n == NGX_ERROR) { rev->error = 1; } return n; } #else /* ! NGX_HAVE_KQUEUE */ ssize_t ngx_udp_unix_recv(ngx_connection_t *c, u_char *buf, size_t size) { ssize_t n; ngx_err_t err; ngx_event_t *rev; rev = c->read; do { n = recv(c->fd, buf, size, 0); ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "recv: fd:%d %d of %d", c->fd, n, size); if (n >= 0) { return n; } err = ngx_socket_errno; if (err == NGX_EAGAIN || err == NGX_EINTR) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "recv() not ready"); n = NGX_AGAIN; } else { n = ngx_connection_error(c, err, "recv() failed"); break; } } while (err == NGX_EINTR); rev->ready = 0; if (n == NGX_ERROR) { rev->error = 1; } return n; } #endif /* NGX_HAVE_KQUEUE */
開始就看到了這個NGX_HAVE_KQUEUE~~google一下得到下面的內容~
kqueue(freebsd)與epoll(linux 2.6)兩個東西極其相似,寫好了一個之后,移到別外一個平台下,只要稍作修改就可以了,原理是一樣,個人認為,從功能角度來盾kqueue比epoll靈活得多。在寫kqueue的時候,內核幫你考慮好了不少東西。但是從效率來看,從我作的壓力測試來看epoll比kqueue強。
那么,就可以直接越過freebsd,直接看linux下的了,都很好理解。
ssize_t ngx_udp_unix_recv(ngx_connection_t *c, u_char *buf, size_t size) { ssize_t n; ngx_err_t err; ngx_event_t *rev; rev = c->read; do { n = recv(c->fd, buf, size, 0); //簡單的recv()函數 ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, "recv: fd:%d %d of %d", c->fd, n, size); //添加日志 if (n >= 0) { //接收數據成功,返回接收數據的大小 return n; } err = ngx_socket_errno; //errno if (err == NGX_EAGAIN || err == NGX_EINTR) {
//EAGAIN重試 EINTR由於信號中斷,沒讀到任何數據
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "recv() not ready"); n = NGX_AGAIN; } else { n = ngx_connection_error(c, err, "recv() failed"); break; } } while (err == NGX_EINTR); //EINTR由於信號中斷,沒讀到任何數據 rev->ready = 0; if (n == NGX_ERROR) { rev->error = 1; } return n; }
src/os/unix/shmem.c&shmem.h
從名稱shmem就能看出來share memory共享內存。
/* * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ #ifndef _NGX_SHMEM_H_INCLUDED_ #define _NGX_SHMEM_H_INCLUDED_ #include <ngx_config.h> #include <ngx_core.h> typedef struct { u_char *addr; size_t size; ngx_str_t name; ngx_log_t *log; ngx_uint_t exists; /* unsigned exists:1; */ } ngx_shm_t; ngx_int_t ngx_shm_alloc(ngx_shm_t *shm); void ngx_shm_free(ngx_shm_t *shm); #endif /* _NGX_SHMEM_H_INCLUDED_ */
其中,有個結構體ngx_shm_t:
typedef struct { u_char *addr; //共享內存的地址 size_t size; //大小 ngx_str_t name; //名稱 ngx_log_t *log; ngx_uint_t exists; /* unsigned exists:1; */ } ngx_shm_t;
和兩個相關的操作函數(分配/釋放共享內存----直接看Linux部分):
ngx_int_t ngx_shm_alloc(ngx_shm_t *shm) { int id; id = shmget(IPC_PRIVATE, shm->size, (SHM_R|SHM_W|IPC_CREAT));//使用linux下的shmget來創建一個共享內存對象 ...log... shm->addr = shmat(id, NULL, 0); //linux下把共享內存區對象映射到調用進程的地址空間 if (shm->addr == (void *) -1) { //失敗的話 ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmat() failed"); } if (shmctl(id, IPC_RMID, NULL) == -1) { //linux下完成對共享內存的控制 IPC_RMID:刪除這片共享內存 ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmctl(IPC_RMID) failed"); } return (shm->addr == (void *) -1) ? NGX_ERROR : NGX_OK; } void ngx_shm_free(ngx_shm_t *shm) { if (shmdt(shm->addr) == -1) { //與shmat函數相反,是用來斷開與共享內存附加點的地址,禁止本進程訪問此片共享內存 ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, "shmdt(%p) failed", shm->addr); } }
參考
http://www.ithov.com/linux/109313_5.shtml
http://machael.blog.51cto.com/829462/479941
http://blog.csdn.net/shellching/article/details/5592511

