IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> C语言简易版webserver -> 正文阅读

[系统运维]C语言简易版webserver

简单的用C语言版本实现的webserver

摘要

本项目是参考黑马Linux网络编程教学的C语言版本webserver服务器。本项目是BS模式,实现了浏览器发来对服务端上某个资源的请求 (http报文),服务端(即我们实现的webserver)返回给浏览器响应报文以及所请求的资源(普通文件或者目录文件)。

技术要点

  1. 多路IO复用技术 epoll
  2. http请求与应答协议
  3. TCP协议:三次握手四次挥手,连接建立完成后进行数据传输
  4. web服务器:解析浏览器发来的请求数据,得到请求文件名
  5. 目录访问功能
  6. 访问中文目录的问题

BS模式示意图(功能)

在这里插入图片描述

程序的执行流程图

在这里插入图片描述

具体实现

创建socket并绑定端口号

这里我们将创建监听文件描述符lfd和绑定端口号和ip的操作封装到一个函数中去

int tcp4bind(short port,const char *IP)
{
    struct sockaddr_in serv_addr;
    int lfd = Socket(AF_INET,SOCK_STREAM,0); //创建监听文件描述符
    bzero(&serv_addr,sizeof(serv_addr));//清空serv_addr地址 对比 memset()
    if(IP == NULL){
        //如果这样使用 0.0.0.0,任意ip将可以连接
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    }else{
        if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){
            perror(IP);//转换失败
            exit(1);
        }
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port   = htons(port);//主机字节序转网络字节序(大端序)
    int opt = 1;
 	//设置端口复用
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    //绑定
    Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    return lfd;
}

调用该函数,端口号设置为9999,IP为NULL,则使用INADDR_ANY,服务器监听0.0.0.0创建socket,无论使用127.0.0.1或本机ip都可以建立tcp连接

 int lfd = tcp4bind(9999, NULL);

设置监听

我们也将listen封装为一个函数,内部带有错误判断

//设置监听,监听队列长度为128
Listen(lfd, 128); //文末wrap.c中给出

创建epoll树

int epfd = epoll_create(1024);
if(epfd < 0)
{
    perror("epoll create error");
    close(lfd);
    return  -1;
}

将监听文件描述符lfd上epoll树

struct epoll_event ev;
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

在循环中调用epoll_wait循环等待事件发生

有事件发生,内核会将数据写入epoll_event数组中,并且根据对应描述符类型来进行相应的处理:

int i = 0;
int nready;
int cfd;
int sockfd;
struct epoll_event events[1024]; 
while(1)
    {
        //等待事件发生,参数-1表示阻塞
        nready = epoll_wait(epfd, events, 1024, -1);
        if(nready < 0)
        {
            //如果是中断信号,不认为是错误
            if (errno == EINTR)
            {
                continue;
            }
            break;
        }
        for(i = 0;i < nready; ++i)
        {
            sockfd = events[i].data.fd;
            //有客户端连接请求
            if(sockfd == lfd)
            {
                //接受新的客户端连接
                cfd = Accept(lfd, NULL, NULL);

                //设置cfd为非阻塞
                int flag = fcntl(cfd, F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(cfd, F_SETFL, flag);

                //将新获得的cfd上树
                ev.data.fd = cfd;
                ev.events = EPOLLIN;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);

            }
            else //有客户端数据发来
            {
                http_request(sockfd, epfd); //解析请求报文
            }
        }
    }

处理浏览器发来的请求http报文(httprequest函数)

前置知识:http报文
  • http请求消息报文

在这里插入图片描述

  • http响应消息报文

在这里插入图片描述

思路分析

处理浏览器发来的请求报文可分为如下几步:

  1. 读取请求行数据,解析出请求的资源文件名
  2. 循环读取完剩余数据
  3. 判断文件是否存在
    1. 不存在:返回http响应消息+错误页内容
    2. 存在:
      1. 普通文件
      2. 目录文件
读取请求数据并解析
int n;
char buf[1024];
//读取请求行,切分出请求的文件名
memset(buf, 0x00, sizeof(buf));
//返回读到的字节数
n = Readline(cfd, buf, sizeof(buf));
if(n <= 0) //非阻塞模式下返回-1表示已经读到没东西了。
{
    //printf("read error or client closed, n = [%d]\n", n);
    //读异常或者客户端断开连接
    close(cfd);
    epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
    return -1;
}
//GET /hanzi.c HTTP/1.1
printf("buf = [%s]\n", buf);
char reqType[16] = {0};
char fileName[255] = {0};
char protocol[16] = {0};

//解析出请求类型,文件名和协议
//[^ ]表示匹配不是空格的任意字符串
sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocol)

char *pFile = fileName; // 必须先赋值,防止后面strcpy操作野指针
//服务器输入:http://192.168.200.150:9000或者http://192.168.200.150:9000/
//得到的请求行为 GET / HTTP/1.1
//要能够够识别出是去当前资源的根目录下
if(strlen(fileName) <= 1)
{
    strcpy(pFile, "./");
}
else
{
    //这里FileName截取为/hanzi.c,要去掉/
    pFile = fileName + 1;
}

//转换汉字编码(就是名字为%xx%xx之类的字符串)
strdecode(pFile, pFile);
循环读完剩余数据

目的是为了防止粘包影响到下一个客户端发来的请求的数据解读,由于内部调用了read,会产生阻塞,故要将cfd设置为非阻塞

while((n = Readline(cfd, buf, sizeof(buf))) > 0);
发送响应报文状态行和头部
//code:状态码
//msg:状态码对应信息(如OK)
//fileType:文件类型(如Content-Type:text/plain;char....)
//len:数据长度(Content-Length)
int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{
    char buf[1024] = {0};
    
    sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);
    //往buf+strlen(buf)指向的内存区域开始,把后面的内容写入进去,
    sprintf(buf + strlen(buf), "Content-Type:%s\r\n", fileType);
    if(len > 0)
    {
        sprintf(buf + strlen(buf), "Content-Length:%d\r\n", len);
    }
    strcat(buf, "\r\n");
    Write(cfd, buf, strlen(buf));
    return 0;
}
发送响应报文消息正文
int send_file(int cfd, char *fileName)
{
    //打开文件
    int fd = open(fileName, O_RDONLY);
    if(fd < 0)
    {
        perror("open error");
        return -1;
    }

    //循环读文件然后发送
    char buf[1024];
    int n;
    while(1)
    {
        memset(buf, 0x00, sizeof(buf));
        n = read(fd, buf, sizeof(buf));

        //小于0读异常,等于0表示到文件尾
        if(n <= 0)
        {
            break;
        }
        else
        {
            Write(cfd, buf, n);
        }
    }
}
判断文件是否存在
struct stat st;
stat(pFile, &st); // 小于0为不存在,反之存在
当文件不存在时处理

组织应答消息:http响应消息+错误页:

printf("file not exist\n");

//组织应答消息:http响应消息+错误页
send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);

send_file(cfd, "error.html");
当文件存在且为普通文件

为普通文件的时候,下方get_mime_type()通过文件名得到文件类型,结尾会给出实现:

if(S_ISREG(st.st_mode))
{
    printf("file exist\n");
    //发送状态行和头部信息
    send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
    //发送文件主体内容
    send_file(cfd, pFile);
}
文件存在且为目录文件

如果是目录文件我们除了要返回状态行和消息报头,我们返回的响应主体应该是一个html页面显示目录下的文件(如下图),所以要进行拼接生成一个html文件,上面是html头,中间是目录的内容(需要程序实现,没法写死)然后加上html的尾部。

在这里插入图片描述

发送html上半部分dir_header.html

<html><head><title>Index of ./</title></head>
<body bgcolor="#99cc99"><h4>Index of ./</h4>
<ul type=circle>

发送html中我们程序实现的目录显示的部分,注意**如果是目录的话href值的结尾要加上/,**下面的代码中要重点留意,如果不加的话访问内层文件的时候会出错。

<li><a href=苦瓜.txt> 苦瓜.txt </a></li>
<li><a href=aa/> aa </a></li>

发送html的结尾部分dir_tail.html

</ul>
<address><a href="http://www.baidu.com/">xhttpd</a></address>
</body></html>

代码的实现如下:

else if(S_ISDIR(st.st_mode))//目录文件
{
    //目录文件用html来呈现其中内容
    printf("目录文件\n");

    //发送头部信息
    send_header(cfd, "200", "OK", get_mime_type(".html"), 0);

    //发送html文件头部
    send_file(cfd, "html/dir_header.html");

    //文件列表信息(存放struct dirent* 元素的数组)
    struct dirent **namelist;
    int num;
    char buffer[1024];
    
    //从目录pFile中读出目录中的文件信息写入到namelist数组中
    //返回目录中文件的数量
    num = scandir(pFile, &namelist, NULL, alphasort);
    if (num < 0) 
    {
        perror("scandir");
        close(cfd);
        //文件描述符对应的事件下树
        epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
        return -1;
    }
    else 
    {
        while (num--) 
        {
            printf("%s\n", namelist[num]->d_name);
            //一条一条的目录发送
            memset(buffer, 0x00,sizeof(buf));
            if(namelist[num]->d_type == DT_DIR) //目录,href值尾要加/
            {
                sprintf(buffer, "<li><a href=%s/> %s </a></li>", namelist[num]->d_name, namelist[num]->d_name);
            }
            else //普通文件
            {
                sprintf(buffer, "<li><a href=%s> %s </a></li>", namelist[num]->d_name, namelist[num]->d_name);
            }
            free(namelist[num]);
            Write(cfd, buffer, strlen(buffer));
        }
        free(namelist);
    }   

    //sleep(10);
    //发送html尾部
    send_file(cfd, "html/dir_tail.html");

}

杂项

  • 更改进程当前工作目录,让程序在找文件的时候到指定的目录下搜寻:

    char path[255] = {0};
    //getenv("HOME")获取环境变量HOME的值
    sprintf(path, "%s/%s", getenv("HOME"), "webpath");
    chdir(path);
    

    在这里插入图片描述

  • 无法访问中文文件的问题解决

    当我们点击一个名字为中文的文件的时所接受到的文件名是UTF8编码,如点击苦瓜.txt时,我们接受到的请求报文中解析出的文件名是%隔开的utf8编码的字符串形式:

    在这里插入图片描述

    在这里插入图片描述

    苦瓜的utf8编码(十六进制)为 :苦:e8 8b a6 ;瓜:e7 93 9c

    注:中文的utf8编码占3个字节

    在这里插入图片描述

    解决这个问题的办法是:将每个中文的utf8编码(字符串格式存储的)转换成十进制存放到三个char变量(一个char变量占1B足够两位十六进制数)中:

    在这里插入图片描述

    代码实现如下:

    void strdecode(char *to, char *from)
    {
        for ( ; *from != '\0'; ++to, ++from) {
    
            if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) { //依次判断from中 %20 三个字符
    
                *to = hexit(from[1])*16 + hexit(from[2]);//字符串E8变成了真正的16进制的E8
                from += 2;                      //移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
            } else
                *to = *from;
        }
        *to = '\0';
    }
    
    //16进制数转化为10进制, return 0不会出现
    int hexit(char c)
    {
        if (c >= '0' && c <= '9')
            return c - '0';
        if (c >= 'a' && c <= 'f')
            return c - 'a' + 10;
        if (c >= 'A' && c <= 'F')
            return c - 'A' + 10;
    
        return 0;
    }
    
  • SIGPIPE信号问题(接收端断开而发送端仍发送数据–》管道破裂)

问题描述:**当服务端正在给客户端发送数据时,数据未发送完时,浏览器(客户端)就断开连接,**此时相当于管道读端关闭,故服务端会收到SIGPIPE信号

测试方法:发送目录文件时,**发送html尾部时sleep(10),主动关掉浏览器,**检查服务端程序发现程序已结束。

解决方法:由于这种情况即便浪费了资源,但我们不希望随便就使服务端程序停止,所以我们采用忽略信号的方法,在main函数开始注册信号处理函数:

int main()
{
    .....
    struct sigaction act;
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGPIPE, &act, NULL);
	.....
}

注意事项

  1. 当浏览器返回上一级的时候,实际上关闭了原来客户端的cfd,然后建立新的连接得到cfd来发送请求数据。

附录

webserver.c

//web服务端程序:使用epoll模型
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include "pub.h"
#include "wrap.h"

//处理客户端发来的请求报文
int http_request(int cfd, int epfd);

//发送响应报文的状态行和头部
int send_header(int cfd, char *code, char *msg, char *fileType, int len);

//发送响应报文的消息正文
int send_file(int cfd, char *fileName);


int main()
{
    //若web服务器给浏览器发送数据中客户端断开连接
    //web服务器就会收到SIGPIPE信号
    //忽略SIGPIPE信号
    //signal(SIGPIPE, SIG_IGN);

    struct sigaction act;
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGPIPE, &act, NULL);

    //改变当前工作目录,让其能访问到资源文件
    char path[255] = {0};
    //getenv("HOME")获取环境变量HOME的值
    sprintf(path, "%s/%s", getenv("HOME"), "webpath");
    chdir(path);

    //创建socket、设置端口复用、bind
    int lfd = tcp4bind(9999, NULL);

    //设置监听
    Listen(lfd, 128);

    //创建epoll树
    int epfd = epoll_create(1024);
    if(epfd < 0)
    {
        perror("epoll create error");
        close(lfd);
        return  -1;
    }

    //将监听文件描述符上树
    struct epoll_event ev;
    ev.data.fd = lfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);

    int i = 0;
    int nready;
    int cfd;
    int sockfd;
    struct epoll_event events[1024]; 
    while(1)
    {
        //等待事件发生,参数-1表示阻塞
        nready = epoll_wait(epfd, events, 1024, -1);
        if(nready < 0)
        {
            //如果是中断信号,不认为是错误
            if (errno == EINTR)
            {
                continue;
            }
            break;
        }
        for(i = 0;i < nready; ++i)
        {
            sockfd = events[i].data.fd;
            //有客户端连接请求
            if(sockfd == lfd)
            {
                //接受新的客户端连接
                cfd = Accept(lfd, NULL, NULL);

                //设置cfd为非阻塞
                int flag = fcntl(cfd, F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(cfd, F_SETFL, flag);

                //将新获得的cfd上树
                ev.data.fd = cfd;
                ev.events = EPOLLIN;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);

            }
            else //有客户端数据发来
            {
                http_request(sockfd, epfd);
            }
        }
    }
} 


int http_request(int cfd, int epfd)
{
    int n;
    char buf[1024];
    //读取请求行,切分出请求的文件名
    memset(buf, 0x00, sizeof(buf));
    //返回读到的字节数
    n = Readline(cfd, buf, sizeof(buf));
    if(n <= 0) //非阻塞模式下返回-1表示已经读到没东西了。
    {
        //printf("read error or client closed, n = [%d]\n", n);
        //读异常或者客户端断开连接
        close(cfd);
        epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
        return -1;
    }
    //GET /hanzi.c HTTP/1.1
    printf("buf = [%s]\n", buf);
    char reqType[16] = {0};
    char fileName[255] = {0};
    char protocol[16] = {0};
    
    sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocol);
    //printf("[%s]\n", reqType);
    printf("[%s]\n", fileName);
    //printf("[%s]\n", protocol);
    
    char *pFile = fileName; // 必须先赋值,防止后面strcpy操作野指针
    //服务器输入:http://192.168.200.150:9000或者http://192.168.200.150:9000/
    //要能够够识别出是去当前资源的根目录下
    if(strlen(fileName) <= 1)
    {
        strcpy(pFile, "./");
    }
    else
    {
        //这里FileName截取为/hanzi.c,要去掉/
        pFile = fileName + 1;
    }

    //转换汉字编码(就是名字为%xx%xx之类的字符串)
    strdecode(pFile, pFile);

    //循环读取完剩余的数据,防止粘包影响到下一个客户端发来的请求的数据解读
    //由于内部调用了read,会产生阻塞,故要将cfd设置为非阻塞
    while((n = Readline(cfd, buf, sizeof(buf))) > 0);

    //判断文件是否存在
    struct stat st;
    //stat(fileName, &st) 通过文件名fileName获取文件信息存放到st中
    //若文件不存在:  
    if(stat(pFile, &st) < 0)
    {
        printf("file not exist\n");

        //组织应答消息:http响应消息+错误页
        send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);

        send_file(cfd, "error.html");
    }    
    else//若文件存在:
    {
        //普通文件
        if(S_ISREG(st.st_mode))
        {
            printf("file exist\n");
            //发送状态行和头部信息
            send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
            //发送文件主体内容
            send_file(cfd, pFile);
        }
        else if(S_ISDIR(st.st_mode))//目录文件
        {
            //目录文件用html来呈现其中内容
            printf("目录文件\n");
            
            //发送头部信息
            send_header(cfd, "200", "OK", get_mime_type(".html"), 0);

            //发送html文件头部
            send_file(cfd, "html/dir_header.html");

            //文件列表信息
            struct dirent **namelist;
            int num;
            char buffer[1024];
            num = scandir(pFile, &namelist, NULL, alphasort);
            if (num < 0) 
            {
                perror("scandir");
                close(cfd);
                //文件描述符对应的事件下树
                epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
                return -1;
            }
            else 
            {
                while (num--) 
                {
                    printf("%s\n", namelist[num]->d_name);
                    //一条一条的目录发送
                    memset(buffer, 0x00,sizeof(buf));
                    if(namelist[num]->d_type == DT_DIR) //目录
                    {
                        sprintf(buffer, "<li><a href=%s/> %s </a></li>", namelist[num]->d_name, namelist[num]->d_name);
                    }
                    else //普通文件
                    {
                        sprintf(buffer, "<li><a href=%s> %s </a></li>", namelist[num]->d_name, namelist[num]->d_name);
                    }
                    free(namelist[num]);
                    Write(cfd, buffer, strlen(buffer));
                }
                free(namelist);
            }   

            //sleep(10);
            //发送html尾部
            send_file(cfd, "html/dir_tail.html");
            
        }
            
    }         
}


//code:状态码
//msg:状态码对应信息(如OK)
//fileType:文件类型(如Content-Type:text/plain;char....)
//len:数据长度(Content-Length)
int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{
    char buf[1024] = {0};
    
    sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);
    //往buf+strlen(buf)指向的内存区域开始,把后面的内容写入进去,
    sprintf(buf + strlen(buf), "Content-Type:%s\r\n", fileType);
    if(len > 0)
    {
        sprintf(buf + strlen(buf), "Content-Length:%d\r\n", len);
    }
    strcat(buf, "\r\n");
    Write(cfd, buf, strlen(buf));
    return 0;
}

int send_file(int cfd, char *fileName)
{
    //打开文件
    int fd = open(fileName, O_RDONLY);
    if(fd < 0)
    {
        perror("open error");
        return -1;
    }

    //循环读文件然后发送
    char buf[1024];
    int n;
    while(1)
    {
        memset(buf, 0x00, sizeof(buf));
        n = read(fd, buf, sizeof(buf));

        if(n <= 0)
        {
            break;
        }
        else
        {
            Write(cfd, buf, n);
        }
    }
}

pub.c

#include "pub.h"
//通过文件名字获得文件类型
char *get_mime_type(char *name)
{
    char* dot;

    dot = strrchr(name, '.');	//自右向左查找‘.’字符;如不存在返回NULL
    /*
     *charset=iso-8859-1	西欧的编码,说明网站采用的编码是英文;
     *charset=gb2312		说明网站采用的编码是简体中文;
     *charset=utf-8			代表世界通用的语言编码;
     *						可以用到中文、韩文、日文等世界上所有语言编码上
     *charset=euc-kr		说明网站采用的编码是韩文;
     *charset=big5			说明网站采用的编码是繁体中文;
     *
     *以下是依据传递进来的文件名,使用后缀判断是何种文件类型
     *将对应的文件类型按照http定义的关键字发送回去
     */
    if (dot == (char*)0)
        return "text/plain; charset=utf-8";
    if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
        return "text/html; charset=utf-8";
    if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
        return "image/jpeg";
    if (strcmp(dot, ".gif") == 0)
        return "image/gif";
    if (strcmp(dot, ".png") == 0)
        return "image/png";
    if (strcmp(dot, ".css") == 0)
        return "text/css";
    if (strcmp(dot, ".au") == 0)
        return "audio/basic";
    if (strcmp( dot, ".wav") == 0)
        return "audio/wav";
    if (strcmp(dot, ".avi") == 0)
        return "video/x-msvideo";
    if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
        return "video/quicktime";
    if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
        return "video/mpeg";
    if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
        return "model/vrml";
    if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
        return "audio/midi";
    if (strcmp(dot, ".mp3") == 0)
        return "audio/mpeg";
    if (strcmp(dot, ".ogg") == 0)
        return "application/ogg";
    if (strcmp(dot, ".pac") == 0)
        return "application/x-ns-proxy-autoconfig";

    return "text/plain; charset=utf-8";
}
/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,
 * carriage return, or a CRLF combination.  Terminates the string read
 * with a null character.  If no newline indicator is found before the
 * end of the buffer, the string is terminated with a null.  If any of
 * the above three line terminators is read, the last character of the
 * string will be a linefeed and the string will be terminated with a
 * null character.
 * Parameters: the socket descriptor
 *             the buffer to save the data in
 *             the size of the buffer
 * Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
//获得一行数据,每行以\r\n作为结束标记
int get_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;

    while ((i < size - 1) && (c != '\n'))
    {
        n = recv(sock, &c, 1, 0);
        /* DEBUG printf("%02X\n", c); */
        if (n > 0)
        {
            if (c == '\r')
            {
                n = recv(sock, &c, 1, MSG_PEEK);//MSG_PEEK 从缓冲区读数据,但是数据不从缓冲区清除
                /* DEBUG printf("%02X\n", c); */
                if ((n > 0) && (c == '\n'))
                    recv(sock, &c, 1, 0);
                else
                    c = '\n';
            }
            buf[i] = c;
            i++;
        }
        else
            c = '\n';
    }
    buf[i] = '\0';

    return(i);
}

//下面的函数第二天使用
/*
 * 这里的内容是处理%20之类的东西!是"解码"过程。
 * %20 URL编码中的‘ ’(space)
 * %21 '!' %22 '"' %23 '#' %24 '$'
 * %25 '%' %26 '&' %27 ''' %28 '('......
 * 相关知识html中的‘ ’(space)是&nbsp
 */
void strdecode(char *to, char *from)
{
    for ( ; *from != '\0'; ++to, ++from) {

        if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) { //依次判断from中 %20 三个字符

            *to = hexit(from[1])*16 + hexit(from[2]);//字符串E8变成了真正的16进制的E8
            from += 2;                      //移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
        } else
            *to = *from;
    }
    *to = '\0';
}

//16进制数转化为10进制, return 0不会出现
int hexit(char c)
{
    if (c >= '0' && c <= '9')
        return c - '0';
    if (c >= 'a' && c <= 'f')
        return c - 'a' + 10;
    if (c >= 'A' && c <= 'F')
        return c - 'A' + 10;

    return 0;
}

//"编码",用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
//strencode(encoded_name, sizeof(encoded_name), name);
void strencode(char* to, size_t tosize, const char* from)
{
    int tolen;

    for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
        if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {
            *to = *from;
            ++to;
            ++tolen;
        } else {
            sprintf(to, "%%%02x", (int) *from & 0xff);
            to += 3;
            tolen += 3;
        }
    }
    *to = '\0';
}

pub.h

#ifndef _PUB_H
#define _PUB_H
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>
char *get_mime_type(char *name);
int get_line(int sock, char *buf, int size);
int hexit(char c);//16进制转10进制
void strencode(char* to, size_t tosize, const char* from);//编码
void strdecode(char *to, char *from);//解码
#endif


wrap.h

#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>

void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int tcp4bind(short port,const char *IP);
#endif

wrap.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
//绑定错误显示和退出
void perr_exit(const char *s)
{
	perror(s);
	exit(-1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int n;

again:
	if ((n = accept(fd, sa, salenptr)) < 0) {
		if ((errno == ECONNABORTED) || (errno == EINTR))//ECONNABORTED 代表连接失败 ETINTR 代表被信号打断
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}

int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = bind(fd, sa, salen)) < 0)
		perr_exit("bind error");

    return n;
}

int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = connect(fd, sa, salen)) < 0)
		perr_exit("connect error");

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

	if ((n = listen(fd, backlog)) < 0)
		perr_exit("listen error");

    return n;
}

int Socket(int family, int type, int protocol)
{
	int n;

	if ((n = socket(family, type, protocol)) < 0)
		perr_exit("socket error");

	return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ( (n = read(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)//被信号打断应该继续读
			goto again;
		else
			return -1;
	}
	return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ( (n = write(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

int Close(int fd)
{
    int n;
	if ((n = close(fd)) == -1)
		perr_exit("close error");

    return n;
}

/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{
	size_t  nleft;              //usigned int 剩余未读取的字节数
	ssize_t nread;              //int 实际读到的字节数
	char   *ptr;

	ptr = vptr;
	nleft = n;

	while (nleft > 0) {
		if ((nread = read(fd, ptr, nleft)) < 0) {
			if (errno == EINTR)
				nread = 0;
			else
				return -1;
		} else if (nread == 0)
			break;

		nleft -= nread;//防止一次数据没有读完
		ptr += nread;//指针需要向后移动
	}
	return n - nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	const char *ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}

		nleft -= nwritten;
		ptr += nwritten;
	}
	return n;
}

static ssize_t my_read(int fd, char *ptr)
{
	static int read_cnt;
	static char *read_ptr;
	static char read_buf[100];//定义了100的缓冲区

	if (read_cnt <= 0) {
again:
        //使用缓冲区可以避免多次从底层缓冲读取数据--为了提高效率
		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
			if (errno == EINTR)
				goto again;
			return -1;
		} else if (read_cnt == 0)
			return 0;
		read_ptr = read_buf;
	}
	read_cnt--;
	*ptr = *read_ptr++;//从缓冲区取数据

	return 1;
}
//读取一行
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t n, rc;
	char    c, *ptr;

	ptr = vptr;
	for (n = 1; n < maxlen; n++) {
		if ( (rc = my_read(fd, &c)) == 1) {
			*ptr++ = c;
			if (c  == '\n')//代表任务完成
				break;
		} else if (rc == 0) {//对端关闭
			*ptr = 0;//0 = '\0'
			return n - 1;
		} else
			return -1;
	}
	*ptr  = 0;

	return n;
}

int tcp4bind(short port,const char *IP)
{
    struct sockaddr_in serv_addr;
    int lfd = Socket(AF_INET,SOCK_STREAM,0);
    bzero(&serv_addr,sizeof(serv_addr));//清空serv_addr地址 对比 memset()
    if(IP == NULL){
        //如果这样使用 0.0.0.0,任意ip将可以连接
        serv_addr.sin_addr.s_addr = INADDR_ANY;
    }else{
        if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){
            perror(IP);//转换失败
            exit(1);
        }
    }
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port   = htons(port);
    int opt = 1;
    setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
    return lfd;
}


  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-06-14 22:57:06  更:2022-06-14 22:57:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 0:08:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码