本文主要讲述如何用C/C++在Linux环境下写一个简单的支持并发的web服务器,并不考虑服务器的健壮性、安全性、性能等一系列因素。
在本文中,该服务器仅支持GET请求。
项目地址:https://github.com/imndszy/webserver
开发环境:ubuntu 16.04,
在编写一个服务器之前,我们需要对socket以及网络协议尤其是http协议有基础的了解,如果不了解,请参阅Beej’s Guide to Network Programming或UNIX网络编程卷一。
当我们打开一个页面时,浏览器会向相应URL所绑定的IP地址发送请求,然后远端的webserver就解析并处理这个请求,比如说如果是GET请求,就会返回一个页面或其他数据。
那么一个webserver的工作流程是怎样的呢?在此我们只讨论一个最简单的服务器且对单个进程而言。
上图中,socket()
用于创建一个套接字,该套接字用于监听某个端口,在bind()
中套接字与端口绑定,当然还有一些其他参数,随后通过listen()
进行监听,这时候就进入服务器程序的主循环,当有连接建立后,accpet()
被调用并返回一个新的套接字用于处理连接,这时派生一个子进程进行处理,子进程中recv()
从缓冲区读取数据交由相关函数处理,处理完毕后的结果通过send()
发送出去,随后关闭用于处理的套接字,子进程退出。
至此,一个简易webserver的雏形就显现出来了。
首先是如何创建及设置套接字的代码:
int sockfd;
char *port;
struct addrinfo hints, *servinfo, *p;
int rv;
//从命令行参数读取要绑定到的端口号
if(argc != 2){
fprintf(stderr,"usage:%s <port>
",argv[0]);
exit(1);
}
port = argv[1];
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE;
if((rv = getaddrinfo(NULL, port, &hints, &servinfo)) != 0){
fprintf(stderr, "getaddrinfo:%s
", gai_strerror(rv));
return 1;
}
for(p = servinfo;p != NULL;p = p->ai_next){
if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1){
perror("server: socket");
continue;
}
if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
perror("setsockopt");
exit(1);
}
if(bind(sockfd, p->ai_addr, p->ai_addrlen) == -1){
close(sockfd);
perror("server:bind");
continue;
}
break;
}
接下来是主循环部分:
while(1){
sin_size = sizeof(other_addr);
connfd = accept(sockfd, (struct sockaddr *)&other_addr, &sin_size);
if(connfd == -1){
perror("accept");
continue;
}
inet_ntop(other_addr.ss_family, get_in_addr((struct sockaddr*)&other_addr), s, sizeof(s));
printf("server:got connection from %s
",s);
if(!fork()){
close(sockfd); //fork以后子进程中也会有一个sockfd
if(recv(connfd, buf, MAXBUF, 0) == -1) {
perror("receive");
close(connfd);
exit(1);
}
sscanf(buf,"%s %s %s",method,uri,version);
if(!strcasecmp(method, "GET")) //如果是GET请求
process_http_get(connfd,uri,filename);
else
client_error(connfd,method,"501","Not Implemented","Webserver does not implement this method");
close(connfd);
exit(0);
}
close(connfd);
}