要使用网络编程,要包含系统提供的头文件winsock2.h,同时还需要加载连接库文件(#pragma comment(lib,“ws2_32.lib”)),实现通信程序的管理。实现简单的http服务器程序,需要有以下7个步骤:
1.初始化网络库
其代码如下:
void initSocket() {
WORD version = MAKEWORD(2, 2);
WSADATA wsadata;
if (0 != WSAStartup(version, &wsadata)) {// 参数1.指定socket版本 参数2.是一个传出参数
error_die("WSAStartup");
}
}
在c++中,BYTE、WORD和DWORD本质上都是一种无符号整型,它们在WINDEF.H中被定义,定义如下:
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
在socket编程中,声明调用不同的Winsock版本MAKEWORD(2, 2)就是调用2.2版本,MAKEWORD(1, 1)就是调用1.1版本,不同的版本是有区别的,例如1.1版只支持TCP/IP协议,而2.2版本可以支持多协议。2.0版本有良好的向后兼容性。
WSAStartup主要就是进行相应的socket库绑定。使用socket的程序在使用之前必须调用WSAStartup函数。当一个应用程序调用WSAStartup函数时,操作系统根据请求socket版本来搜索相应的socket库,然后绑定找到的socket库到该应用程序中。以后应用程序就可以调用所请求的socket库中其他的socket函数。该函数执行成功后返回0。
WSADATA结构被用来保存函数WSAStartup返回的Windows sockets初始化信息。
2.创建socket
其代码如下:
//2.创建socket
//参数1.指定IP协议 ipv4(AF_INET) ipv6(AF_INET6)
//参数2.数据传输格式,常见的有两种,流式传输和数据报传输
//参数3.传输协议, TCP 和 UDP
SOCKET serfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serfd == INVALID_SOCKET) {
error_die("socket");
}
3.绑定IP地址和端口号
其代码如下:
struct sockaddr_in serAddr;
/*
typedef struct sockaddr_in {
USHORT sin_port;
IN_ADDR sin_addr;
CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;
*/
serAddr.sin_family = AF_INET;//必须和创建socket的时候一样
//大端存储--一般网络和小端存储--一般计算机,htons把本地字节序转为网络字节序
serAddr.sin_port = htons(80); //[0-65535) 0-1024是系统保留端口号,一般不用,80是http服务器专用
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;//绑定本机任意服务器
if (SOCKET_ERROR == bind(serfd, (struct sockaddr*)&serAddr, sizeof(serAddr))) {
error_die("bind");
}
int bind(int socket,const struct *sockaddr,socklen_t address_len);将address指向的sockaddr结构体中的描述的一些属性(IP地址、端口号、地址蔟)于socket套接字进行绑定,调用后,就为socket套接字关联了一个相应的地址和端口号,即发送到地址值该端口的数据可通过socket读取和使用。当然也可以通过该socket发送数据到指定目的。
难点:(struct sockaddr*)&serAddr为什么要强制转换?
答:struct sockaddr是通用的套接字地址,而struct sockaddr_in则是因特网环境下的套接字的地址形式,二者长度一样,都是16字节。一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入到系统调用函数中。
4.监听
其代码如下:
listen(serfd, 10);
连接请求只能由客户端发起,此时服务端的listen函数是将服务端的主动描述符转换为被动描述符,否则无法用于监听客户端的连接。参数1表示socket创建的套接字文件描述符;参数2指定队列的容量。
5.接受连接
其代码如下:
SOCKET clifd = accept(serfd, (struct sockaddr*)&cliAddr, &len);//阻塞的
//SOCKET clifd = accept(serfd, NULL, NULL);
if (clifd == INVALID_SOCKET) {
error_die("accept");
}
printf("have a new connect...\n");
accept()系统调用主要用在基于连接的套接字类型,它提取所监听套接字的等待连接队列中的第一个连接请求,创建一个新的套接字的文件描述符。新建立的套接字准备发送send()和接收数据recv()。其定义如下:int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen)。
6.处理连接请求
其代码如下:
void accept_request(SOCKET clifd) {
//从clifd接受数据
char recvBuf[1024] = "";
if (recv(clifd, recvBuf, sizeof(recvBuf), 0)<=0) {
error_die("recv offline");
}
printf("recvBuf:%s\n", recvBuf);
//直接给客户端发送文本
char filePath[128] = "";
//随机选择一个网页发送
strcpy(filePath,rand() % 2 ? "htmlFiles/index.html" : "htmlFiles/css.html");
//判断文件是否存在
if (access(filePath,0)==0) {//0代表文件是否存在 如果存在返回0,否则返回-1
//发送网页
sendHtml(clifd, filePath);
}
else {
//发送404 not found网页
notFound(clifd);
}
}
void sendHtml(SOCKET clifd, const char * filePath) {
FILE* pr = fopen(filePath, "r");
if (pr==NULL) {
error_die("fopen");
}
char data[1024] = "";
do {
fgets(data, 1024, pr);
send(clifd, data, strlen(data), 0);
} while (!feof(pr));
fclose(pr);
}
void notFound(SOCKET clifd) {
/*
* 1.发送404错误 HTTP/1.0 404 not found
*/
char sendData[1024] = "<html><body><h1>zhinen丶</h1></body></html>";
send(clifd, sendData, strlen(sendData), 0);
char sendBuf[1024];
sprintf(sendBuf, "HTTP/1.0 404 not found\r\n");
send(clifd, sendBuf, strlen(sendBuf), 0);
}
其解释如下:
strcpy(filePath,rand() % 2 ? "htmlFiles/index.html" : "htmlFiles/css.html");
在本地创建两个网页代码,利用随机函数和三元运算符来随机选择其中一个网页。
access()函数有四种方式,00:用来判断指定的文件或目录是否仅存在;04:已存在的文件或目录是否有仅读;02:已存在的文件或目录是否有仅写;06:已存在的文件或目录是否有既读又可写。
7.关闭连接,清理网络库
其代码如下:
closesocket(serfd);
WSACleanup();
8.完整代码
视频讲解地址:https://www.bilibili.com/video/BV1Xv411b7vH?from=search&seid=1982437677755267168&spm_id_from=333.337.0.0
其代码如下:
// httpServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <stdio.h>
#include<string.h>
#include<io.h>
//要使用网络编程,要包含系统给我们提供的头文件
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")
void sendHtml(SOCKET clifd, const char* filePath);
void notFound(SOCKET clifd);
//输出错误代码,并返回输出码
void error_die(const char* str) {
printf("[hint]%s failed:%d", str,WSAGetLastError());//获取错误码
exit(-1);
}
//初始化
void initSocket() {
// 参数1.指定socket版本 参数2.是一个传出参数
WORD version = MAKEWORD(2, 2);
WSADATA wsadata;
if (0 != WSAStartup(version, &wsadata)) {
error_die("WSAStartup");
}
}
SOCKET listenClient(){
//2.创建socket
//参数1.指定IP协议 ipv4(AF_INET) ipv6(AF_INET6)
//参数2.数据传输格式,常见的有两种,流式传输和数据报传输
//参数3.传输协议, TCP 和 UDP
SOCKET serfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (serfd == INVALID_SOCKET) {
error_die("socket");
}
//3.绑定IP地址和端口号
//参数1.指定的socket
//参数2.IP地址和端口号
//参数3.
struct sockaddr_in serAddr;
/*
typedef struct sockaddr_in {
USHORT sin_port;
IN_ADDR sin_addr;
CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;
*/
serAddr.sin_family = AF_INET;//必须和创建socket的时候一样
//大端存储--一般网络和小端存储--一般计算机,htons把本地字节序转为网络字节序
serAddr.sin_port = htons(80); //[0-65535) 0-1024是系统保留端口号,一般不用,80是http服务器专用
serAddr.sin_addr.S_un.S_addr = INADDR_ANY;//绑定本机任意服务器
if (SOCKET_ERROR == bind(serfd, (struct sockaddr*)&serAddr, sizeof(serAddr))) {
error_die("bind");
}
//4.监听
listen(serfd, 10);
return serfd;
}
void accept_request(SOCKET clifd) {
//从clifd接受数据
char recvBuf[1024] = "";
if (recv(clifd, recvBuf, sizeof(recvBuf), 0)<=0) {
error_die("recv offline");
}
printf("recvBuf:%s\n", recvBuf);
//直接给客户端发送文本
char filePath[128] = "";
//随机选择一个网页发送
strcpy(filePath,rand() % 2 ? "htmlFiles/index.html" : "htmlFiles/css.html");
//判断文件是否存在
if (access(filePath,0)==0) {//0代表文件是否存在 如果存在返回0,否则返回-1
//发送网页
sendHtml(clifd, filePath);
}
else {
//发送404 not found网页
notFound(clifd);
}
}
void sendHtml(SOCKET clifd, const char * filePath) {
FILE* pr = fopen(filePath, "r");
if (pr==NULL) {
error_die("fopen");
}
char data[1024] = "";
do {
fgets(data, 1024, pr);
send(clifd, data, strlen(data), 0);
} while (!feof(pr));
fclose(pr);
}
void notFound(SOCKET clifd) {
/*
* 1.发送404错误 HTTP/1.0 404 not found
*/
char sendData[1024] = "<html><body><h1>zhinen丶</h1></body></html>";
send(clifd, sendData, strlen(sendData), 0);
char sendBuf[1024];
sprintf(sendBuf, "HTTP/1.0 404 not found\r\n");
send(clifd, sendBuf, strlen(sendBuf), 0);
}
int main()
{
//1.初始化网络库wsa windows socket ansyc->windows异步套接字
initSocket();
SOCKET serfd = listenClient();
printf("zhinen丶的http服务器!\n");
//5.接收链接
struct sockaddr_in cliAddr;
int len = sizeof(cliAddr);
while (1) {
SOCKET clifd = accept(serfd, (struct sockaddr*)&cliAddr, &len);//阻塞的
//SOCKET clifd = accept(serfd, NULL, NULL);
if (clifd == INVALID_SOCKET) {
error_die("accept");
}
printf("have a new connect...\n");
//6.处理链接请求
//直接给客户端发送文本
//char sendData[1024] = "<html><body><h1>zhinen丶</h1></body></html>";
//send(clifd, sendData, strlen(sendData), 0);
accept_request(clifd);
closesocket(clifd);//发送完直接关闭,http是无连接的
}
//7.关闭链接,清理网络库
closesocket(serfd);
WSACleanup();
return 0;
}