逐行解释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,每次刷新这个页面,控制台都会显示新的东西
运行代码后是这样
在浏览器输入地址后是这样的
我们再刷新一次网页
逐行解析
这段代码是用 C++ 写的,它使用了 Winsock 库来创建一个简单的服务器,该服务器监听 8080 端口并以 “Hello World” 消息响应传入的请求。下面是对每一行代码的解释:
- #define _WINSOCK_DEPRECATED_NO_WARNINGS - 此行抑制有关不推荐使用的 Winsock 函数的警告。
- #pragma comment(lib, “ws2_32.lib”) - 这行代码告诉编译器链接到 ws2_32.lib 库,该库包含了 Winsock 函数的实现。
- #include <iostream> - 这行代码包含了 iostream 库,它提供了用于输入和输出的流类。
- #include <WinSock2.h> - 这行代码包含了 WinSock2.h 头文件,它包含了 Winsock 库的定义和函数原型。
- #include <string> - 这行代码包含了 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 = “<html><h1>Hello world</h1><html>”; - 这行代码定义并初始化了一个 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; - 这行代码表示程序正常退出。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)