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 相关文章推荐
windows下快速安装nginx并配置开机自启动的方法
May 11 Servers
Nginx 路由转发和反向代理location配置实现
Nov 11 Servers
Linux安装apache服务器的配置过程
Nov 27 Servers
Apache Linkis 中间件架构及快速安装步骤
Mar 16 Servers
Nginx工作模式及代理配置的使用细节
Mar 21 Servers
Apache Hudi的多版本清理服务彻底讲解
Mar 31 Servers
使用kubeadm命令行工具创建kubernetes集群
Mar 31 Servers
解决Windows Server2012 R2 无法安装 .NET Framework 3.5
Apr 29 Servers
Tomcat配置访问日志和线程数
May 06 Servers
Nginx HTTP跳转至HTTPS
May 15 Servers
Nginx如何限制IP访问只允许特定域名访问
Jul 23 Servers
Nginx如何配置根据路径转发详解
Jul 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
PHP中CURL方法curl_setopt()函数的参数分享
2013/01/19 PHP
php修改指定文件后缀的方法
2014/09/11 PHP
Prototype 1.5.0_rc1 及 Prototype 1.5.0 Pre0小抄本
2006/09/22 Javascript
js 数组的for循环到底应该怎么写?
2010/05/31 Javascript
javascript获取所有同类checkbox选项(实例代码)
2013/11/07 Javascript
Javascript call和apply区别及使用方法
2013/11/14 Javascript
javascript移动设备Web开发中对touch事件的封装实例
2014/06/05 Javascript
jQuery select表单提交省市区城市三级联动核心代码
2014/06/09 Javascript
node.js中的http.response.end方法使用说明
2014/12/14 Javascript
JavaScript中String.match()方法的使用详解
2015/06/06 Javascript
快速学习jQuery插件 Form表单插件使用方法
2015/12/01 Javascript
JavaScript深度复制(deep clone)的实现方法
2016/02/19 Javascript
jQuery remove()过滤被删除的元素(推荐)
2017/07/18 jQuery
30分钟精通React今年最劲爆的新特性——React Hooks
2019/03/11 Javascript
使用jQuery实现掷骰子游戏
2019/10/24 jQuery
微信小程序文章列表功能完整实例
2020/06/03 Javascript
js实现扫雷源代码
2020/11/27 Javascript
vue图片裁剪插件vue-cropper使用方法详解
2020/12/16 Vue.js
Python导入txt数据到mysql的方法
2015/04/08 Python
关于Tensorflow中的tf.train.batch函数的使用
2018/04/24 Python
Python判断telnet通不通的实例
2019/01/26 Python
Python3 导入上级目录中的模块实例
2019/02/16 Python
Python Numpy 自然数填充数组的实现
2019/11/28 Python
python装饰器的特性原理详解
2019/12/25 Python
详解python with 上下文管理器
2020/09/02 Python
详解python对象之间的交互
2020/09/29 Python
基于Django快速集成Echarts代码示例
2020/12/01 Python
商务助理岗位职责
2013/11/13 职场文书
普罗米修斯教学反思
2014/02/06 职场文书
药品营销专业毕业生自荐信
2014/07/02 职场文书
小学班主任事迹材料
2014/12/17 职场文书
应届生求职自荐信范文
2015/03/04 职场文书
房地产公司财务总监岗位职责
2015/04/03 职场文书
女性健康知识讲座主持词
2015/07/04 职场文书
婚宴致辞
2015/07/28 职场文书
浅谈Vue的computed计算属性
2022/03/21 Vue.js