Linux下使用C语言代码搭建一个简单的HTTP服务器


Posted in Servers onApril 13, 2022

1. 前言

作为Linux下socket(TCP)网络编程的练习,使用C语言代码搭建一个简单的HTTP服务器,完成与浏览器之间的交互,最终在浏览器上显示一张图片。

2. HTTP协议介绍

HTTP协议本身是基于TCP通信协议来传递数据(HTML 文件, 图片文件-也叫超文本传输协议),HTTP协议必须工作在客户端-服务端架构上(本身底层就是TCP),HTTP 默认端口号为 80(浏览器访问默认就是80端口),但是你也可以改为 8080 或者其他端口(可以手动指定端口)。

HTTP协议是无连接的,也就是限制每次连接只处理一个请求;服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

3. HTTP的消息结构

客户端向HTTP服务器发送的请求消息格式包括了4个部分:
请求行(request line)、 请求头部(header)、空行、请求数据

Linux下使用C语言代码搭建一个简单的HTTP服务器

下面这个是浏览器的请求,可以对比上面这张图的格式:

GET / HTTP/1.1
Host: 10.0.0.6
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

Linux下使用C语言代码搭建一个简单的HTTP服务器

HTTP常用的请求是GETPOST

HTTP1.0 定义了三种请求方法: GET, POST 和 HEAD 方法。
HTTP1.1 新增了五种请求方法: OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。

HTTP服务器向客户端的响应也由四个部分组成,分别是:状态行、消息报头、空行、响应正文。

例如:

"HTTP/1.1 200 OK\r\n"
"Content-type:image/jpeg\r\n"
"Content-Length:1234\r\n"
"\r\n"
"...............正文............."

上面列出的报文字段含义:
HTTP/1.0 200 OK: Http/1.0 表示当前协议为 Http。 1.0 是协议的版本。 200 表示成功

Content-type : 告诉浏览器回送的数据类型

Content-Length: 告诉浏览器报文中实体主体的大小,也就是返回的内容长度

上面字段里回复的状态码一般有好几种,分别是:
200 - 请求成功
301 - 资源(网页等)被永久转移到其它 URL
404 - 请求的资源(网页等)不存在
500 - 内部服务器错误

4. HTTP交互流程

第一次请求是由HTTP客户端(浏览器)发起的,HTTP服务器收到请求后,对请求进行解析,然后完成后续的交互。

如果要在浏览器上显示一张图片,那么交互的流程大致如下:

Linux下使用C语言代码搭建一个简单的HTTP服务器

Linux下使用C语言代码搭建一个简单的HTTP服务器

要让浏览器在界面显示一张图片,还得编写一个HTML代码给浏览器,直接用一个图片标签即可。

当前程序使用的HTML代码比较简单,代码下面贴出来了:

<! DOCTYPE HTML>
<html>
	<head>
		<title>jpg</title>
		<meta http-equiv="content-type" content="text/html; charset=iso-8859-1" />
	</head>
	
	<body>
		<center><img src="www/123.jpg" width="512px" height="384px" />
		</center>
	</body>
</html>

然后还得准备一张JPG图片,作为资源文件,方便传递给浏览器,本地文件结构如下:

Linux下使用C语言代码搭建一个简单的HTTP服务器

5. 案例代码: 搭建HTTP服务器

下面代码采用多线程形式响应浏览器的请求。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>

/*
函数功能: 服务器向客户端发送响应数据
*/
int HTTP_ServerSendFile(int client_fd,char *buff,char *type,char *file)
{
    /*1. 打开文件*/
    int fd=open(file,2);
    if(fd<0)return -1;
    /*2. 获取文件大小*/
    struct stat s_buff;
    fstat(fd,&s_buff);
    /*3. 构建响应头部*/
    sprintf(buff,"HTTP/1.1 200 OK\r\n"
                "Content-type:%s\r\n"
                "Content-Length:%d\r\n"
                "\r\n",type,s_buff.st_size);
    /*4. 发送响应头*/
    if(write(client_fd,buff,strlen(buff))!=strlen(buff))return -2;
    /*5. 发送消息正文*/
    int cnt;
    while(1)
    {
        cnt=read(fd,buff,1024);
        if(write(client_fd,buff,cnt)!=cnt)return -3;
        if(cnt!=1024)break;
    }
    return 0;
}

/*线程工作函数*/
void *thread_work_func(void *argv)
{
    int client_fd=*(int*)argv;
    free(argv);

    unsigned int cnt;
    unsigned char buff[1024];
    //读取浏览器发送过来的数据
    cnt=read(client_fd,buff,1024);
    buff[cnt]='\0';
    printf("%s\n",buff);

    if(strstr(buff,"GET / HTTP/1.1"))
    {
        HTTP_ServerSendFile(client_fd,buff,"text/html","www/image_text.html");
    }
    else if(strstr(buff,"GET /www/123.jpg HTTP/1.1"))
    {
        HTTP_ServerSendFile(client_fd,buff,"image/jpeg","www/888.jpg");
    }
    else if(strstr(buff,"GET /favicon.ico HTTP/1.1"))
    {
        HTTP_ServerSendFile(client_fd,buff,"image/x-icon","www/1.ico");
    }
    
    close(client_fd);
    //退出线程
    pthread_exit(NULL);
}

int main(int argc,char **argv)
{   
    if(argc!=2)
    {
        printf("./app <端口号>\n");
        return 0;
    }

    signal(SIGPIPE,SIG_IGN); //忽略 SIGPIPE 信号--防止服务器异常退出

    int sockfd;
    /*1. 创建socket套接字*/
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    int on = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

    /*2. 绑定端口号与IP地址*/
    struct sockaddr_in addr;
    addr.sin_family=AF_INET;
    addr.sin_port=htons(atoi(argv[1])); // 端口号0~65535
    addr.sin_addr.s_addr=INADDR_ANY;    //inet_addr("0.0.0.0"); //IP地址
    if(bind(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr))!=0)
    {
        printf("服务器:端口号绑定失败.\n");
    }
    /*3. 设置监听的数量,表示服务器同一时间最大能够处理的连接数量*/
    listen(sockfd,20);

    /*4. 等待客户端连接*/
    int *client_fd;
    struct sockaddr_in client_addr;
    socklen_t addrlen;
    pthread_t thread_id;
    while(1)
    {
        addrlen=sizeof(struct sockaddr_in);
        client_fd=malloc(sizeof(int));
        *client_fd=accept(sockfd,(struct sockaddr *)&client_addr,&addrlen);
        if(*client_fd<0)
        {
            printf("客户端连接失败.\n");
            return 0;
        }
        printf("连接的客户端IP地址:%s\n",inet_ntoa(client_addr.sin_addr));
        printf("连接的客户端端口号:%d\n",ntohs(client_addr.sin_port));

        /*创建线程*/
        if(pthread_create(&thread_id,NULL,thread_work_func,client_fd))
        {
            printf("线程创建失败.\n");
            break;
        }
        /*设置线程的分离属性*/
        pthread_detach(thread_id);
    } 
    /*5. 关闭连接*/
    close(sockfd);
    return 0;
}

6. 最终运行的效果

Linux下使用C语言代码搭建一个简单的HTTP服务器

到此这篇关于Linux下搭建简易的HTTP服务器完成图片显示的文章就介绍到这了!

Servers 相关文章推荐
Nginx Rewrite使用场景及配置方法解析
Apr 01 Servers
关于Nginx中虚拟主机的一些冷门知识小结
Mar 03 Servers
Nginx中使用Lua脚本与图片的缩略图处理的实现
Mar 18 Servers
Shell脚本一键安装Nginx服务自定义Nginx版本
Mar 20 Servers
Nginx图片服务器配置之后图片访问404的问题解决
Mar 21 Servers
Dashboard管理Kubernetes集群与API访问配置
Apr 01 Servers
Nginx隐藏式跳转(浏览器URL跳转后保持不变)
Apr 07 Servers
docker-compose部署Yapi的方法
Apr 08 Servers
阿里云日志过滤器配置日志服务
Apr 09 Servers
解决Vmware虚拟机安装centos8报错“Section %Packages Does Not End With %End. Pane Is Dead”
Jun 01 Servers
如何让你的Nginx支持分布式追踪详解
Jul 07 Servers
Tomcat安装使用及部署Web项目的3种方法汇总
Aug 14 Servers
idea下配置tomcat避坑详解
CentOS安装Nginx并部署vue
CentOS7安装GlusterFS集群以及相关配置
Nginx+Tomcat负载均衡多实例详解
Nginx配置根据url参数重定向
Apr 11 #Servers
在Docker容器中部署SQL Server
Apr 11 #Servers
阿里云日志过滤器配置日志服务
You might like
PHP文件锁函数flock()详细介绍
2014/11/18 PHP
PHP pthreads v3下worker和pool的使用方法示例
2020/02/21 PHP
经典的解除许多网站无法复制文字的绝招
2006/12/31 Javascript
jQuery.Highcharts.js绘制柱状图饼状图曲线图
2015/03/14 Javascript
jQuery给多个不同元素添加class样式的方法
2015/03/26 Javascript
js控制文本框输入的字符类型方法汇总
2015/06/19 Javascript
详解AngularJS中$http缓存以及处理多个$http请求的方法
2016/02/06 Javascript
基于JavaScript FileReader上传图片显示本地链接
2016/05/27 Javascript
超实用的javascript时间处理总结
2016/08/16 Javascript
关于json字符串与实体之间的严格验证代码
2016/11/10 Javascript
如何解决jQuery EasyUI 已打开Tab重新加载问题
2016/12/19 Javascript
bootstrap选项卡扩展功能详解
2017/06/14 Javascript
ES6解构赋值实例详解
2017/10/31 Javascript
微信小程序实现下载进度条的方法
2017/12/08 Javascript
基于Bootstrap下拉框插件bootstrap-select使用方法详解
2018/08/07 Javascript
JS实现数组的增删改查操作示例
2018/08/29 Javascript
jQuery实现的卷帘门滑入滑出效果【案例】
2019/02/18 jQuery
Node.JS枚举统计当前文件夹和子目录下所有代码文件行数
2019/08/23 Javascript
详解Webpack4多页应用打包方案
2020/07/16 Javascript
python中的实例方法、静态方法、类方法、类变量和实例变量浅析
2014/04/26 Python
python字符串,数值计算
2016/10/05 Python
正确理解python中的关键字“with”与上下文管理器
2017/04/21 Python
Python动刷新抢12306火车票的代码(附源码)
2018/01/24 Python
Python列表生成式与生成器操作示例
2018/08/01 Python
详解Python logging调用Logger.info方法的处理过程
2019/02/12 Python
Tensorflow实现神经网络拟合线性回归
2019/07/19 Python
Python使用py2neo操作图数据库neo4j的方法详解
2020/01/13 Python
关于Python字符编码与二进制不得不说的一些事
2020/10/04 Python
HearthSong官网:儿童户外玩具、儿童益智玩具
2017/10/16 全球购物
2014年安全生产大检查方案
2014/05/13 职场文书
安全伴我行演讲稿
2014/09/04 职场文书
2014党员学习兰辉先进事迹思想汇报
2014/09/17 职场文书
机关中层领导干部群众路线教育实践活动个人对照检查材料
2014/09/24 职场文书
2014向国旗敬礼网上签名活动总结
2014/09/27 职场文书
2015年入党决心书
2015/02/05 职场文书
共青团员自我评价
2015/03/10 职场文书