4

Linux下搭建简易的HTTP服务器完成图片显示_3月月更_DS小龙哥_InfoQ写作平台

 2 years ago
source link: https://xie.infoq.cn/article/7807473439764590ad4dfe2e6
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Linux 下搭建简易的 HTTP 服务器完成图片显示

作者:DS小龙哥
  • 2022 年 3 月 31 日
  • 本文字数:3051 字

    阅读完需:约 10 分钟

这篇文章作为 Linux 下 socket(TCP)网络编程的练习,使用 C 语言代码搭建一个简单的 HTTP 服务器,完成与浏览器之间的交互,最终在浏览器上显示一张图片;通过这个例子可以巩固 socket 里多线程使用,也可以方便学习了解 HTTP 协议。

2. HTTP 协议介绍

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

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

3. HTTP 的消息结构

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

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

GET / HTTP/1.1Host: 10.0.0.6Connection: keep-aliveUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36Accept: 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.9Accept-Encoding: gzip, deflateAccept-Language: zh-CN,zh;q=0.9

HTTP 常用的请求是 GET POST

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 - 资源(网页等)被永久转移到其它 URL404 - 请求的资源(网页等)不存在 500 - 内部服务器错误

4. HTTP 交互流程

第一次请求是由 HTTP 客户端(浏览器)发起的,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 图片,作为资源文件,方便传递给浏览器,本地文件结构如下:

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. 最终运行的效果

划线
评论
复制
发布于: 2022 年 03 月 31 日阅读数: 676

版权声明: 本文为 InfoQ 作者【DS小龙哥】的原创文章。

原文链接:【https://xie.infoq.cn/article/7807473439764590ad4dfe2e6】。文章转载请联系作者。

用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022.01.06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK