能开始学习nginx的你,肯定也撸了不少代码了,相信你学习代码都是从helloWorld开始的,那么,今天我们就用nginx开发一个helloWorld,我们将要实现的功能就是当浏览器来访问你的服务器时,你的终端打印一个helloWorld。
先别急着开始撸代码,先聊一聊自己为什么想写这个专栏,其实本人也是个服务器开发菜鸟,感觉年纪稍微大了一点,反应和记忆力就下降的很厉害了,必须得写点什么帮助自己记忆一下。这个专栏将会帮助广大的nginx菜鸟们一点点深入理解nginx,我的每篇文章都不会很长,但都会有一个重点,如果你细心跟着我一直学习下去,你至少将学会如何用nginx搭建自己的web服务器,用nginx搭建自己的反向代理服务器,然后如果你足够认真,对于nginx的各种高级特性,诸如内存对齐带来的高效率,slab算法,各种池(内存池,连接池)也会有一定的了解。在讲解nginx的同时,我也会同时结合nginx中的很多关于网络的特性,讲解说明一些现有的前沿的知识。如http2.0,tls1.3等,如果你发现我的文章有任何纰漏之处,欢迎大家指正。
在正式开始上手nginx之前,我们先回忆下一个echo服务器是怎么做的:
#include
#include
#include
#include
#include
#define EHCO_PORT 8080
#define MAX_CLIENT_NUM 10
int main()
{
int socketfd;
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if(socketfd == -1)
{
printf("errno=%d ", errno);
exit(1);
}
else
{
printf("socket create successfully ");
}
struct sockaddr_in sa;
bzero(&sa, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(EHCO_PORT);
sa.sin_addr.s_addr = htons(INADDR_ANY);
bzero(&(sa.sin_zero), 8);
if(bind(socketfd, (struct sockaddr *)&sa, sizeof(sa))!= 0)
{
printf("bind failed ");
printf("errno=%d ", errno);
exit(1);
}
else
{
printf("bind successfully ");
}
//listen
if(listen(socketfd ,MAX_CLIENT_NUM) != 0)
{
printf("listen error ");
exit(1);
}
else
{
printf("listen successfully ");
}
int clientfd;
struct sockaddr_in clientAdd;
char buff[101];
socklen_t len = sizeof(clientAdd);
int closing =0;
while( closing == 0 && (clientfd = accept(socketfd, (struct sockaddr *)&clientAdd, &len)) >0 )
{
int n;
while((n = recv(clientfd,buff, 100,0 )) > 0)
{
printf("number of receive bytes = %d ", n);
write(STDOUT_FILENO, buff, n);
send(clientfd, buff, n, 0);
buff[n] = '';
if(strcmp(buff, "quit ") == 0)
{
break;
}
else if(strcmp(buff, "close ") == 0)
{
//server closing
closing = 1;
printf("server is closing ");
break;
}
}
close(clientfd);
}
close(socketfd);
return 0;
}
任何服务器,都逃不过那几个基本的函数,socket().bind(),listen(),connect(),accept(),read(),write(),send(),recv()无论多么高级的服务器,本质上都是利用操作系统给的IO复用和各种设计模式对这几个接口做了封装。在学习服务器开发的时候个人觉得一定要把握这个本质(毕竟再复杂的逻辑也就是收到一段message,然后处理message,再发送相应的response回去嘛)。
回忆好一个echo服务器是怎么做的之后,再复习一下一个不得不说的基本知识:事件驱动机制。什么是事件驱动机制呢?字面理解就是,一个事件到来,才会触发下一个事件,一个事件触发一个事件,层层推进。文字说起来很抽象,其实本质上就是select,poll,epoll这几个IO多路复用函数结合回调函数实现的架构。来看下一个简单的epoll服务器的逻辑是怎么样的:
//这里摘取了部分重要的代码来说明一个简单的epoll服务器的框架,如果想看完整的epoll服务器实现可以去我的github上看https://github.com/zk3326312/EpollServer
if((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("socket error");
return -1;
}
memset(&serveraddr,0,sizeof(serveraddr));
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(default_port);
serveraddr.sin_family = AF_INET;
if( bind(listenfd,(sockaddr*)&serveraddr,sizeof(sockaddr)) == -1)
{
cout<<"bind error"<add(listenfd,EPOLLIN);
while(true)
{
int nfds = sp_Epoll->wait(maxfdNUM,-1);
for(int i = 0;i < nfds;i++)
{
if(sp_Epoll->_events[i].data.fd == listenfd)
{
if((confd = accept(listenfd,(sockaddr*)&clientaddr,&clilen)) == -1)
{
cout<<"accept error"<add(confd,EPOLLIN);
}
else if((sp_Epoll->_events[i].events & EPOLLIN) == EPOLLIN)
{
int tempfd = sp_Epoll->_events[i].data.fd;
if(tempfd < 0)
{
continue;
}
char buffer[buffer_len];
int n = 0;
if((n = recv(tempfd,buffer,buffer_len,0)) > 0)
{
cout<< "receive message is"<< buffer<_events[i].data.fd = -1;
continue;
}
else if(errno == EINTR)
{
cout << "connect problem"<_events[i].data.fd = -1;
cout << "the connection is terminated by client"<mod(confd,EPOLLOUT);
}
else if((sp_Epoll->_events[i].events & EPOLLOUT) == EPOLLOUT)
{
int tempfd = sp_Epoll->_events[i].data.fd;
send(tempfd,"success recv your message",25,0);
//sp_Epoll->del(tempfd);
sp_Epoll->mod(tempfd,EPOLLIN);
}
}
}
return 0;
}
初看nginx的代码,又多又杂,到处都是回调和宏,很容易让人无从下手(看不懂真的不怪你),但是当你牢牢记住事件驱动这个本质时,你就会清晰很多了,整个过程无非就是连接到了,注册读写事件到epoll中,然后等fd就绪了再处理嘛,好好消化一下,下一篇文章我们就要正式开始编写nginx的helloWorld功能了。