美文网首页程序员技术干货网络编程
C语言实现简单Web服务器(一)

C语言实现简单Web服务器(一)

作者: dongwenbo | 来源:发表于2017-04-07 15:42 被阅读5028次
    DWBServer

    我们这次要完成的最终结果如上图所示

    前置知识

    • C语言
    • Linux Socket编程
    • 基本的网络知识
    • Unix/Linux 基本知识
    Socket通信模式

    一图胜千言,可以看出Socket编程主要分为这7个步骤,这次我们主要编写服务器端的代码,客户端由浏览器代理。

    Socket在OSI七层模型中的位置
    网络层的IP协议使用IP地址唯一的标识了一台主机,而传输层的协议使用协议名+端口号唯一的标识了系统的一个进程,所以我们才可以利用socket在不同主机的进程间通信

    创建一个socket

    int socket(int domain, int type, int protocol);
    

    这是创建socket的函数原型

    domain
    中文意思为域,可传的值为AF_UNIXAF_LOCALAF_INETAF意为Adress Family。前两个为本机操作,最后一个为IPv4的网络操作,所以为AF_INET

    type
    类型,可传值为SOCK_STREAMSOCK_DGRAMSOCK_PACKET
    SOCK_STREAM 使用 TCP 协议传输数据,SOCK_DGRAM 使用 UDP 协议传输数据,我们要做的是Web服务器,肯定是选择面向连接的可靠的TCP协议,所以这个值传SOCK_STREAM

    protocol:
    所用的协议,有IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTP,传0为自动选择协议,所以我们传0

    返回值
    返回一个socket描述符(socket descriptor),它唯一标识一个socket,这个socket描述字跟文件描述字一样。

    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    

    将socket和地址绑定

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    

    sockfd:
    socket文件描述符,socket()函数的返回值,也就是server_socket

    addr:
    指向地址结构体的指针,这是一个struct sockaddr类型的通用指针,我们实际创建的结构体为

    struct sockaddr_in {
        __uint8_t   sin_len;
        sa_family_t sin_family;
        in_port_t   sin_port;
        struct  in_addr sin_addr;
        char        sin_zero[8];
    };
    

    传递的时候需要做强制类型转换

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);
    

    注意这里的网络字节序和主机字节序的转换,INADDR_ANY表示任何网络地址都可以访问

    memset函数初始化server_addr各个字节为0,防止有未初始化的垃圾值存在

    addrlen:
    结构体的长度,由于在函数内部无法获取到结构体长度(因为传递的是指针,参考数组),所以需要把长度传入

    返回值:
    绑定成功或者失败的消息码,暂时不作处理

    bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
    

    监听

    int listen(int sockfd, int backlog);
    

    sockfd:
    socket文件描述符,socket()函数的返回值,也就是server_socket

    backlog:
    socket待连接队列的最大个数,一般为5

    返回值:
    绑定成功或者失败的消息码,暂时不作处理

    listen(server_socket, 5);
    

    与客户端建立连接

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    

    sockfd:
    socket文件描述符,socket()函数的返回值,也就是server_socket

    addr:
    客户端地址信息的结构体,不关心可以传NULL

    addrlen:
    客户端地址长度,不关心可以传NULL

    返回值:
    socket文件描述符,在与客户端建立连接后,accpet还是会生成一个专门用于和当前客户端通信的socket,而原来那个socket照常负责和其他等待建立连接的客户端建立通信

    int client_socket = accept(server_socket, NULL, NULL);
    

    从浏览器读取请求内容

    ssize_t read(int fd, void *buf, size_t count);
    

    fd:
    文件描述符,从哪个文件读
    buf
    读的内容存到buf中
    count:
    共读多少个字节

    char buf[1024];
    read(client_socket, buf, 1024);
    

    记住,在Linux,一切皆文件,网络接口、甚至鼠标键盘显示器都是文件

    往浏览器写响应内容

    ssize_t write(int fd, const void *buf, size_t count);
    

    fd:
    文件描述符,往哪个文件写
    buf
    内容的首地址
    count:
    共读多少个字节

    char status[] = "HTTP/1.0 200 OK\r\n";
    char header[] = "Server: DWBServer\r\nContent-Type: text/html;charset=utf-8\r\n\r\n";
    char body[] = "<html><head><title>C语言构建小型Web服务器</title></head><body><h2>欢迎</h2><p>Hello,World</p></body></html>";
    
    write(client_socket, status, sizeof(status));
    write(client_socket, header, sizeof(header));
    write(client_socket, body, sizeof(body));
    

    写的格式是按HTTP协议响应报文的格式写的,响应报文的格式为响应行+响应首部+响应体,注意响应首部响应体之间有一个空行

    在浏览器中输入http://localhost:8080/, 就会出现

    DWBServer

    用Charles抓包

    请求 响应

    关闭连接

    int close(int fd);
    
    close(client_socket);
    close(server_socket);
    

    最后把两个socket全部关闭

    完整代码

    #include <sys/socket.h>
    #include <sys/un.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdio.h>
    
    #define PORT 8080                       // 服务器监听端口
    
    int main(){
        
        int server_socket = socket(AF_INET, SOCK_STREAM, 0);
        
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        server_addr.sin_port = htons(PORT);
        
        bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
        
        listen(server_socket, 5);
        
        int client_socket = accept(server_socket, NULL, NULL);
        
        char buf[1024];
        read(client_socket, buf, 1024);
        
        printf("%s",buf);
    
        char status[] = "HTTP/1.0 200 OK\r\n";
        char header[] = "Server: DWBServer\r\nContent-Type: text/html;charset=utf-8\r\n\r\n";
        char body[] = "<html><head><title>C语言构建小型Web服务器</title></head><body><h2>欢迎</h2><p>Hello,World</p></body></html>";
    
        write(client_socket, status, sizeof(status));
        write(client_socket, header, sizeof(header));
        write(client_socket, body, sizeof(body));
    
        close(client_socket);
        close(server_socket);
    
        return 0;
    }
    
    

    结语

    至此,我们已经用socket实现了一个最简单的Web服务器(其实还算不上,只是一个浏览器充当clientsocket小程序),下一篇继续完善这个Web服务器,加入处理Get请求的逻辑,进一步实现HTTP协议

    相关文章

      网友评论

        本文标题:C语言实现简单Web服务器(一)

        本文链接:https://www.haomeiwen.com/subject/ndrhattx.html