逐行解释webserver源码(window版)

简介: 逐行解释webserver源码(window版)

引言

由于自己对linux掌握的不多,但又对服务器和客户端有那么点兴趣,而目前网上的webserver基本都是linux下的,所以在想是否可以用window来做,因为我的本意只是想简单了解下socket等而已,不需要那么复杂的功能,这个代码只需要一个cpp就可以简单实现

学习思路很简单,先把我的代码运行下,先看下实现的效果再来看源代码

后面有时间的话,我会更新更多关于源码的解析

基本思路

对webserver完全没有概念的人可以先简单了解下流程:初始化Winsock、创建服务器套接字、绑定套接字到地址、监听地址、处理客户端请求、构造服务器响应、发送服务器响应,关闭客户端套接字和清理资源,最后关闭服务器套接字,清理Winsock资源并退出程序

源代码

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#pragma comment(lib, "ws2_32.lib")
#include <iostream>
#include <WinSock2.h>
#include <string>

int main()
{
   
   
    std::cout << "create a server\n";

    SOCKET wsocket;
    SOCKET new_wsocket;
    WSADATA wsaData;
    struct sockaddr_in server;
    int server_len;
    int BUFFER_SIZE = 30720;

    //初始化Winsock
    if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
    {
   
   
        std::cout << "not initialize";
    }

    //创建服务器套接字
    wsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (wsocket == INVALID_SOCKET)
    {
   
   
        std::cout << "not create socket";
    }

    //绑定套接字到地址
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr("127.0.0.1");
    server.sin_port = htons(8080);
    server_len = sizeof(server);

    if (bind(wsocket, (SOCKADDR*)&server, server_len) != 0)
    {
   
   
        std::cout << "not bind socket";
    }

    //监听地址
    if (listen(wsocket, 20) != 0)
    {
   
   
        std::cout << "not start listening \n";
    }

    std::cout << "listening on 127.0.0.1:8080 \n";
    //处理客户端请求
    int bytes = 0;
    while (true)
    {
   
   
        new_wsocket = accept(wsocket, (SOCKADDR*)&server, &server_len);
        if (new_wsocket == INVALID_SOCKET) {
   
   
            std::cout << "not accept \n";
        }

        //读取客户端请求
        char buff[30720] = {
   
    0 };
        bytes = recv(new_wsocket, buff, BUFFER_SIZE, 0);
        if (bytes < 0)
        {
   
   
            std::cout << "not read client request";
        }

        //构造服务器响应
        std::string serverMessage = "HTTP/1.1 200 OK\nContent-Type: text/html\nContent-Length: ";
        std::string response = "<html><h1>Hello world</h1><html>";
        serverMessage.append(std::to_string(response.size()));
        serverMessage.append("\n\n");
        serverMessage.append(response);

        //发送服务器响应
        int bytesSent = 0;
        int totalBytesSent = 0;
        while (totalBytesSent < serverMessage.size())
        {
   
   
            bytesSent = send(new_wsocket, serverMessage.c_str(), serverMessage.size(), 0);
            if (bytesSent < 0)
            {
   
   
                std::cout << "not send response";
            }
            totalBytesSent += bytesSent;
        }
        std::cout << "Sent response to client\n";
        //关闭客户端套接字和清理资源
        closesocket(new_wsocket);
    }
    //关闭服务器套接字、清理 Winsock 资源并退出程序
    closesocket(wsocket);
    WSACleanup();

    return 0;
}

效果

将这份代码运行后,在浏览器地址框里输入127.0.0.1:8080就可以看到hello world,每次刷新这个页面,控制台都会显示新的东西

运行代码后是这样

在这里插入图片描述

在浏览器输入地址后是这样的

在这里插入图片描述

\[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XyJ9TDxk-1684241584532)(webserver.assets/image-20230516205054498.png)\]

我们再刷新一次网页

在这里插入图片描述

逐行解析

这段代码是用 C++ 写的,它使用了 Winsock 库来创建一个简单的服务器,该服务器监听 8080 端口并以 “Hello World” 消息响应传入的请求。下面是对每一行代码的解释:

  • define _WINSOCK_DEPRECATED_NO_WARNINGS - 此行抑制有关不推荐使用的 Winsock 函数的警告。

  • pragma comment(lib, "ws2_32.lib") - 这行代码告诉编译器链接到 ws2_32.lib 库,该库包含了 Winsock 函数的实现。

  • include - 这行代码包含了 iostream 库,它提供了用于输入和输出的流类。

  • include - 这行代码包含了 WinSock2.h 头文件,它包含了 Winsock 库的定义和函数原型。

  • include - 这行代码包含了 string 库,它提供了用于处理字符串的类和函数。

  • int main(){ - 这是程序的主函数,程序从这里开始执行。
  • std::cout << "create a server\n"; - 这行代码使用 cout 对象输出字符串 “create a server” 和一个换行符。
  • SOCKET wsocket; - 这行代码定义了一个 SOCKET 类型的变量 wsocket,它将用于存储服务器套接字的句柄。
  • SOCKET new_wsocket; - 这行代码定义了一个 SOCKET 类型的变量 new_wsocket,它将用于存储客户端套接字的句柄。
  • WSADATA wsaData; - 这行代码定义了一个 WSADATA 类型的变量 wsaData,它将用于存储有关 Winsock 实现的信息。
  • struct sockaddr_in server; - 这行代码定义了一个 sockaddr_in 结构类型的变量 server,它将用于存储服务器套接字的地址信息。
  • int server_len; - 这行代码定义了一个 int 类型的变量 server_len,它将用于存储服务器套接字地址结构的大小。
  • int BUFFER_SIZE = 30720; - 这行代码定义并初始化了一个 int 类型的常量 BUFFER_SIZE,它表示缓冲区的大小。
    接下来是初始化 Winsock 的部分:
  • if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { - 这行代码调用 WSAStartup 函数来初始化 Winsock 库。MAKEWORD(2, 2) 参数指定请求的 Winsock 版本为 2.2。如果 WSAStartup 函数返回非零值,则表示初始化失败。
  • std::cout << "not initialize"; - 如果初始化失败,则使用 cout 对象输出字符串 “not initialize”。
  • } - 这是 if 语句块的结束。
    接下来是创建服务器套接字的部分:
  • wsocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - 这行代码调用 socket 函数来创建一个服务器套接字。AF_INET 参数指定使用 IPv4 地址族。SOCK_STREAM 参数指定使用面向连接的套接字类型。IPPROTO_TCP 参数指定使用 TCP 协议。socket 函数返回创建的套接字句柄。
  • if (wsocket == INVALID_SOCKET) { - 如果 socket 函数返回 INVALID_SOCKET,则表示创建套接字失败。
  • std::cout << "not create socket"; - 如果创建套接字失败,则使用 cout 对象输出字符串 “not create socket”。
  • } - 这是 if 语句块的结束。
    接下来是绑定套接字到地址的部分:
  • server.sin_family = AF_INET; - 这行代码设置服务器套接字地址结构中的地址族为 AF_INET,即 IPv4 地址族。
  • server.sin_addr.s_addr = inet_addr("127.0.0.1"); - 这行代码调用 inet_addr 函数将 IP 地址字符串 “127.0.0.1” 转换为网络字节序,并将结果存储在服务器套接字地址结构中的 sin_addr 成员中。
  • server.sin_port = htons(8080);- 这行代码调用 htons 函数将端口号 8080 转换为网络字节序,并将结果存储在服务器套接字地址结构中的 sin_port 成员中。
  • server_len = sizeof(server); - 这行代码计算服务器套接字地址结构的大小,并将结果存储在变量 server_len 中。
  • if (bind(wsocket, (SOCKADDR*)&server, server_len) != 0) { - 这行代码调用 bind 函数将服务器套接字绑定到指定的地址。wsocket 参数是服务器套接字的句柄。server 参数是指向服务器套接字地址结构的指针。server_len 参数是服务器套接字地址结构的大小。如果 bind 函数返回非零值,则表示绑定失败。
  • std::cout << "not bind socket"; - 如果绑定失败,则使用 cout 对象输出字符串 “not bind socket”。
  • } - 这是 if 语句块的结束。
    接下来是监听地址的部分:
  • if (listen(wsocket, 20) != 0) { - 这行代码调用 listen 函数使服务器套接字开始监听指定的地址。wsocket 参数是服务器套接字的句柄。20 参数是等待连接队列的最大长度。如果 listen 函数返回非零值,则表示监听失败。
  • std::cout << "not start listening \n"; - 如果监听失败,则使用 cout 对象输出字符串 “not start listening” 和一个换行符。
  • } - 这是 if 语句块的结束。
  • std::cout << "listening on 127.0.0.1:8080 \n"; - 这行代码使用 cout 对象输出字符串 “listening on 127.0.0.1:8080” 和一个换行符,表示服务器已经开始监听。
    接下来是处理客户端请求的部分:
  • int bytes = 0; - 这行代码定义了一个 int 类型的变量 bytes,它将用于存储读取或发送数据的字节数。
  • while (true) { - 这是一个无限循环,用于不断地处理客户端请求。
  • new_wsocket = accept(wsocket, (SOCKADDR*)&server, &server_len); - 这行代码调用 accept 函数等待并接受客户端连接请求。wsocket 参数是服务器套接字的句柄。server 参数是指向服务器套接字地址结构的指针,它将用于存储客户端套接字的地址信息。server_len 参数是指向存储服务器套接字地址结构大小的变量的指针,它将被更新为客户端套接字地址结构的实际大小。accept 函数返回客户端套接字的句柄。
  • if (new_wsocket == INVALID_SOCKET) { - 如果 accept 函数返回 INVALID_SOCKET,则表示接受连接请求失败。
  • std::cout << "not accept \n"; - 如果接受连接请求失败,则使用 cout 对象输出字符串 “not accept” 和一个换行符。
  • } - 这是 if 语句块的结束。
    接下来是读取客户端请求的部分:
  • char buff[30720] = { 0 }; - 这行代码定义并初始化了一个 char 类型的数组 buff,它将用作缓冲区来存储从客户端读取到的数据。
  • bytes = recv(new_wsocket, buff, BUFFER_SIZE, 0); - 这行代码调用 recv 函数从客户端套接字读取数据。new_wsocket 参数是客户端套接字的句柄。buff 参数是指向缓冲区的指针。BUFFER_SIZE 参数是缓冲区的大小。0 参数表示不使用任何标志。recv 函数返回实际读取到的字节数。
  • if (bytes < 0) { - 如果 recv 函数返回值小于 0,则表示读取数据失败。
  • std::cout << "not read client request"; - 如果读取数据失败,则使用 cout 对象输出字符串 “not read client request”。
  • } - 这是 if 语句块的结束。
    接下来是构造服务器响应的部分:
  • std::string serverMessage = "HTTP/1.1 200 OK\nContent-Type: text/html\nContent-Length: "; - 这行代码定义并初始化了一个 string 类型的变量 serverMessage,它将用于存储服务器响应消息。初始值为 HTTP 响应头,包括 HTTP 版本、状态码、Content-Type 和 Content-Length 字段。
  • std::string response = "\
\

Hello world\

\"; - 这行代码定义并初始化了一个 string 类型的变量 response,它表示服务器响应的正文内容,即 “Hello World” 消息。
  • serverMessage.append(std::to_string(response.size())); - 这行代码调用 to_string 函数将服务器响应正文的大小转换为字符串,并将结果追加到服务器响应消息中,作为 Content-Length 字段的值。
  • serverMessage.append("\n\n"); - 这行代码在服务器响应消息中追加两个换行符,表示 HTTP 响应头和正文之间的分隔。
  • serverMessage.append(response); - 这行代码将服务器响应正文追加到服务器响应消息中。
    接下来是发送服务器响应的部分:
  • int bytesSent = 0; - 这行代码定义了一个 int 类型的变量 bytesSent,它将用于存储每次发送数据的字节数。
  • int totalBytesSent = 0; - 这行代码定义了一个 int 类型的变量 totalBytesSent,它将用于存储已经发送数据的总字节数。
  • while (totalBytesSent < serverMessage.size()) { - 这是一个 while 循环,用于循环发送服务器响应消息,直到所有数据都发送完毕。
  • bytesSent = send(new_wsocket, serverMessage.c_str(), serverMessage.size(), 0); - 这行代码调用 send 函数向客户端套接字发送数据。new_wsocket 参数是客户端套接字的句柄。serverMessage.c_str() 参数是指向服务器响应消息字符串的指针。serverMessage.size() 参数是服务器响应消息字符串的大小。0 参数表示不使用任何标志。send 函数返回实际发送的字节数。
  • if (bytesSent < 0) { - 如果 send 函数返回值小于 0,则表示发送数据失败。
  • std::cout << "not send response"; - 如果发送数据失败,则使用 cout 对象输出字符串 “not send response”。
  • } - 这是 if 语句块的结束。
  • totalBytesSent += bytesSent; - 这行代码累加已经发送数据的总字节数。
  • } - 这是 while 循环块的结束。
    接下来是关闭客户端套接字和清理资源的部分:
  • std::cout << "Sent response to client\n"; - 这行代码使用 cout 对象输出字符串 “Sent response to client” 和一个换行符,表示服务器已经向客户端发送了响应
  • closesocket(new_wsocket); - 这行代码调用 closesocket 函数关闭客户端套接字。
  • } - 这是 while 循环块的结束。
    最后是关闭服务器套接字、清理 Winsock 资源并退出程序的部分:
  • closesocket(wsocket); - 这行代码调用 closesocket 函数关闭服务器套接字。
  • WSACleanup(); - 这行代码调用 WSACleanup 函数清理 Winsock 库使用的资源。
  • return 0; - 这行代码表示程序正常退出。
  • 相关文章
    关于 QtCreator中写Qt程序遇到printf不输出问题 的解决方法
    关于 QtCreator中写Qt程序遇到printf不输出问题 的解决方法
    |
    PHP
    php开发实战分析(4):php调用封装函数包含文件路径自适应不同目录的解决方案($_SERVER[‘DOCUMENT_ROOT‘]与__DIR__魔术常量)
    php开发实战分析(4):php调用封装函数包含文件路径自适应不同目录的解决方案($_SERVER[‘DOCUMENT_ROOT‘]与__DIR__魔术常量)
    175 0
    |
    6月前
    |
    BI PHP
    php代码优化---本人的例子
    php代码优化---本人的例子
    55 0
    |
    缓存 PHP
    |
    前端开发 JavaScript 数据可视化
    javascript逐行显示数据及php实时输出前端内容后台保持继续运行的解决方案(setTimeout定时器、flush和ob_flush函数、安装进度展示)
    javascript逐行显示数据及php实时输出前端内容后台保持继续运行的解决方案(setTimeout定时器、flush和ob_flush函数、安装进度展示)
    197 0
    |
    算法 PHP 数据安全/隐私保护
    |
    JavaScript 前端开发
    JavaScript基础插曲---apply,call和URL编码等方法
    JavaScript基础插曲---apply,call和URL编码等方法
    164 0
    JavaScript基础插曲---apply,call和URL编码等方法
    |
    存储 Python
    Python 自动化-pywinauto库print_control_identifiers()方法打印内容显示不全解决办法,cmd展示更多内容设置方法
    Python 自动化-pywinauto库print_control_identifiers()方法打印内容显示不全解决办法,cmd展示更多内容设置方法
    891 0
    Python 自动化-pywinauto库print_control_identifiers()方法打印内容显示不全解决办法,cmd展示更多内容设置方法
    |
    XML 数据格式
    【Tip】如何让引用的dll随附的xml注释文档、pdb调试库等文件不出现在项目输出目录中
    原文:【Tip】如何让引用的dll随附的xml注释文档、pdb调试库等文件不出现在项目输出目录中 项目输出目录(bin/debug|release)中经常是这个样子: main.exemain.pdb a.dll a.xml b.dll b.pdb b.xml ... 其中xml是同名dll的注释文档,pdb是调试库。
    948 0