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 相关文章推荐
Apache压力测试工具的安装使用
Mar 31 Servers
Nginx中break与last的区别详析
Mar 31 Servers
nginx基于域名,端口,不同IP的虚拟主机设置的实现
Mar 31 Servers
apache基于端口创建虚拟主机的示例
Apr 22 Servers
windows下快速安装nginx并配置开机自启动的方法
May 11 Servers
Nginx缓存设置案例详解
Sep 15 Servers
Windows server 2012 R2 安装IIS服务器
Apr 29 Servers
讨论nginx location 顺序问题
May 30 Servers
Windows Server 2022 超融合部署(图文教程)
Jun 25 Servers
nginx七层负载均衡配置详解
Jul 15 Servers
Nginx代理Redis哨兵主从配置的实现
Jul 15 Servers
django项目、vue项目部署云服务器的详细过程
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动态编译出现Cannot find autoconf的解决方法
2014/11/05 PHP
试用php中oci8扩展
2015/06/18 PHP
ThinkPHP实现转换数据库查询结果数据到对应类型的方法
2017/11/16 PHP
php用xpath解析html的代码实例讲解
2019/02/14 PHP
jQuery 获取对象 基本选择与层级
2010/05/31 Javascript
js字符串日期yyyy-MM-dd转化为date示例代码
2014/03/06 Javascript
jQuery实现的超酷苹果风格图标滑出菜单效果代码
2015/09/16 Javascript
JS获取文件大小方法小结
2015/12/08 Javascript
适用于javascript开发者的Processing.js入门教程
2016/02/24 Javascript
解决angularJS中input标签的ng-change事件无效问题
2018/09/13 Javascript
js实现秒表计时器
2019/12/16 Javascript
Vue+Node实现的商城用户管理功能示例
2019/12/23 Javascript
Python数据结构之Array用法实例
2014/10/09 Python
Python中用于转换字母为小写的lower()方法使用简介
2015/05/19 Python
python虚拟环境virtualenv的安装与使用
2017/09/21 Python
pandas DataFrame数据转为list的方法
2018/04/11 Python
在IPython中执行Python程序文件的示例
2018/11/01 Python
详解Python Matplot中文显示完美解决方案
2019/03/07 Python
python 图像处理画一个正弦函数代码实例
2019/09/10 Python
简单了解python shutil模块原理及使用方法
2020/04/28 Python
css3绘制百度的小度熊
2018/10/29 HTML / CSS
css3 position fixed固定居中问题解决方案
2014/08/19 HTML / CSS
CSS3实现自定义Checkbox特效实例代码
2017/04/24 HTML / CSS
GLAMGLOW香港官网:明星出镜前的秘密武器
2017/03/16 全球购物
Theflamel意大利:女士奢华服装、鞋子和配件
2020/01/11 全球购物
如何打开WebSphere远程debug
2014/10/10 面试题
旅游与酒店管理的自我评价分享
2013/11/03 职场文书
银行演讲稿范文
2014/01/03 职场文书
快递业务员岗位职责
2014/01/06 职场文书
运动会解说词200字
2014/02/06 职场文书
市场拓展计划书
2014/05/03 职场文书
2014五年级班主任工作总结
2014/12/05 职场文书
先进单位申报材料
2014/12/25 职场文书
2015年八一建军节演讲稿
2015/03/19 职场文书
国庆节新闻稿
2015/07/17 职场文书
2016年公共机构节能宣传周活动总结
2016/04/05 职场文书