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跳转的实现
Mar 31 Servers
浅谈Nginx 中的两种限流方式
Mar 31 Servers
Nginx下配置Https证书详细过程
Apr 01 Servers
Nginx部署vue项目和配置代理的问题解析
Aug 04 Servers
解决使用了nginx获取IP地址都是127.0.0.1 的问题
Sep 25 Servers
nginx刷新页面出现404解决方案(亲测有效)
Mar 18 Servers
openstack云计算keystone组件工作介绍
Apr 20 Servers
Apache POI操作批量导入MySQL数据库
Jun 21 Servers
Windows server 2012 NTP时间同步的实现
Jun 25 Servers
Windows Server 2008配置防火墙策略详解
Jun 28 Servers
使用 DataAnt 监控 Apache APISIX的原理解析
Jul 07 Servers
Nginx 502 bad gateway错误解决的九种方案及原因
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 广告调用类代码(支持Flash调用)
2011/08/11 PHP
php数组的概述及分类与声明代码演示
2013/02/26 PHP
php读取3389的脚本
2014/05/06 PHP
php读取目录及子目录下所有文件名的方法
2014/10/20 PHP
PHP图片处理之使用imagecopyresampled函数裁剪图片例子
2014/11/19 PHP
PHP中把有符号整型转换为无符号整型方法
2015/05/27 PHP
PHP实现批量上传单个文件
2015/12/29 PHP
用正则获取指定路径文件的名称
2007/02/27 Javascript
设置下载不需要倒计时cookie(倒计时代码)
2008/11/19 Javascript
在UpdatePanel内jquery easyui效果失效的解决方法
2010/04/11 Javascript
jquery提升性能最佳实践小结
2010/12/06 Javascript
jQuery插件PageSlide实现左右侧栏导航菜单
2015/04/12 Javascript
jQuery包裹节点用法完整示例
2016/09/13 Javascript
Bootstrap导航条可点击和鼠标悬停显示下拉菜单
2016/11/25 Javascript
Ionic+AngularJS实现登录和注册带验证功能
2017/02/09 Javascript
webpack教程之webpack.config.js配置文件
2017/07/05 Javascript
ios设备中angularjs无法改变页面title的解决方法
2018/09/13 Javascript
详解element-ui中form验证杂记
2019/03/04 Javascript
原生JS利用transform实现banner的无限滚动示例代码
2020/06/15 Javascript
python连接sql server乱码的解决方法
2013/01/28 Python
Python通过PIL获取图片主要颜色并和颜色库进行对比的方法
2015/03/19 Python
Python图像处理之简单画板实现方法示例
2018/08/30 Python
padas 生成excel 增加sheet表的实例
2018/12/11 Python
OpenCV3.0+Python3.6实现特定颜色的物体追踪
2019/07/23 Python
django做form表单的数据验证过程详解
2019/07/26 Python
PyTorch中Tensor的维度变换实现
2019/08/18 Python
西班牙伏林航空公司:Vueling
2016/08/05 全球购物
施华洛世奇天猫官方旗舰店:SWAROVSKI
2017/04/17 全球购物
丝芙兰意大利官方网站:Sephora.it
2019/12/13 全球购物
美国饼干礼物和美食甜点购买网站:Cheryl’s
2020/05/28 全球购物
美国相机和电子产品零售商:Beach Camera
2020/11/26 全球购物
医学专业职业生涯规划范文
2014/02/05 职场文书
化验员岗位职责
2015/02/14 职场文书
小学生一年级(书信作文)
2019/08/13 职场文书
详解JS ES6编码规范
2021/05/07 Javascript
python 远程执行命令的详细代码
2022/02/15 Python