本篇文章将着重讲解如何实现,对于 http 与 tcp 的概念本篇将不过多讲解。
一、了解 Socket 及 web 服务工作原理
既然是基于 tcp 实现 web 服务器,很多学习 C 语言的小伙伴可能会很快的想到套接字 socket。socket 是一个较为抽象的通信进程,或者说是主机与主机进行信息交互的一种抽象。socket 可以将数据流送入网络中,也可以接收数据流。
socket 的信息交互与本地文件信息的读取从表面特征上看类似,但其中所存在的编写复杂度是本地 IO 不能比拟的,但却有相似点。在 win 下 socket 的交互交互步骤为:WSAStartup 进行初始化--> socket 创建套接字--> bind 绑定--> listen 监听--> connect 连接--> accept 接收请求--> send/recv 发送或接收数据--> closesocket 关闭 socket--> WSACleanup 最终关闭。
了解完了一个 socket 的基本步骤后我们了解一下一个基本 web 请求的用户常规操作,操作分为:打开浏览器-->输入资源地址 ip 地址-->得到资源。当目标服务器接收到该操作产生掉请求后,我们可以把服务器的响应流程步骤看为:获得 request 请求-->得到请求关键数据-->获取关键数据-->发送关键数据。服务器的这一步流程是在启动socket 进行监听后才能响应。通过监听得知接收到请求,使用 recv 接收请求数据,从而根据该参数得到进行资源获取,最后通过 send 将数据进行返回。
二、创建sokect完成监听
2.1 WSAStartup初始化
首先在c语言头文件中引入依赖 WinSock2.h:
代码语言:javascript复制#include <WinSock2.h>
在第一点中对 socket 的创建步骤已有说明,首先需要完成 socket 的初始化操作,使用函数 WSAStartup,该函数的原型为:
代码语言:javascript复制int WSAStartup(
WORD wVersionRequired,
LPWSADATA lpWSAData
);
该函数的参数 wVersionRequired 表示 WinSock2 的版本号;lpWSAData 参数为指向 WSADATA 的指针,WSADATA 结构用于 WSAStartup 初始化后返回的信息。
wVersionRequired 可以使用 MAKEWORD 生成,在这里可以使用版本 1.1 或版本2.2,1.1 只支持 TCP/IP,版本 2.1 则会有更多的支持,在此我们选择版本 1.1。
首先声明一个 WSADATA 结构体 :
代码语言:javascript复制WSADATA wsaData;
随后传参至初始化函数 WSAStartup 完成初始化:
代码语言:javascript复制WSAStartup(MAKEWORD(1, 1), &wsaData)
WSAStartup 若初始化失败则会返回非0值:
代码语言:javascript复制if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0)
{
exit(1);
}
2.2 创建socket 套接字
初始化完毕后开始创建套接字,套接字创建使用函数,函数原型为:
代码语言:javascript复制SOCKET WSAAPI socket(
int af,
int type,
int protocol
);
在函数原型中,af 表示 IP 地址类型,使用 PF_INET 表示 IPV4,type 表示使用哪种通信类型,例如 SOCK_STREAM 表示 TCP,protocol 表示传输协议,使用 0 会根据前 2 个参数使用默认值。
代码语言:javascript复制int skt = socket(PF_INET, SOCK_STREAM, 0);
创建完 socket 后,若为 -1 表示创建失败,进行判断如下:
代码语言:javascript复制if (skt == -1)
{
return -1;
}
2.3 绑定服务器
创建完 socket 后需要对服务器进行绑定,配置端口信息、IP 地址等。 首先查看 bind 函数需要哪一些参数,函数原型如下:
代码语言:javascript复制int bind(
SOCKET socket,
const sockaddr *addr,
int addrlen
);
参数 socket 表示绑定的 socket,传入 socket 即可;addr 为 sockaddr_in 的结构体变量的指针,在 sockaddr_in 结构体变量中配置一些服务器信息;addrlen 为 addr 的大小值。
通过 bind 函数原型得知了我们所需要的数据,接下来创建一个 sockaddr_in 结构体变量用于配置服务器信息:
代码语言:javascript复制struct sockaddr_in server_addr;
随后配置地址家族为AF_INET对应TCP/IP:
代码语言:javascript复制server_addr.sin_family = AF_INET;
接着配置端口信息:
代码语言:javascript复制server_addr.sin_port = htons(8080);
再指定 ip 地址:
代码语言:javascript复制server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
ip 地址若不确定可以手动输入,最后使用神器 memset 初始化内存,完整代码如下:
代码语言:javascript复制//配置服务器
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8080);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
memset(&(server_addr.sin_zero), '