利用C语言实现http服务器(Linux)

目录
  • 一、实习目的
  • 二、实习项目及内容
    • 2.1开发平台
    • 2.2项目功能
    • 2.3技能储备
  • 三、项目设计
    • 3.1设计概述
    • 3.2 Reactor模式
    • 3.3 socket网络编程
    • 3.4 http服务器应答报文设计
  • 四、代码实现及运行结果
    • 4.1主要功能实现
    • 4.2测试及运行结果

这篇文章是我的生产实习报告,在Linux操作系统上实现的一个简单的HTTP服务器,也算是一个小项目。请大家多多指教。

一、实习目的

本次实习紧紧围绕Linux操作系统基础知识展开,主要学习了Linux系统的常用命令、gcc编译链接过程、多线程通信和同步技术、socket网络通信、HTTP服务器等内容。与此同时,在老师的带领下进行实操训练,例如:编写Makefile文件管理工程、实现静态库和动态库、模仿系统bash实现自己的命令解释器、编写多线程程序并实现同步、实现TCP/UDP服务器端和客户端进行通信等。

最后通过独立完成一个基于Linux平台C语言编写的http服务器,巩固课程学到的Linux平台上的编程规范、技术和技巧,增强对于Linux操作系统的熟练度,培养我们编写较大型程序的能力,培养底层软件开发的能力,并为将来从事Linux平台开发、嵌入式开发等相对高端的软件开发工作打下基础。

本次实习具体目的如下:

(1)掌握并熟练使用Linux操作系统常用命令;

(2)熟练使用vim、gcc编译器、gdb等工具在Linux平台上进行程序的编写、编译以及调试;

(3)使用C语言编写轻量级http服务器实现发布静态页面功能;

(4)采用线程池和I/O复用方法实现同时处理多个客户端请求。

二、实习项目及内容

2.1开发平台

本项目是基于Linux系统C语言实现的http服务器,开发环境如下:

开发平台:腾讯云服务器

操作系统:Ubuntu Server 20.04 LTS 64bit

CPU:2核

内存:4GB

系统盘:60GB SSD云硬盘

2.2项目功能

本项目设计的http服务器是一个轻量级的服务器,使用Reactor模式,即主线程只负责监听文件描述符上是否有事件发生,有的话立即将该事件通知工作线程。除此之外,主线程不做其他实质性的工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。

本项目的基本功能如下:

(1)能接收客户端的GET请求;

(2)能够解析客户端的请求报文,根据客户端要求找到相应的资源;

(3)能够回复http应答报文;

(4)能够读取服务器中存储的文件,并返回给请求客户端,实现对外发布静态资源;

(5)使用I/O复用来提高处理请求的并发度;

(6)服务器端支持错误处理,如要访问的资源不存在时回复404错误等。

2.3技能储备

为了完成本项目,实现本项目的具体功能,需要具有一定的技能储备作为技术支撑。

首先应该掌握Linux操作系统的常用命令,C语言基础,熟练使用vim、gcc编译器、gdb等工具,Linux平台上进行程序的编写、编译以及调试能力,socket网络通信的编程能力,I/O复用理论知识以及编程能力,多线程编程能力,以及一定的HTML语言能力。

三、项目设计

3.1设计概述

本项目是基于Linux操作系统,使用C语言实现的轻量级http服务器。使用socket网络编程技术实现服务器端和客户端之间的通信。同时,为了提高本服务器的并发处理性能,本次http服务器设计使用Reactor模式。通过I/O复用和线程池相结合,实现同时响应多个客户端的请求,保证http服务器的并发性。

3.2 Reactor模式

Reactor模式是指主线程只负责监听文件描述符上是否有事件发生,有的话立即将该事件通知工作线程。除此之外,主线程不做其他实质性的工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。

工作流程如下:

(1)主线程往epoll内核事件表中注册socket上的读就绪事件。

(2)主线程调用epoll_wait等待socket上有数据可读。

(3)当socket上有数据可读时,epoll_wait 通知主线程。主线程则将socket可读事件放入消息队列。

(4)一旦放入消息队列便创建相应的线程即工作线程,在线程函数中处理客户端信息,然后往epoll内核事件表中注册该socket上的写就绪事件。

(5)主线程调用epoll_ wait 等待socket可写。

(6)当socket可写时,epoll _wait 通知主线程。主线程将socket可写事件放入消息队列。

(7)创建工作线程,往socket上写入服务器处理客户请求的结果。

3.3 socket网络编程

本项目通过socket网络编程技术实现http服务器端和客户端实现通信。并且采用的是TCP协议。

TCP 提供的是面向连接的、可靠的、字节流服务。TCP 的服务器端和客户端编程流程如下图:

3.4 http服务器应答报文设计

如果客户端请求响应成功,则想客户端发送成功应答报文。如下表所示:

表3-1 请求成功的应答报文

如果客户端请求响应失败,例如服务器端没有客户端所请求的资源,则回复失败报文。如下表所示:

表3-2 请求失败应答报文

四、代码实现及运行结果

4.1主要功能实现

4.1.1 主函数

int main()
{
    signal(SIGPIPE,sig_fun);
    sockfd = socket_init();//调用创建套接字函数
    if ( sockfd == -1 )
    {
        exit(0);
    }
    msgid = msgget((key_t)1234,IPC_CREAT|0600);//创建消息队列
    if ( msgid == -1 )
    {
        exit(0);
    }
    pthread_t id[4];
    for( int i = 0; i < 4; i++ )    //循环创建线程池
    {
        pthread_create(&id[i],NULL,loop_thread,NULL);
    }
    epfd = epoll_create(MAXFD);//创建内核事件表
    if ( epfd == -1 )
    {
        printf("create epoll err\n");
        exit(0);
}
epoll_add(epfd,sockfd);//调用封装的函数添加描述符和事件
  struct epoll_event evs[MAXFD];
    while( 1 )
    {
        int n = epoll_wait(epfd,evs,MAXFD,-1);//获取就绪描述符
        if( n == -1 )
        {
            continue;
        }
else
        {
            struct mess m;
            m.type = 1;
            for(int i = 0; i < n; i++ )
            {
                m.c = evs[i].data.fd;
                if ( evs[i].events & EPOLLIN )
                {
                    msgsnd(msgid,&m,sizeof(int),0); //向消息队列发送消息
                }
            }
        }
    }
}

主函数中主要调用各个封装好的方法函数,首先调用创建套接字函数,创建套接字,然后创建消息队列。接着创建线程池,子线程同时执行loop_thread线程函数,将在 msgrcv处阻塞,等待获取消息队列中的消息。主线程调用epoll_create方法创建内核事件表,调用epoll_add函数添加描述符和事件。接着使用epoll_wait方法获取就绪描述符,一旦获取到就绪描述符便向消息队列中发送消息,便可以解除子线程中消息队列的阻塞,执行子线程中的程序,连接客户端,实现通信。

4.1.2创建套接字函数

int socket_init()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if ( sockfd == -1 )
    {
        return -1;
    }
    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(80);
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
 if ( res == -1 )
    {
        printf("bind err\n");
        return -1;
    }
    res = listen(sockfd,5);
    if ( res == -1 )
    {
        return -1;
    }
    return sockfd;
}

将初始化创建套接字函数封装。

4.1.3线程函数

 void* loop_thread(void*  arg)
{
    while( 1 )
    {
        struct mess m;
        msgrcv(msgid,&m,sizeof(int),1,0);//从消息队列中读取消息
        int c = m.c;
        if ( c == sockfd )
        {
            struct sockaddr_in caddr;
            int len = sizeof(caddr);
            int cli = accept(sockfd,(struct sockaddr*)&caddr,&len);
            if ( cli < 0 )
            {
                continue;
            }
            epoll_add(epfd,cli);
        }
        else
        {
            char buff[1024] = {0};
            int n = recv(c,buff,1023,0);
             if ( n <= 0 )
            {
                epoll_del(epfd,c);//调用移除描述符函数
                close(c);
                printf("close\n");
                continue;
            }
            char* filename = get_filename(buff);//调用资源名获取函数
            if ( filename == NULL )
            {
                send_404status(c);//调用发送错误应答报文函数
                epoll_del(epfd,c);//调用移除描述符函数
                close(c);
                continue;
            }
            printf("filename:%s\n",filename);

            if ( send_httpfile(c,filename) == -1 )//调用发送正确应答报文函数
            {
                printf("主动关闭连接\n");
                epoll_del(epfd,c);
                close(c);
                continue;
            }
        }
        epoll_mod(epfd,c);//调用重置函数
    }
}

线程将在 msgrcv处阻塞,等待获取消息队列中的消息,判断描述符类型,进行accept操作或recv操作。收到客户端请求数据后再调用get_filename函数获取客户端请求的资源名称,再判断发送错误或者正确应答报文。

4.1.4封装epoll函数

//添加描述符函数
void epoll_add(int epfd,int fd)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN | EPOLLONESHOT;

    if ( epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
    {
        printf("epoll add err\n");
    }
}
//移除描述符函数
void epoll_del(int epfd, int fd )
{
    if ( epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1 )
    {
        printf("epoll del err\n");
    }
}
//重置描述符函数
void epoll_mod(int epfd, int fd)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN | EPOLLONESHOT;
    if ( epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev) == -1 )
    {
        printf("epoll mod err\n");
    }
}

4.1.5获取资源名函数

char* get_filename(char buff[])
{   char* ptr = NULL;
    char * s = strtok_r(buff," ",&ptr);
    if ( s == NULL )
    {
        printf("请求报文错误\n");
        return NULL;
    }
    printf("请求方法:%s\n",s);
    s = strtok_r(NULL," ",&ptr);
    if ( s == NULL )
    {
        printf("请求报文 无资源名字\n");
        return NULL;
    }
    if ( strcmp(s,"/") == 0 )
    {
        return "/index.html";
    }
    return s;
}

通过这个函数来解析客户端请求报文,获取资源名称。

4.1.6发送正确应答报文函数

int  send_httpfile(int c, char* filename)
{
    if ( filename == NULL || c < 0 )
    {
        send(c,"err",3,0);
        return -1 ;
    }

    char path[128] = {PATH};
    strcat(path,filename);//  /home/ubuntu/ligong/day12/index.hmtl
    int fd = open(path,O_RDONLY);
    if ( fd == -1 )
    {
        //send(c,"404",3,0);
        send_404status(c);
        return -1;
    }

    int size = lseek(fd,0,SEEK_END);
    lseek(fd,0,SEEK_SET);
    char head_buff[512] = {"HTTP/1.1 200 OK\r\n"};
    strcat(head_buff,"Server: myhttp\r\n");
    sprintf(head_buff+strlen(head_buff),"Content-Length: %d\r\n",size);
    strcat(head_buff,"\r\n");//分隔报头和数据 空行
    send(c,head_buff,strlen(head_buff),0);
    printf("send file:\n%s\n",head_buff);

    int num = 0;
    char data[1024] = {0};
    while( ( num = read(fd,data,1024)) > 0 )
    {
        send(c,data,num,0);
    }
    close(fd);

    return 0;
}

如果客户请求资源可以正常访问,则调用该函数发送应答报文。

4.1.7发送错误应答报文函数

int  send_404status(int c)
{
    int fd = open("err404.html",O_RDONLY);
    if ( fd == -1 )
    {
        send(c,"404",3,0);
        return 0;
    }

    int size = lseek(fd,0,SEEK_END);
    lseek(fd,0,SEEK_SET);
    char head_buff[512] = {"HTTP/1.1 404 Not Found\r\n"};
    strcat(head_buff,"Server: myhttp\r\n");
    sprintf(head_buff+strlen(head_buff),"Content-Length: %d\r\n",size);
    strcat(head_buff,"\r\n");//分隔报头和数据 空行
    send(c,head_buff,strlen(head_buff),0);

    char data[1024] = {0};
    int num = 0;
    while( ( num = read(fd,data,1024)) > 0 )
    {
        send(c,data,num,0);
    }
    close(fd);
    return 0;
}

如果客户端访问的在服务器端资源不存在,则调用该函数发送应答报文。

4.1.8 index.htlm

<html>
     <head>
         <meta charset=utf8>
          <title>baixingyu</title>
          </head>
      <body background="R-C.jpg">
             <center>
                 <h2>bxy</h2>
                 </center>
                <a href="test.html">下一页</a>
                </body>
      </html>

4.1.9 test.html

 <html>
      <head>
         <meta charset=utf8>
         <title>测试</title>
          </head>
         <body>
             <center>
                 <h2>小狗小狗
                 </center>
                   <a href="index.html">返回</a>
              </body>
 </html>

4.1.10 404err.html

 <html>
     <head>
          <meta charset=utf8>
          <title>访问失败</title>
          </head>
          <body background="1.jpg">
              <center>
                  <h2>页面走丢了
                  </center>
              </body>
</html>

4.2测试及运行结果

为了测试http服务器是否能够正常运行,并且实现上文提到的功能,分别采用了PC端和移动手机端进行网页测试。

本次测试用例及预期结果如下表所示:

表4-1 测试用例及结果

首先PC端在浏览器地址栏输入服务器所在的IP地址进行访问,可以成功获取到服务器端的index页面,如图所示。

点击下一页,跳转到test页面。如图所示:

在访问地址后随意追加错误访问信息,即访问客户端不存在的资源,得到404err页面。如图所示:

访问客户端存在的资源,读取到的相应的资源。例如输入1.116.157.150\3.jpg或2.jpg。得到图片内容,如图所示:

移动手机端测试同理,同样可以得到相应的结果如下图所示:

最后,为测试http服务器端的并发性,同时使用多个客户端进行连接,同时访问服务器端的资源,均可正常运行。服务器端打印的部分请求信息如下图:

通过测试结果显示,本http服务器实现了对外发布的静态资源的功能,并且对于错误的访问信息可以进行处理回复。并且,通过多用户同时访问测试结果显示,该服务器具有较好的并发性,能够满足一定客户端同时请求资源的需求。

到此这篇关于利用C语言实现http服务器(Linux)的文章就介绍到这了,更多相关C语言 http服务器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 如何利用C语言实现最简单的HTTP服务器详解

    此段代码的特点 <h1>Hello!</h1> 如何编译运行? 编译: gcc -o hello_server hello_server.c 运行: ./hello_server 请求: curl http://localhost:8888/any 源文件 hello_server.c #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/so

  • C语言多线程服务器的实现实例

    本文基于 C 标准库提供的网络通信 API,使用 TCP ,实现一个简单的多线程服务器 Demo . 首先要看 API API 字节序转换 函数原型: #include <arpa/inet.h> uint64_t htonll(uint64_t hostlonglong); uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint64_t ntohll(uint64_t netlonglong);

  • c语言实现http下载器的方法

    一.介绍 最近做ota升级需要用到http下载,所以写了一下http下载器 实现流程 1.解析url网址的域名和文件名 2.获取ip地址 3.构建http请求头发送到服务器 4.解析回复 5.下载文件 环境ubuntu linux c语言 开源链接 main.c #include <stdio.h> #include "http_download.h" int main(int argc, char const *argv[]) { if (argc == 1) { pri

  • c语言多进程tcp服务器示例

    server.h 复制代码 代码如下: #ifndef SERVER_H#define SERVER_H#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <arpa/inet.h>#include <as

  • C语言实现简单回声服务器

    本文实例为大家分享了C语言实现简单的回声服务器,供大家参考,具体内容如下 新建echo_server.c #include<stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <string.h> #include <ctype.h> #include <arpa/inet.h> #define SERVE

  • 利用C语言实现http服务器(Linux)

    目录 一.实习目的 二.实习项目及内容 2.1开发平台 2.2项目功能 2.3技能储备 三.项目设计 3.1设计概述 3.2 Reactor模式 3.3 socket网络编程 3.4 http服务器应答报文设计 四.代码实现及运行结果 4.1主要功能实现 4.2测试及运行结果 这篇文章是我的生产实习报告,在Linux操作系统上实现的一个简单的HTTP服务器,也算是一个小项目.请大家多多指教. 一.实习目的 本次实习紧紧围绕Linux操作系统基础知识展开,主要学习了Linux系统的常用命令.gcc

  • C#控制台程序如何发布到服务器Linux上运行

    目录 1.创建控制台程序 2.创建TCP服务端程序 3.利用vs发布程序 4.Ubuntu服务器安装.Net环境 5.解压运行tcp服务端程序 6.服务器后台运行程序 总结 1.创建控制台程序 如上图所示,选择linux开发平台,我用的VS2019,.Net5.0,一直点下一步,创建. 2.创建TCP服务端程序 using LinuxTcpApp.TcpServer; using System; using System.Net; namespace LinuxTcpApp { class Pr

  • Linux中利用c语言删除某个目录下的文件

    利用c语言删除目录下文件 最近这段时间工作内容是关于Linux下的简单文件操作,以前对于Linux系统下的文件操作函数都不是太熟悉,经过这次实践,对这些函数使用有了一定的了解 如何创建文件,读写文件,这些简单的我想大家应该是比较熟悉的,我所介绍的是如何遍历某个目录,并且删除该目录下的文件(可以指定后缀名),并且也可以指定 文件的修改时间范围(多少小时以前的旧文件可以删除),下面就是简单的函数实现,仅供初学者参考(毕竟我也是初学者\(^o^)/~) #include <stdio.h> #inc

  • 利用 Go 语言编写一个简单的 WebSocket 推送服务

    本文中代码可以在 github.com/alfred-zhong/wserver获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息都是通过短信,微信和 App 推送给用户的,现在要让登录用户在网页端也能实时接收到报警推送. 依稀记得以前工作的时候遇到过类似的需求.因为以前的浏览器标准比较陈旧,并且那时用 Java 较多,所以那时候解决这个问题就用了 Comet4J.具体的原理就是长轮询,长链接.但现在毕竟 html5 流行开来了,IE 都被 Edge 接替了,再用以前这种技术就显得过

  • 利用nginx搭建静态资源服务器的方法步骤

    以windows为例,linux其实一样: 搭建静态资源服务器 我电脑上的work文件夹下面有很多图片,我想通过nginx搭建静态资源服务器,通过在地址栏输入ip+port的方式完成目录的映射 找到nginx安装目录,打开/conf/nginx.conf配置文件,添加一个虚拟主机 添加监听端口.访问域名 重点是添加location, 映射-URL:/work/; 注意:如果当前server模块中已有一个location且URL为"/",那么新建的location的url应为匹配路径,不

  • 教你利用R语言测试电脑的性能

    利用R语言测试电脑的性能如何 同事新配了一个电脑,想用R语言编写一个程序,看一下电脑性能如何,让我写个代码测试一下. 我能怎么样,我也不懂如何测试电脑啊,那就计算一下矩阵的运算吧.因为我理解的电脑运行性能就是矩阵计算了. 编写代码 rm(list=ls()) set.seed(123) # 设置矩阵的行数 n = 10000 # 生成一个矩阵 value = rnorm(n*n, 10,3) mat = matrix(value,n,n) # 测试电脑性能 system.time({ # 矩阵求

  • 利用C语言实现经典多级时间轮定时器

    目录 1. 序言 2. 多级时间轮实现框架 2.1 多级时间轮对象 2.2 时间轮对象 2.3 定时任务对象 2.4 双向链表 2.5 联结方式 3. 多级时间轮C语言实现 3.1 双向链表头文件: list.h 3.2 调试信息头文件: log.h 3.3 时间轮代码: timewheel.c 3.4 编译运行 总结 1. 序言 最近一直在找时间轮的C语言实现代码,发现很多都是Java或者c++实现的.而我对其他语言不熟悉,看不太懂.关于C实现的,让我如沐春风的实现没找到,github上也只找

  • 利用Go语言实现流量回放工具的示例代码

    目录 前言 goreplay介绍与安装 使用示例 流量放大.缩小 流量写入到ElastichSearch goreplay基本实现原理 总结 前言 哈喽,大家好,我是asong. 今天给大家推荐一款使用Go语言编写的流量回放工具 -- goreplay:工作中你一定遇到过需要在服务器上抓包的场景,有了这个工具就可以助你一臂之力,goreplay的功能十分强大,支持流量的放大.缩小,并且集成了ElasticSearch,将流量存入ES进行实时分析: 废话不多,我们接下来来看一看这个工具: gore

  • 利用Go语言实现轻量级OpenLdap弱密码检测工具

    目录 1.Go连接LDAP服务 2.下载 3.准备LDAP环境 4.GO-LDAP案例实践 创建用户 遍历用户 删除账号 弱密码检查 1.Go连接LDAP服务 通过go操作的ldap,这里使用到的是go-ldap包,该包基本上实现了ldap v3的基本功能. 比如连接ldap服务.新增.删除.修改用户信息等,支持条件检索的ldap库中存储的数据信息. 2.下载 go get github.com/go-ldap/ldap/v3 go get github.com/wxnacy/wgo/array

  • 利用Go语言实现在终端绘制小兔子

    目录 前言 创作过程 小兔子模型制作 实现思路 代码 小结 前言 思来想去,使用 Go 语言创作,没有想到好的创意,最后打算在终端动态打印小兔子,给大家拜年! 先来看看效果图: 创作过程 小兔子模型制作 大部分的时间,都花在了画兔子图案的工作上.创建一个 txt 文档,在文档里通过特殊符号,一步一步将模型搭好,然后附上一副春节对联. 实现思路 整个动态过程是由几个不同的图案依次打印而成,一共有 7 个图案.起初我是将这 7 个图案放到 txt 文件里,然后通过读取文件,分割图案,最后打印图案,这

随机推荐