前阵子在nginx中配置服务时,发现服务器只对外开放了80端口,若想服务器提供多项服务,就得考虑端口如何复用了。这里是通过域名也就是server_name字段来区分各项服务的。配置如下:
http {
...
server {
listen 80;
server_name blog.cn;
root /home/www/blog;
...
}
server {
listen 80;
server_name laravel.cn;
root /home/www/laravel/public;
...
}
}
上面只是配置的一个简版,我们也只需要关注这些就够了。配置了两个虚拟主机,监听同一个端口。然后在本地host文件中配置域名:
30.96.xx.xx blog.cn
39.96.xx.xx laravel.cn
39.96.xx.xx是服务器的外网地址,这样输入各个域名就能跳转到对应的服务了。
到这里算是实现了端口复用,但是就产生了一个疑问:一般服务器监听的是套接字,也就是ip地址和端口号,nginx是怎么识别到域名的呢?以及如果nginx提供的服务过多,查找服务时会不会影响效率呢?下面我们看下原理。
其实nginx中每起一个监听的端口号,都会分配一个ngx_http_conf_port_t结构体:
typedef struct {
//socket地址家族
ngx_int_t family;
//监听端口
in_port_t port;
//监听的端口下对应着所有的ngx_http_conf_addr_t地址
ngx_array_t addrs;
} ngx_http_conf_port_t;
看结构体成员可以知道除了有ip地址和端口号,还维护了一个ngx_http_conf_addr_t类型的动态数组。这个动态数组的类型也是一个结构体,定义如下:
typedef struct {
//监听套接字的各种属性
ngx_http_listen_opt_t opt;
//完全匹配server_name的散列表
ngx_hash_t hash;
...
//servers动态数组中的成员将指向ngx_http_core_srv_conf_t结构体
ngx_array_t servers;
} ngx_http_conf_addr_t;
中间也省去了部分成员,可以看到ngx_http_conf_addr_t结构体重维护了一个servers动态数组,这个数组类型是ngx_http_core_srv_conf_t结构体,而这个结构体与每一个虚拟主机配置是一一对应的,就是说我们每在配置文件中定义一个虚拟主机server{},nginx就会生成一个ngx_http_core_srv_conf_t结构体,并加入到与监听端口对应的动态数组中。总体示意图如下:
再回头看刚开始的疑问,实际上当开始处理一个http请求时,会拿到http头部的host值,这个host就存储了访问的域名,拿到域名也就是server_name值后,也不是遍历servers动态数组,因为如果server{}过多,则会影响效率,http框架是使用了散列表来存放虚拟主机,其中每个元素的key是server_name字符串,value就是ngx_http_core_srv_conf_t结构体的指针。这样就能够快速响应客户端过来的请求。