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的反向代理实例详解
Mar 31 Servers
详解Nginx启动失败的几种错误处理
Apr 01 Servers
使用Nginx搭载rtmp直播服务器的方法
Oct 16 Servers
nginx负载功能+nfs服务器功能解析
Feb 28 Servers
Tomcat用户管理的优化配置详解
Mar 31 Servers
详解使用内网穿透工具Ngrok代理本地服务
Mar 31 Servers
基于Apache Hudi在Google云构建数据湖平台的思路详解
Apr 07 Servers
阿里云国际版 使用Nginx作为HTTPS转发代理服务器
May 11 Servers
nginx静态资源的服务器配置方法
Jul 07 Servers
Linux安装Docker详细教程
Jul 07 Servers
Tomcat安装使用及部署Web项目的3种方法汇总
Aug 14 Servers
Flink 侧流输出源码示例解析
Sep 23 Servers
idea下配置tomcat避坑详解
CentOS安装Nginx并部署vue
CentOS7安装GlusterFS集群以及相关配置
Nginx+Tomcat负载均衡多实例详解
Nginx配置根据url参数重定向
Apr 11 #Servers
在Docker容器中部署SQL Server
Apr 11 #Servers
阿里云日志过滤器配置日志服务
You might like
phpStudy访问速度慢和启动失败的解决办法
2015/11/19 PHP
YiiFramework入门知识点总结(图文教程)
2015/12/28 PHP
PHP实现的简单路由和类自动加载功能
2018/03/13 PHP
php使用socket调用http和smtp协议实例小结
2019/07/26 PHP
Aster vs KG BO3 第一场2.19
2021/03/10 DOTA
javascript setTimeout和setInterval 的区别
2009/12/08 Javascript
用Jquery实现可编辑表格并用AJAX提交到服务器修改数据
2009/12/27 Javascript
javascript获取作用在元素上面的样式属性代码
2012/09/20 Javascript
jquery实现图片滚动效果的简单实例
2013/11/23 Javascript
js简单实现根据身份证号码识别性别年龄生日
2013/11/29 Javascript
Javascript模拟加速运动与减速运动代码分享
2014/12/11 Javascript
jQuery中document与window以及load与ready 区别详解
2014/12/29 Javascript
jQuery Validate表单验证入门学习
2015/12/18 Javascript
详解百度百科目录导航树小插件
2017/01/08 Javascript
vue.js学习笔记:如何加载本地json文件
2017/01/17 Javascript
浅谈vue实现数据监听的函数 Object.defineProperty
2017/06/08 Javascript
JS+jQuery实现注册信息的验证功能
2017/09/26 jQuery
深入理解Vue nextTick 机制
2018/04/28 Javascript
基于vue v-for 多层循环嵌套获取行数的方法
2018/09/26 Javascript
vue项目搭建以及全家桶的使用详细教程(小结)
2018/12/19 Javascript
Vue 实现创建全局组件,并且使用Vue.use() 载入方式
2020/08/11 Javascript
Python类定义和类继承详解
2015/05/08 Python
浅谈机器学习需要的了解的十大算法
2017/12/15 Python
django 按时间范围查询数据库实例代码
2018/02/11 Python
基于循环神经网络(RNN)实现影评情感分类
2018/03/26 Python
Python里字典的基本用法(包括嵌套字典)
2019/02/27 Python
Python对列表的操作知识点详解
2019/08/20 Python
Mountain Hardwear官网:攀岩服装和户外装备
2019/09/26 全球购物
英文简历中的自我评价
2013/10/06 职场文书
英语专业毕业生自荐信
2013/10/28 职场文书
个人查摆问题及整改措施
2014/10/16 职场文书
辞职书格式样本
2015/02/26 职场文书
施工单位工程部经理岗位职责
2015/04/09 职场文书
python爬虫selenium模块详解
2021/03/30 Python
golang http使用踩过的坑与填坑指南
2021/04/27 Golang
Redis 配置文件重要属性的具体使用
2021/05/20 Redis