如何写一个简单的webserver(一):最简实现

本文主要讲述如何用C/C++在Linux环境下写一个简单的支持并发的web服务器,并不考虑服务器的健壮性、安全性、性能等一系列因素。 
在本文中,该服务器仅支持GET请求。 

项目地址:https://github.com/imndszy/webserver 
开发环境:ubuntu 16.04,

在编写一个服务器之前,我们需要对socket以及网络协议尤其是http协议有基础的了解,如果不了解,请参阅Beej’s Guide to Network ProgrammingUNIX网络编程卷一

当我们打开一个页面时,浏览器会向相应URL所绑定的IP地址发送请求,然后远端的webserver就解析并处理这个请求,比如说如果是GET请求,就会返回一个页面或其他数据。 
那么一个webserver的工作流程是怎样的呢?在此我们只讨论一个最简单的服务器且对单个进程而言。 
如何写一个简单的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);
}