本文构建了一个十分简易的HTTPServer,实现http解析与应答,能够进行简单的网页部署
1、Windows下的套接字编程
1、需包含的头文件
Windows 下使用 Winsock 库进行套接字编程,需要包含如下文件
#include <windows.h>
#include <winSock2.h>
2、创建 Winsock 环境
WORD ver = MAKEWORD(2, 2); |
MAKEWORD(2, 2)
创建一个版本号,表示你希望使用 Winsock 库的版本为 2.2。这里的2
是主版本号,2
是次版本号,构成了一个WORD
类型的版本号。WSADATA dat;
声明了一个WSADATA
结构体类型的变量dat
,用于接收WSAStartup
函数的初始化信息。WSAStartup(ver, &dat);
调用了WSAStartup
函数,该函数用于初始化 Winsock 库,指定使用的版本号,并将初始化信息保存在dat
变量中。
具体来说,WSAStartup
函数用于启动 Winsock 库的使用,在你的应用程序中调用该函数后,就可以使用 Winsock 提供的套接字编程功能了。需要注意的是,在使用完 Winsock 库后,应该调用 WSACleanup
函数来清理资源。
3、创建套接字
SOCKET socket( |
参数说明:
af
(Address Family):指定地址族,常用的有AF_INET
(IPv4 地址族)和AF_INET6
(IPv6 地址族)等。对于 TCP/IP,通常使用AF_INET
。type
:指定套接字类型,常用的有SOCK_STREAM
(流式套接字,对应于 TCP)和SOCK_DGRAM
(数据报套接字,对应于 UDP)等。对于 TCP 通信,通常使用SOCK_STREAM
。protocol
:指定协议,对于 TCP/IP,通常使用IPPROTO_TCP
表示 TCP 协议。
4、绑定地址和端口
sockaddr_in _sin = {}; |
sockaddr_in _sin = {};
:创建一个 sockaddr_in
结构体类型的变量 _sin
,并初始化为全零,这样可以避免结构体中的未初始化字段可能引发的问题。
_sin.sin_family = AF_INET;
:设置套接字地址族为 AF_INET
,表示使用 IPv4 地址族。
_sin.sin_port = htons(4567);
:将端口号设置为网络字节序(Big Endian),使用 htons
函数将端口号从主机字节序转换为网络字节序。
_sin.sin_addr.s_addr = INADDR_ANY;
:设置 IP 地址为 INADDR_ANY
,表示服务器将接受来自任意网络接口的连接请求。
bind(_sock, (sockaddr*)&_sin, sizeof(_sin))
:调用 bind
函数将套接字 _sock
绑定到指定的 IP 地址和端口上。
_sock
是之前使用socket
函数创建的套接字描述符。(sockaddr*)&_sin
强制类型转换为sockaddr*
类型,因为bind
函数的参数类型为sockaddr*
。sizeof(_sin)
表示传递给bind
函数的地址结构体的大小。
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin))) { ... }
:判断 bind
函数的返回值是否等于 SOCKET_ERROR
,如果等于,则表示绑定失败,输出错误信息;否则表示绑定成功,输出成功信息。
5、监听连接请求
if (SOCKET_ERROR == listen(_sock, 5)) { |
listen(_sock, 5)
:调用listen
函数开始监听套接字连接请求。_sock
是之前使用socket
函数创建的套接字描述符。5
表示指定套接字连接队列的最大长度,即同时可以处理的待处理连接请求的数量。
if (SOCKET_ERROR == listen(_sock, 5)) { ... }
:判断listen
函数的返回值是否等于SOCKET_ERROR
,如果等于,则表示监听失败,输出错误信息;否则表示监听成功,输出成功信息。
listen
函数的作用是告诉操作系统开始监听套接字上的连接请求,并指定可以同时处理的待处理连接请求的最大数量。一旦有新的连接请求到达,操作系统会将其加入到套接字的待处理连接队列中,然后等待程序调用 accept
函数来接受连接。
需要注意的是,listen
函数仅用于监听套接字,不会阻塞等待连接请求,因此在调用 listen
函数后,程序可以继续执行其他操作,例如调用 accept
函数来接受连接。
6、select多路复用
fd_set fdRead; // 描述符集合 |
这段代码是在使用 select
函数前准备好描述符集合 fdRead
、fdWrite
、fdExp
的过程.
-
fd_set
结构体:fdRead
、fdWrite
、fdExp
是用来存放描述符的集合,分别用于读、写和异常事件的监听。FD_ZERO(&fdRead)
、FD_ZERO(&fdWrite)
、FD_ZERO(&fdExp)
将这些集合清空,使其不包含任何描述符。
-
FD_SET
函数:FD_SET(_sock, &fdRead)
、FD_SET(_sock, &fdWrite)
、FD_SET(_sock, &fdExp)
将_sock
描述符添加到对应的集合中,表示对该描述符进行监听。_sock
是服务器的监听套接字,所以需要监听读、写、异常事件。
-
添加客户端套接字:
- 使用
for
循环遍历g_clients
客户端套接字列表,将每个客户端套接字添加到fdRead
集合中,表示对客户端的读事件进行监听。 - 同时,在循环中找出
g_clients
中最大的套接字描述符,并保存在maxSock
变量中,这个最大描述符值会在后面调用select
函数时用到。
timeval t = { 0, 0 };
int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t);
if (ret < 0) {
printf("select任务结束。\n");
break;
}调用
select
函数来等待套接字事件的发生。timeval t = { 0, 0 };
:- 创建了一个
timeval
结构体变量t
,并将其初始化为0秒。 timeval
结构体用于设置select
函数的超时时间,由于这里将其设置为0秒,表示select
函数在检查完描述符状态后立即返回,不会阻塞等待事件发生。
- 创建了一个
int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t);
:- 调用
select
函数来等待套接字事件的发生。 maxSock + 1
表示最大描述符值加一,即描述符集合中需要监视的最大描述符值。&fdRead
、&fdWrite
、&fdExp
分别表示读、写、异常事件的描述符集合。&t
表示超时时间,这里设置为0秒表示立即返回。
- 调用
if (ret < 0) { ... }
:- 判断
select
函数的返回值是否小于0,如果小于0,则表示select
函数出错或任务结束,输出相应的提示信息并跳出循环。 ret
的值表示有事件发生的描述符数量,如果没有事件发生,则ret
的值为0。
- 判断
- 使用
7、接受连接
if (FD_ISSET(_sock, &fdRead)) { |
检查监听套接字 _sock
是否在 fdRead
集合中,并且如果有数据可读,则调用 accept
函数接受客户端连接。
FD_ISSET(_sock, &fdRead)
:FD_ISSET
函数用于检查描述符是否在指定的集合中,并且表示该描述符是否有可读数据。- 这里用于检查监听套接字
_sock
是否在fdRead
集合中,如果是,表示有新的客户端连接请求到达。
FD_CLR(_sock, &fdRead)
:FD_CLR
函数用于从集合中清除指定的描述符。- 这里将监听套接字
_sock
从fdRead
集合中清除,避免重复处理同一个连接请求。
accept
函数:accept
函数用于接受客户端的连接请求,返回一个新的套接字用于与客户端通信。- 在 Windows 平台下,
accept
函数的第二个参数是sockaddr*
类型的地址结构体指针,用于存储客户端的地址信息。 accept
函数成功返回新的套接字_cSock
,用于后续与客户端通信;如果返回INVALID_SOCKET
,表示接受连接失败。
- 将新连接的套接字添加到
g_clients
列表中,并输出客户端的 IP 地址和端口信息。
8、接收和发送数据
recv
和 send
是用于在套接字编程中进行数据收发的两个常用函数,下面分别对它们的用法进行解释:
recv
函数
int recv(SOCKET s, char* buf, int len, int flags); |
s
: 指定要接收数据的套接字。buf
: 指向接收数据的缓冲区的指针。len
: 缓冲区的长度,即可以接收的最大字节数。flags
: 用于指定接收数据的方式,常用的取值有:0
: 默认值,表示以默认方式接收数据。MSG_PEEK
: 接收数据但不从输入队列中移除数据。MSG_WAITALL
: 等待直到接收到指定长度的数据或出错。- 等等,还有其他选项可以用于控制接收数据的行为。
recv
函数的返回值表示实际接收到的数据的字节数,如果返回值为0表示对方已关闭连接,返回值为 SOCKET_ERROR
表示接收出错。
send
函数
int send(SOCKET s, const char* buf, int len, int flags); |
s
: 指定要发送数据的套接字。buf
: 指向要发送数据的缓冲区的指针。len
: 要发送的数据的长度,即缓冲区中的字节数。flags
: 用于指定发送数据的方式,常用的取值有:0
: 默认值,表示以默认方式发送数据。MSG_NOSIGNAL
: 在发送数据时不产生SIGPIPE
信号,一般在 Linux 环境下使用。
send
函数的返回值表示实际发送的数据的字节数,如果返回值为 SOCKET_ERROR
表示发送出错。
示例用法
char recvBuf[1024]; // 接收数据的缓冲区 |
9、关闭套接字和清理
for (int n = (int)g_clients.size() - 1; n >= 0; n--) { |
关闭套接字和清理 Windows Socket 环境。
for
循环关闭客户端套接字:g_clients
是一个存储客户端套接字的容器。g_clients.size() - 1
表示最后一个客户端套接字的索引,即g_clients
中的最后一个元素。- 从最后一个客户端套接字开始,逐个关闭套接字,直到第一个客户端套接字。
closesocket
函数用于关闭套接字,避免资源泄漏和占用。
- 关闭服务器监听套接字:
closesocket(_sock)
用于关闭服务器的监听套接字,即关闭了服务器的网络监听服务。
- 清理 Windows Socket 环境:
WSACleanup()
是在 Windows 环境下清理 Windows Socket 环境的函数。- 在使用 Windows Socket API 时,需要在程序结束时调用
WSACleanup()
来释放相关资源和清理环境。
2、将vue项目部署在我们的HTTPServer
1、创建 Vue 项目
使用 create-vue
来创建一个新的 Vue 项目并使用 Vite 进行构建。
npm create vue@latest |
按照提示完成项目设置。例如:
✔ Project name: … my-vue-app |
进入项目目录并安装依赖:
cd my-vue-app |
2、添加示例组件和路由
创建一个新组件
在 src/components
目录下创建一个名为 HelloWorld.vue
的文件:
<template> |
配置路由
在 src/router/index.js
中添加路由配置:
import { createRouter, createWebHistory } from 'vue-router'; |
更新主页视图
在 src/views/Home.vue
中添加一个链接导航到 HelloWorld
组件:
<template> |
构建和部署
构建生产版本:
npm run build |
这会在 dist
目录下生成静态文件,将 dist
目录的内容复制到 HTTP 服务器可以访问的位置。
3、程序
|
- 本文作者: hzr
- 本文链接: https://HZR0709.github.io/2024/06/11/HTTP服务器/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!