C++初级项目webserver项目流程介绍(2)

news2024/11/15 10:49:44

一、引言

C++的webserver项目是自己在学完网络编程后根据网课的内容做的一个初级的网络编程项目。

这个项目的效果是可以在浏览器通过输入网络IP地址和端口,然后打开对应的文件目录

效果如下:

也可以打开文件夹后点击目录,打开到对应的文件夹中去。

这个就是简单的webserver功能,后期自己也可以修改代码实现更多可能性的玩法,比如做一个简单的前端交互式的界面。

二、代码开发流程

我这个项目主要用到的实现方式,是用epoll,epoll是可以实现网络服务器编程有下面几个优点

1. 高效:epoll使用事件驱动模型,只有当IO事件发生时才会被激活,避免了轮询的开销,提高了服务器的效率。

2. 可扩展:epoll支持较大的并发连接数,可以处理成千上万个连接,而且在连接数量增加时,性能下降较慢。

3. 高可靠性:epoll使用边缘触发模式,只有在数据可读或可写时才会通知应用程序,避免了因为网络拥塞等原因导致的误报,提高了服务器的可靠性。

4. 灵活性:epoll支持多种事件类型,包括读、写、异常等,可以根据不同的需求进行定制。

5. 跨平台:epoll是Linux系统内核提供的机制,可以在不同的Linux系统上使用,实现跨平台开发。

下面是epoll开发webserver项目的流程图(不包括具体函数的实现)

 

int main()
{
	 //若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, 
    //则web服务器就会收到SIGPIPE信号
    struct sigaction act;
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGPIPE, &act, NULL);
    
	int lfd = tcp4bind(9999,NULL);
	Listen(lfd,128);
    int epfd = epoll_create(1024);
    if(epfd < 0)
    {
    	perror("epoll_create error");
    	close(lfd);
    	return -1;
	}
	struct epoll_event ev;
	struct epoll_event events[1024];
    ev.data.fd = lfd;
    ev.events = EPOLLIN;
    
    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
    
    int nready;
    int i;
    int cfd;
    int sockfd;
    while(1)
    {
    	nready = epoll_wait(epfd,events,1024,-1);
    	if(nready < 0)
    	{
    		perror("epoll wait error");
    		if(nready == EINTR)
    		{
    			continue;
			}
			break;
		     
		}
		for(i = 0;i < nready;i ++)
		{
		    sockfd = events[i].data.fd;
		    if(sockfd == lfd)
		    {
		    	cfd = Accept(lfd,NULL,NULL);
		    	//设置cfd为非阻塞,防止其在 while((n = Readline(cfd,buf,sizeof(buf))) > 0)处阻塞 
		    	//设置cfd为非阻塞
                int flag = fcntl(cfd, F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(cfd, F_SETFL, flag);
                
		    	ev.data.fd = cfd;
		    	ev.events = EPOLLIN;
		    	epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
		    	
			}
			else {
				http_request(sockfd,epfd);
			}
		}
	}
}

上面的tcp4bind是封装好的函数,如果想看具体实现,可以看一下文章后面的全部代码wrap.c

代码。类似的Listen,Accept也是封装好的函数。

三、http_request 函数

这个函数是具体实现,打开文件和打开文件夹的函数。

当客户端发起HTTP请求时,服务器会调用http_request函数来处理请求。函数流程如下:

函数流程如下:

  1. 读取请求行数据,分析出要请求的资源文件名。
  2. 判断请求的文件是否存在,若不存在则发送404 NOT FOUND的头部信息和error.html文件内容。
  3. 若文件存在,判断文件类型,如果是普通文件则发送200 OK的头部信息和文件内容;如果是目录文件则发送200 OK的头部信息和目录文件列表信息的html内容。
  4. 发送完数据后关闭连接,并将文件描述符从epoll树上删除。

代码

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)
    {
        //printf("read error or client closed, n==[%d]\n", n);
        //关闭连接
        close(cfd);
         
        //将文件描述符从epoll树上删除
        epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
        return -1;  
    }
    printf("buf==[%s]\n", buf);
    //GET /hanzi.c HTTP/1.1
    char reqType[16] = {0};
    char fileName[255] = {0};
    char protocal[16] = {0};
    sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);
    //printf("[%s]\n", reqType);
    printf("--[%s]--\n", fileName);
    //printf("[%s]\n", protocal);
     
    char *pFile = fileName;
    if(strlen(fileName)<=1)
    {
        strcpy(pFile, "./");
    }
    else
    {
        pFile = fileName+1;
    }
     
    //转换汉字编码
    strdecode(pFile, pFile);
    printf("[%s]\n", pFile);
     
    //循环读取完剩余的数据,避免产生粘包
    while((n=Readline(cfd, buf, sizeof(buf)))>0);
     
    //判断文件是否存在
    struct stat st;
    if(stat(pFile, &st)<0)
    {
        printf("file not exist\n");
         
        //发送头部信息
        send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
         
        //发送文件内容
        send_file(cfd, "error.html");   
    }
    else //若文件存在
    {
        //判断文件类型
        //普通文件
        if(S_ISREG(st.st_mode))  //man 2 stat查询,S_ISREG表示普通文件        
        {
            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))
        {
            printf("目录文件\n");
             
            char buffer[1024];
            //发送头部信息
            send_header(cfd, "200", "OK", get_mime_type(".html"), 0);   
             
            //发送html文件头部
            send_file(cfd, "html/dir_header.html"); 
             
            //文件列表信息
            struct dirent **namelist;
            int num;
 
            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(buffer));
                   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);
            }
            //发送html尾部
            sleep(10);
            send_file(cfd, "html/dir_tail.html");       
        }
    }
     
    return 0;
}

四、细节

1.cfd要设置为非阻塞

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

这段代码的作用是将文件描述符cfd设置为非阻塞模式。

首先,使用fcntl函数和F_GETFL命令获取cfd的文件状态标志。这些标志包括文件的读写模式、是否阻塞等信息。获取后的标志保存在flag变量中。

接着,使用按位或运算符(|)将O_NONBLOCK标志(表示非阻塞模式)添加到flag变量中。这样做是为了将O_NONBLOCK标志添加到文件描述符的状态标志中,表示将该文件描述符设置为非阻塞模式。

最后,使用fcntl函数和F_SETFL命令将修改后的flag标志设置回文件描述符cfd,以实现将cfd设置为非阻塞模式。

因此,这段代码的作用是将文件描述符cfd设置为非阻塞模式,以便在进行I/O操作时,如果没有数据可读或没有足够的空间可写,不会阻塞进程的执行,而是立即返回一个错误或一个特殊的状态,使得进程可以继续执行其他任务。

2.要改变环境工作目录

前提是把webpath设置在家目录下

char path[255] = {0};
sprintf(path, "%s/%s", getenv("HOME"), "webpath");
chdir(path);

这段代码的作用是构造一个路径并将当前工作目录切换到该路径。

让我们逐步解释这段代码:

char path[255] = {0};

- 定义一个长度为255的字符数组path,并初始化为0。这个数组将用来存储构造的路径。

sprintf(path, "%s/%s", getenv("HOME"), "webpath");

- 使用sprintf函数将路径构造为$HOME/webpath的形式。getenv("HOME")用于获取当前用户的主目录路径,然后将其与"webpath"拼接起来,得到完整的路径。

chdir(path);

- 使用chdir函数将当前工作目录切换到构造的路径。这样,程序的当前工作目录就会变成$HOME/webpath。

综合起来,这段代码的作用是构造一个路径,并将当前工作目录切换到该路径。通常情况下,这样的操作用于确保程序在正确的目录下执行,以便正确地访问和处理文件。

3.fileName 读取位置+1,略过“/“

不然就是下面这样

 

 

4.scandir函数

scandir 函数是用于扫描指定目录并返回目录中的文件列表的函数。它返回一个指向 dirent 结构的指针数组,每个结构包含一个目录中的一个条目的信息。

以下是 scandir 函数的原型:

int scandir(const char *dirp, struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **, const struct dirent **));

dirp:要扫描的目录的路径名。

namelist:指向指针数组的指针,用于存储指向每个目录条目的指针。

filter:一个可选的过滤函数,用于决定哪些目录条目应该被返回。如果不需要过滤,可以将其设置为 NULL。

compar:一个可选的比较函数,用于对返回的目录条目进行排序。如果不需要排序,可以将其设置为 NULL。

以下是一个简单的示例,演示了如何使用 scandir 函数来列出目录中的文件:

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>

int main() {
    struct dirent **namelist;
    int n;

    n = scandir(".", &namelist, NULL, alphasort);
    if (n < 0) {
        perror("scandir");
        exit(EXIT_FAILURE);
    } else {
        for (int i = 0; i < n; i++) {
            printf("%s\n", namelist[i]->d_name);
            free(namelist[i]);
        }
        free(namelist);
    }

    return 0;
}

在这个示例中,scandir 函数扫描当前目录,并使用 alphasort 函数对返回的文件列表进行排序。然后,它遍历列表并打印每个文件的名称。

5.添加默认路径

比如http://192.168.44.3:9999 可以访问默认的主目录下面的文件夹内容

char *pFile = fileName;
if(strlen(fileName)<=1)    //添加默认为主目录下面 
{
     strcpy(pFile, "./");
}
else
{
     pFile = fileName+1;
}

注意不能将char *pFile fileName = NULL 设置为这样,否则会产生段错误

 

6.解决遇到汉字的问题

在webserver代码中调用了一个函数

strdecode(pFile, pFile); 这个函数在pub.c中,然后写了一个"编码",用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。 

//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';
}

 

5.对 SIGPIPE 信号的处理方式设置为忽略

对 SIGPIPE 信号的处理方式设置为忽略,即当进程收到 SIGPIPE 信号时,不做任何处理。这通常用于避免在网络编程中出现 SIGPIPE 错误,因为当一个进程向一个已经关闭的 socket 发送数据时,系统会向该进程发送 SIGPIPE 信号,如果不处理该信号,进程会终止。通过将 SIGPIPE 信号的处理方式设置为忽略,可以避免进程因此而终止。

//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, 
//则web服务器就会收到SIGPIPE信号
struct sigaction act;
act.sa_handler = SIG_IGN;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGPIPE, &act, NULL);

五、完整代码

webserver.c

//web服务端程序--使用epoll模型
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>
 
#include "pub.h"
#include "wrap.h"
 
int http_request(int cfd, int epfd);
 
int main()
{
    //若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, 
    //则web服务器就会收到SIGPIPE信号
    struct sigaction act;
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGPIPE, &act, NULL);
     
    //改变当前进程的工作目录
    char path[255] = {0};
    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;
    }
     
    //将监听文件描述符lfd上树
    struct epoll_event ev;
    ev.data.fd = lfd;
    ev.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
     
    int i;
    int cfd;
    int nready;
    int sockfd;
    struct epoll_event events[1024];
    while(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 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);
    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;
    }
     
    //循环读文件, 然后发送
    int n;
    char buf[1024];
    while(1)
    {
        memset(buf, 0x00, sizeof(buf));
        n = read(fd, buf, sizeof(buf));
        if(n<=0)
        {
            break;
        }
        else
        {
            Write(cfd, buf, n);
        }
    }
}
 
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)
    {
        //printf("read error or client closed, n==[%d]\n", n);
        //关闭连接
        close(cfd);
         
        //将文件描述符从epoll树上删除
        epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
        return -1;  
    }
    printf("buf==[%s]\n", buf);
    //GET /hanzi.c HTTP/1.1
    char reqType[16] = {0};
    char fileName[255] = {0};
    char protocal[16] = {0};
    sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);
    //printf("[%s]\n", reqType);
    printf("--[%s]--\n", fileName);
    //printf("[%s]\n", protocal);
     
    char *pFile = fileName;
    if(strlen(fileName)<=1)
    {
        strcpy(pFile, "./");
    }
    else
    {
        pFile = fileName+1;
    }
     
    //转换汉字编码
    strdecode(pFile, pFile);
    printf("[%s]\n", pFile);
     
    //循环读取完剩余的数据,避免产生粘包
    while((n=Readline(cfd, buf, sizeof(buf)))>0);
     
    //判断文件是否存在
    struct stat st;
    if(stat(pFile, &st)<0)
    {
        printf("file not exist\n");
         
        //发送头部信息
        send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
         
        //发送文件内容
        send_file(cfd, "error.html");   
    }
    else //若文件存在
    {
        //判断文件类型
        //普通文件
        if(S_ISREG(st.st_mode))  //man 2 stat查询,S_ISREG表示普通文件        
        {
            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))
        {
            printf("目录文件\n");
             
            char buffer[1024];
            //发送头部信息
            send_header(cfd, "200", "OK", get_mime_type(".html"), 0);   
             
            //发送html文件头部
            send_file(cfd, "html/dir_header.html"); 
             
            //文件列表信息
            struct dirent **namelist;
            int num;
 
            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(buffer));
                   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);
            }
            //发送html尾部
            sleep(10);
            send_file(cfd, "html/dir_tail.html");       
        }
    }
     
    return 0;
}

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)是 
 */
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';
}

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;
}

完整项目包上篇文章有,自取。感谢支持

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1249559.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

北京劲松HPV诊疗中心专业分析:扁平疣有什么特征?

扁平疣是一种常见的皮肤疾病&#xff0c;具有传染性&#xff0c;其主要特征包括皮肤出现扁平的丘疹、轻微瘙痒、好发于青少年等。今日特邀北京劲松HPV诊疗中心主任谭巍将详细介绍扁平疣的特征&#xff0c;希望借此能提高大众认知水平&#xff0c;以更好预防。 年轻漂亮的小芳是…

一体化污水处理设备各种材质的优缺点

一体化污水处理设备的材质有多种&#xff0c;包括不锈钢、玻璃钢、聚乙烯塑料、碳钢等。每种材质都有其独特的优点和缺点。 不锈钢材质的优点是防腐性能好&#xff0c;耐磨损&#xff0c;使用寿命长&#xff0c;且外观美观。其缺点是成本较高&#xff0c;不适合在一些特殊的环…

Django 通过 Trunc(kind) 和 Extract(lookup_name) 参数进行潜在 SQL 注入 (CVE-2022-34265)

漏洞描述 Django 于 2022 年6月4 日发布了一个安全更新&#xff0c;修复了 Trunc&#xff08;&#xff09; 和 Extract&#xff08;&#xff09; 数据库函数中的 SQL 注入漏洞。 参考链接&#xff1a; Django security releases issued: 4.0.6 and 3.2.14 | Weblog | Djang…

洛谷P1157组合的输出 递归:我他又来辣

没没没没没没没错&#xff0c;这是一道简单的递归&#xff08;其实是深搜加回溯) 我不管&#xff0c;我说是递归就是递归。 上题干&#xff1a; 题目描述 排列与组合是常用的数学方法&#xff0c;其中组合就是从 n 个元素中抽出 r个元素&#xff08;不分顺序且 r≤n&#x…

【阿里云服务器】2023安装宝塔面板8.0.4

文章目录 前言安装宝塔远程链接服务器输入安装宝塔命令放行宝塔端口 一键安装环境附录重装系统Linux系统卸载宝塔方式一方式二 遇见的问题 前言 镜像是CentOS 7.9.4 安装宝塔 远程链接服务器 输入安装宝塔命令 yum install -y wget && wget -O install.sh https://…

BGP基础配置

EBGP是AS之间 IBGP是AS内 R1-R2是EBGP,R4-R5是EBGP R2-R3-R4是IBGP 第一步基础配置&#xff1a;IP地址 [r1-GigabitEthernet0/0/0]ip ad 12.0.0.1 24 [r1-LoopBack0]ip ad 1.1.1.1 32 [r2-GigabitEthernet0/0/0]ip ad 12.0.0.2 24 [r2-LoopBack0]ip ad 2.2.2.2 32 [r2-Loop…

Educational Codeforces Round 158 (Rated for Div. 2)(A~E)(贪心,树形DP)

A - Line Trip 题意&#xff1a;有一条路&#xff0c;可以用一条数线来表示。你位于数线上的点 0 &#xff0c;你想从点 0 到点 x &#xff0c;再回到点 0。你乘汽车旅行&#xff0c;每行驶 1个单位的距离要花费 1 升汽油。当您从点 0出发时&#xff0c;汽车已加满油(油箱中的…

记录ruoyi-plus-vue部署的问题

ruoyi-vue-plus5.x 后端 ruoyi-vue-plus5.x 前端 前端本地启动命令 # 克隆项目 git clone https://gitee.com/JavaLionLi/plus-ui.git# 安装依赖 npm install --registryhttps://registry.npmmirror.com# 启动服务 npm run dev# 构建生产环境 yarn build:prod # 前端访问地址…

在Spring Boot中实现单文件,多文件上传

这篇文章算是一篇水文&#xff0c;因为也没啥好讲的&#xff0c;在Spring Boot中&#xff0c;上传文件是我们常常做的&#xff0c;包括我们在实际开发过程中&#xff0c;我们也经常碰到与文件上传有关的功能&#xff0c;这也算是我们常用的一个功能了&#xff0c;毕竟作为开发者…

5种主流API网关技术选型,yyds!

API网关是微服务项目的重要组成部分&#xff0c;今天来聊聊API网关的技术选型&#xff0c;有理论&#xff0c;有实战。 不 BB&#xff0c;上文章目录&#xff1a; 1 API网关基础 1.1 什么是API网关 API网关是一个服务器&#xff0c;是系统的唯一入口。 从面向对象设计的角度…

大众博客系统测试报告【改】

一、项目背景 大众博客系统采用前后端分离的方法来实现&#xff0c;同时使用了数据库来存储相关的数据&#xff0c;同时将其部署到云服务器上。前端主要有四个页面构成&#xff1a;登录页、列表页、详情页以及编辑页&#xff0c;以上模拟实现了最简单的大众博客系统。其结合后端…

Spine深入学习 —— 数据

atlas数据的处理 作用 图集&#xff0c;描述了spine使用的图片信息。 结构 page 页块 页块包含了页图像名称, 以及加载和渲染图像的相关信息。 page1.pngsize: 640, 480format: RGBA8888filter: Linear, Linearrepeat: nonepma: truename: 首行为该页中的图像名称. 图片位…

电流模式的PWM控制电路芯片D3846,封装形式采用DIP16/SOIC16,内置差动电流检测放大器, 共模输入范围宽

D3846是一块电流模式的PWM控制电路。 主要特点&#xff1a; ● 自动前馈补偿 ● 可编程控制的逐个脉冲限流功能 ● 推挽输出结构^ 下自动对称校正 ● 负载响应特性好 ● 可并联运行&#xff0c;适用于模块系统 ● 内置差动电流检测放大器&#xff0c; 共模输入范围宽 ● 双脉冲…

反思一次效能提升

前天与一个大佬交流。想起自己在6年多前在团队里做的一次小小的效能提升。 改进前 在同一个产品团队&#xff0c;同时有前端工程师和后端工程师。他们经常需要共同协作完成features。 前端是一个传统的多页应用。前端渲染是由后端的velocity模板引擎实现的。 打包后&#xff0c…

【浏览器】录音open失败:浏览器禁止不安全页面录音,可开启https解决..

在浏览器地址栏中输入&#xff1a;chrome://flags/#unsafely-treat-insecure-origin-as-secure 启动选项&#xff0c;并且添加你本地的开发地址

多actor实体组合并统一应用变换_vtkAssembly

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example参考代码 demo解决问题&#xff1a;创建了一个球体和立方体的三维可视化&#xff0c;将它们组合成一个装配体&#xff0c;应用变换&#xff0c;调整不透明度&#…

C#,《小白学程序》第三课:类class,类的数组及类数组的排序

类class把数值与功能巧妙的进行了结合&#xff0c;是编程技术的主要进步。 下面的程序你可以确立 分数 与 姓名 之间关系&#xff0c;并排序。 1 文本格式 /// <summary> /// 同学信息类 /// </summary> public class Classmate { /// <summary> /…

使用 JavaScript 进行 API 测试的综合教程

说明 API 测试是软件测试的一种形式&#xff0c;涉及直接测试 API 并作为集成测试的一部分&#xff0c;以确定它们是否满足功能、可靠性、性能和安全性的预期。 先决条件&#xff1a; JavaScript 基础知识。Node.js 安装在您的计算机上。如果没有&#xff0c;请在此处下载。npm…

设计模式—里氏替换原则

1.概念 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说&#xff0c;任何基类可以出现的地方&#xff0c;子类一定可以出现。 LSP是继承复用的基石&#xff0c;只有当衍生类可以替换掉基类&#xff0c;软件单位的功能不受到影…

K8S客户端一 Rancher的安装

一 安装方式一 通过官网方式安装&#xff1a;官网 sudo docker run --privileged -d --restartunless-stopped -p 80:80 -p 443:443 rancher/rancher:stable访问服务器地址即可&#xff1a;http://192.168.52.128 修改语言 第一次安装会生成密码&#xff0c;查看密码步骤如下…