C语言实现简单的Web服务器

  • 一. 基础知识
  • 二. 详细设计
  • 三. 代码实现
  • 四. 功能测试
  • 五. 内容总结

个人博客:coonaa.cn 【本文博客同步地址】

在之前的文章中使用C语言实现基于TCP的WinSock套接字编程。基于此,同样可以使用C语言来实现简单的Web服务器。

一. 基础知识

1. TCP协议和WinSock套接字
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP协议的通信需要经过创建连接(三次握手)、数据传送、终止连接(四次挥手)三个步骤。
TCP三次握手

TCP四次挥手

WinSock套接字是Windows操作系统所提供的网络编程接口,是一个抽象层,应用程序可以通过套接字来实现数据的发送和接收。
基于TCP的WinSock工作流程

2. HTTP协议
超文本传输协议(HTTP,Hypertext Transfer Protocol),是一个基于请求与响应模式的、无状态的、应用层的协议,通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII码的形式给出,消息内容则具有一个类似MIME的格式。
可以抽象的认为,基于TCP的WinSock编程已经铺好了一条路,接下来需要的就是选用一辆好的卡车来输送客户端和服务器端的“交流”信息了。HTTP协议正是这辆卡车。
HTTP请求报文的基本格式构成

二. 详细设计

Web服务器与客户端之间的大致工作流程

整个Web服务器设计可分为以下几个部分:
1. 初始化Windows Socket,为TCP连接的建立做准备
①加载Windows Socket;
②创建套接字;
③根据服务器端的相关IP地址和端口号信息,进行套接字绑定。

2. 启动监听,接受客户端请求建立TCP连接,接收HTTP请求报文
①Web服务器启动监听;
②接收来自客户端的连接请求,建立TCP连接;
③接收来自客户端的TCP传输数据,即HTTP请求报文。

3. 处理HTTP请求报文并做出响应
①处理HTTP请求报文的请求行,提取“请求方法”、“URL”、“HTTP版本“三个关键要素;
②判断处理“请求方法”,根据判断处理的结果,构造相应的响应报文,反馈相关给客户端,并且在服务器端打印反馈的结果;
③判断处理“URL”,根据判断处理的结果,构造相应的响应报文,反馈相关给客户端,并且在服务器端打印反馈的结果;
④由于Web服务器的要求不太高以及目前主流的客户端浏览器都会采用较“统一”的HTTP协议版本,所以“HTTP版本”的关键信息可以做忽略处理。

4. 关闭连接及Windows Socket
①关闭所建立连接的套接字;
②关闭Windows Socket。

注:根据实际情况,可运用循环多线程等方式对以上步骤进行重构。

三. 代码实现

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNNINGS#define SERVER_IP_ADDR "127.0.0.1"    //服务器IP地址
#define SERVER_PORT 80              //服务器端口号
#define BACKLOG 10
#define BUF_SIZE 1024
#define OK 1
#define ERROR 0#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")const char* Server_name = "Server: Web Server 1.0 - BooLo\r\n";
//Web服务器信息 int Server_Socket_Init();
int Handle_Request_Message(char* message, int Socket);
int Judge_URI(char* URI, int Socket);
int Send_Ifon(int Socket, const char* sendbuf, int Length);
int Error_Request_Method(int Socket);
int Inquire_File(char* URI);
int File_not_Inquire(int Socket);
int Send_File(char* URI, int Socket);
int Logo();
const char* Judge_Method(char* method, int Socket);
const char* Judge_File_Type(char* URI, const char* content_type);
const char* Get_Data(const char* cur_time);
const char* Post_Value(char* message);int Server_Socket_Init() {//初始化和构造套接字 WORD wVersionrequested;WSADATA wsaData;SOCKET ServerSock;struct sockaddr_in ServerAddr;int rval;/* 加载Winsock */wVersionrequested = MAKEWORD(2, 2);if (WSAStartup(wVersionrequested, &wsaData) != 0) {printf("Failed to load Winsock!\n");system("pause");return -1;}printf("Succeed to load Winsock!\n");/* 创建套接字 */ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (ServerSock == INVALID_SOCKET) {printf("Failed to create socket!\n");system("pause");exit(1);}printf("Succeed to create socket!\n");/* 配置服务器IP、端口信息 */memset(&ServerAddr, 0, sizeof(struct sockaddr));   //每一个字节都用0来填充ServerAddr.sin_family = AF_INET;ServerAddr.sin_port = htons(SERVER_PORT);ServerAddr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);/* 绑定 */rval = bind(ServerSock, (SOCKADDR*)&ServerAddr, sizeof(struct sockaddr));if (rval == SOCKET_ERROR) {printf("Failed to bind stream socket!\n");system("pause");exit(1);}printf("Succeed to bind stream socket!\n");return ServerSock;
}int Handle_Request_Message(char* message, int Socket) {//处理HTTP请求报文信息 int rval = 0;char Method[BUF_SIZE];char URI[BUF_SIZE];char Version[BUF_SIZE];if (sscanf(message, "%s %s %s", Method, URI, Version) != 3) {printf("Request line error!\n");return ERROR;}   //提取"请求方法"、"URL"、"HTTP版本"三个关键要素 if (Judge_Method(Method, Socket) == ERROR) {return ERROR;}else if(Judge_Method(Method, Socket) == "POST") {Post_Value(message);}    //判断处理"请求方法" if (Judge_URI(URI, Socket) == ERROR) {return ERROR;}   //判断处理"URI" elserval = Send_File(URI, Socket);if (rval == OK) {printf("The process is successfully finished!\n");}return OK;
}const char* Judge_Method(char* method, int Socket) {//判断请求方式 if (strcmp(method, "GET") == 0) {return "GET";}else if (strcmp(method, "POST") == 0) {return "POST";}else{Error_Request_Method(Socket);return ERROR;}
}int Judge_URI(char* URI, int Socket) {//判断请求URI if (Inquire_File(URI) == ERROR) {File_not_Inquire(Socket);return ERROR;}elsereturn OK;
}int Send_Ifon(int Socket, const char* sendbuf, int Length) {//发送信息到客户端 int sendtotal = 0, bufleft, rval = 0;bufleft = Length;while (sendtotal < Length) {rval = send(Socket, sendbuf + sendtotal, bufleft, 0);if (rval < 0) {break;}sendtotal += rval;bufleft -= rval;}Length = sendtotal;return rval < 0 ? ERROR : OK;
}int Error_Request_Method(int Socket) {//501 Not Implemented响应 const char* Method_err_line = "HTTP/1.1 501 Not Implemented\r\n";const char* cur_time = "";const char* Method_err_type = "Content-type: text/plain\r\n";const char* File_err_length = "Content-Length: 41\r\n";const char* Method_err_end = "\r\n";const char* Method_err_info = "The request method is not yet completed!\n";printf("The request method from client's request message is not yet completed!\n");if (Send_Ifon(Socket, Method_err_line, strlen(Method_err_line)) == ERROR) {printf("Sending method_error_line failed!\n");return ERROR;}if (Send_Ifon(Socket, Server_name, strlen(Server_name)) == ERROR) {printf("Sending Server_name failed!\n");return ERROR;}cur_time = Get_Data(cur_time);Send_Ifon(Socket, "Data: ", 6);if (Send_Ifon(Socket, cur_time, strlen(cur_time)) == ERROR) {printf("Sending cur_time error!\n");return ERROR;}if (Send_Ifon(Socket, Method_err_type, strlen(Method_err_type)) == ERROR) {printf("Sending method_error_type failed!\n");return ERROR;}if (Send_Ifon(Socket, Method_err_end, strlen(Method_err_end)) == ERROR) {printf("Sending method_error_end failed!\n");return ERROR;}if (Send_Ifon(Socket, Method_err_info, strlen(Method_err_info)) == ERROR) {printf("Sending method_error_info failed!\n");return ERROR;}return OK;
}int Inquire_File(char* URI) {//查找文件 struct stat File_info;if (stat(URI, &File_info) == -1)return ERROR;elsereturn File_info.st_size;
}int File_not_Inquire(int Socket) {//404 Not Found响应 const char* File_err_line = "HTTP/1.1 404 Not Found\r\n";const char* cur_time = "";const char* File_err_type = "Content-type: text/plain\r\n";const char* File_err_length = "Content-Length: 42\r\n";const char* File_err_end = "\r\n";const char* File_err_info = "The file which is requested is not found!\n";printf("The request file from client's request message is not found!\n");if (Send_Ifon(Socket, File_err_line, strlen(File_err_line)) == ERROR) {printf("Sending file_error_line error!\n");return ERROR;}if (Send_Ifon(Socket, Server_name, strlen(Server_name)) == ERROR) {printf("Sending Server_name failed!\n");return ERROR;}cur_time = Get_Data(cur_time);Send_Ifon(Socket, "Data: ", 6);if (Send_Ifon(Socket, cur_time, strlen(cur_time)) == ERROR) {printf("Sending cur_time error!\n");return ERROR;}if (Send_Ifon(Socket, File_err_type, strlen(File_err_type)) == ERROR) {printf("Sending file_error_type error!\n");return ERROR;}if (Send_Ifon(Socket, File_err_length, strlen(File_err_length)) == ERROR) {printf("Sending file_error_length error!\n");return ERROR;}if (Send_Ifon(Socket, File_err_end, strlen(File_err_end)) == ERROR) {printf("Sending file_error_end error!\n");return ERROR;}if (Send_Ifon(Socket, File_err_info, strlen(File_err_info)) == ERROR) {printf("Sending file_error_info failed!\n");return ERROR;}return OK;
}int Send_File(char* URI, int Socket) {//200 OK响应 const char* File_ok_line = "HTTP/1.1 200 OK\r\n";const char* cur_time = "";const char* File_ok_type = "";const char* File_ok_length = "Content-Length: ";const char* File_ok_end = "\r\n";FILE* file;struct stat file_stat;char Length[BUF_SIZE];char sendbuf[BUF_SIZE];int send_length;if (Judge_File_Type(URI, File_ok_type) == ERROR) {printf("The request file's type from client's request message is error!\n");return ERROR;}file = fopen(URI, "rb");if (file != NULL) {fstat(fileno(file), &file_stat);itoa(file_stat.st_size, Length, 10);if (Send_Ifon(Socket, File_ok_line, strlen(File_ok_line)) == ERROR) {printf("Sending file_ok_line error!\n");return ERROR;}if (Send_Ifon(Socket, Server_name, strlen(Server_name)) == ERROR) {printf("Sending Server_name failed!\n");return ERROR;}cur_time = Get_Data(cur_time);Send_Ifon(Socket, "Data: ", 6);if (Send_Ifon(Socket, cur_time, strlen(cur_time)) == ERROR) {printf("Sending cur_time error!\n");return ERROR;}File_ok_type = Judge_File_Type(URI, File_ok_type);if (Send_Ifon(Socket, File_ok_type, strlen(File_ok_type)) == ERROR) {printf("Sending file_ok_type error!\n");return ERROR;}if (Send_Ifon(Socket, File_ok_length, strlen(File_ok_length)) != ERROR) {if (Send_Ifon(Socket, Length, strlen(Length)) != ERROR) {if (Send_Ifon(Socket, "\n", 1) == ERROR) {printf("Sending file_ok_length error!\n");return ERROR;}}}if (Send_Ifon(Socket, File_ok_end, strlen(File_ok_end)) == ERROR) {printf("Sending file_ok_end error!\n");return ERROR;}while (file_stat.st_size > 0) {if (file_stat.st_size < 1024) {send_length = fread(sendbuf, 1, file_stat.st_size, file);if (Send_Ifon(Socket, sendbuf, send_length) == ERROR) {printf("Sending file information error!\n");continue;}file_stat.st_size = 0;}else {send_length = fread(sendbuf, 1, 1024, file);if (Send_Ifon(Socket, sendbuf, send_length) == ERROR) {printf("Sending file information error!\n");continue;}file_stat.st_size -= 1024;}}}else {printf("The file is NULL!\n");return ERROR;}return OK;
}const char* Judge_File_Type(char* URI, const char* content_type) {//文件类型判断 const char* suffix;if ((suffix = strrchr(URI, '.')) != NULL)suffix = suffix + 1;if (strcmp(suffix, "html") == 0) {return content_type = "Content-type: text/html\r\n";}else if (strcmp(suffix, "jpg") == 0) {return content_type = "Content-type: image/jpg\r\n";}else if (strcmp(suffix, "png") == 0) {return content_type = "Content-type: image/png\r\n";}else if (strcmp(suffix, "gif") == 0) {return content_type = "Content-type: image/gif\r\n";}else if (strcmp(suffix, "txt") == 0) {return content_type = "Content-type: text/plain\r\n";}else if (strcmp(suffix, "xml") == 0) {return content_type = "Content-type: text/xml\r\n";}else if (strcmp(suffix, "rtf") == 0) {return content_type = "Content-type: text/rtf\r\n";}elsereturn ERROR;
}const char* Get_Data(const char* cur_time) {//获取Web服务器的当前时间作为响应时间 time_t curtime;time(&curtime);cur_time = ctime(&curtime);return cur_time;
}const char* Post_Value(char* message) {//获取客户端POST请求方式的值 const char* suffix;if ((suffix = strrchr(message, '\n')) != NULL)suffix = suffix + 1;printf("\n\nPost Value: %s\n\n", suffix);return suffix;
}int Logo() {//Web服务器标志信息 printf("___________________________________________________________\n");printf("  __          ________ _______\n");printf("  \\ \\        / /  ____|  _____\\\n");printf("   \\ \\  /\\  / /| |____  |____) )\n");printf("    \\ \\/  \\/ / |  ____|  ____(   __  __     __ ___\n");printf("     \\  /\\  /  | |____  |____) )(__ |_ \\  /|_ |___)\n");printf("      \\/  \\/   |______|_______/  __)|__ \\/ |__|   \\\n");printf("\n");printf("            Welcome to use the Web Server!\n");printf("                     Version 1.0\n\n");printf("                         BooLo\n");printf("___________________________________________________________\n\n");return OK;
}int main() {//实现主要功能 SOCKET ServerSock, MessageSock;struct sockaddr_in ClientAddr;int rval, Length;char revbuf[BUF_SIZE];Logo();printf("Web Server 1.0 is starting......\n\n");ServerSock = Server_Socket_Init();printf("\n-----------------------------------------------------------\n");while (OK) {/* 启动监听 */rval = listen(ServerSock, BACKLOG);if (rval == SOCKET_ERROR) {printf("Failed to listen socket!\n");system("pause");exit(1);}printf("Listening the socket ......\n");/* 接受客户端请求建立连接 */Length = sizeof(struct sockaddr);MessageSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &Length);if (MessageSock == INVALID_SOCKET) {printf("Failed to accept connection from client!\n");system("pause");exit(1);}printf("Succeed to accept connection from [%s:%d] !\n\n", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));/* 接收客户端请求数据 */memset(revbuf, 0, BUF_SIZE);   //每一个字节都用0来填充 rval = recv(MessageSock, revbuf, BUF_SIZE, 0);revbuf[rval] = 0x00;if (rval <= 0)printf("Failed to receive request message from client!\n");else {printf("%s\n", revbuf);    //输出请求数据内容rval = Handle_Request_Message(revbuf, MessageSock);}closesocket(MessageSock);printf("\n-----------------------------------------------------------\n");}closesocket(ServerSock);   //关闭套接字 WSACleanup();   //停止Winsockreturn OK;
}

四. 功能测试

测试准备工作
功能测试需要两个准备工作:准备测试文件、将文件存放到正确的文件目录。

(1)准备测试文件
准备几个常见文件类型的测试文件,内容随意。需要本文中测试文件的小伙伴也可以在我的博客留言联系我。

(2)将文件存放到正确的文件目录
由于在设计本程序时采用了“绝对路径”,因此需要将程序和测试文件存放到同一个磁盘的特定目录内(不理解“绝对路径”和“相对路径”的小伙伴自行某度)。

①在某个磁盘的根目录下创建一个文件夹,将测试文件和服务器程序存放到在该文夹内;

②运行服务器程序后,打开浏览器,在地址栏输入对应的文件目录即可请求成功。注意:IP地址后的第一个斜杠“ / ”即表示对应磁盘的“根目录”。

程序运行测试

index.html页面请求测试




picture1.jpg图片请求测试



download_test.rtf文档下载测试



POST请求方式测试

404响应状态测试


五. 内容总结

本文中Web服务器程序的设计和开发,关键在于TCP协议、Windows Socket网络接口、HTTP协议三大知识点。在实现最基本的Web服务器核心功能的过程中,这三者缺一不可。
在Web服务器程序的设计和开发过程中,需要充分了解Web服务器的工作过程:从浏览器发起请求到浏览器显示请求的页面内容的整个过程,来逐一完成各个阶段的设计和开发。
开发过程中,特别是编码的过程中,会遇到很多的难题。这些难题通过参考一些官方的代码示例能够得到一定的启示,从而解决问题。

本文中的程序并未实现多线程,各位有兴趣的小伙伴可自行进行改进。

C语言实现简单的Web服务器相关推荐

  1. 标准c语言建立简单的web服务器,C语言写的简易实用的web服务器

    码农公社  210.net.cn  210是何含义?10月24日是程序员节,1024 =210.210既 210 之意. Apache在码农界是比较知名的,它也是目前最接地气.使用最广泛的Web服务器 ...

  2. 我的Go语言学习之旅八:创建一个简单的WEB服务器

    因为一直在做WEB程序,所以更关注WEB界的发展,这里就用GO做了一个简单的WEB服务器,直接看例子吧 package main import ( "fmt" "net/ ...

  3. 用python写一个简单的web服务器

    人生苦短,我用python 简洁高效,这才是理想的语言啊 分享一点python的学习经验-----如何用python写一个简单的web服务器 首先,我们需要简单地了解一下网络通信协议,这里用白话介绍一 ...

  4. 用Python建立最简单的web服务器

    用Python建立最简单的web服务器 利用Python自带的包可以建立简单的web服务器.在DOS里cd到准备做服务器根目录的路径下,输入命令: python -m Web服务器模块 [端口号,默认 ...

  5. ipad php mysql_如何用PHP/MySQL为 iOS App 写一个简单的web服务器(译) PART1

    原文:http://www.raywenderlich.com/2941/how-to-write-a-simple-phpmysql-web-service-for-an-ios-app 作为一个i ...

  6. 使用node.js作为简单的Web服务器

    我想运行一个非常简单的HTTP服务器. 对example.com每个GET请求都应该将index.html提供给它,但是作为常规HTML页面(即,与阅读普通网页时相同的体验). 使用下面的代码,我可以 ...

  7. 基于epoll实现简单的web服务器

    1. 简介 epoll 是 Linux 平台下特有的一种 I/O 复用模型实现,于 2002 年在 Linux kernel 2.5.44 中被引入.在 epoll 之前,Unix/Linux 平台下 ...

  8. Linux C简单的web服务器

    Linux C简单的web服务器 目录 Linux C简单的web服务器 一.基础类型重命名 二.包裹函数(wrap.h/wrap.c 主要是网络通讯和多线程的包裹函数) 三.服务端程序(web_se ...

  9. python搭建web服务器_Python搭建简单的web服务器

    Python搭建简单的web服务器 1.win+R输入cmd打开命令行 2.通过 cd 进入到你保存 HTML 文件的目录.例如:H:\D3\d3 输入 cd\ 指令进入到C盘的根目录.(CD(更改目 ...

最新文章

  1. Java的类,对象以及字段和方法
  2. 利用XML实现通用WEB报表打印(转载)
  3. codeproject网页翻译
  4. 【Leetcode】大神总结的链表常见面试问题
  5. 2017-9-17pat甲级 C
  6. c++ windows获得当前工作目录文件_使用命令行修改当前工作目录
  7. java文件绝对路径_获取文件夹文件绝对路径
  8. idea Spring-boot 项目debug启动过慢 spring debug启动过慢解决办法:已解决
  9. Visual Basic.Net连各种数据库的几种方法
  10. mac nginx php-fpm,Mac OS nginx 502 解决记录(php-fpm 启动失败)
  11. 服务器响应302是什么意思,HTTP 状态中的 301 和 302 是什么意思?二者有何不同?...
  12. 万恶的LayoutSubviews
  13. 企小码会话存档使用教程——删人提醒
  14. 清华大学出版社大数据可视化技术与应用第六章Tableau实训
  15. data mining blog (foreign)
  16. 服务于离群点检测的无监督特征选择值-特征层次耦合模型
  17. 如何从 GRUB rescue 恢复 Win7,win7 USB恢复盘制作
  18. Linux——LDAP(相当于Windows下的AD)
  19. 第三方开发者平台地址整理
  20. 折纸多少次可以达到珠穆朗玛峰的高度

热门文章

  1. 桌面会飞的鸟Qt C++小项目
  2. linux怎样下载115网盘,115云备份 115云备份 v1.0.0 For Linux版
  3. 全能程序员系列(3)-安装Windows Server 2012操作系统
  4. 003-计算机应用基础 统考,计算机应用基础 试题003
  5. 打包rpm包报错contains an invalid rpath
  6. 记录一次腾讯云木马攻击
  7. 回头再说 011 金庸-读你千遍不厌倦
  8. 国产姿态传感器(陀螺仪)
  9. 支撑一个人信念的名言
  10. 在本地怎么使用phpstudy搭建WordPress网站