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

一、引言

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.1char 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--设置端口复用---bindint 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.1char 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);elsec = '\n';}buf[i] = c;i++;}elsec = '\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进制的E8from += 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;elseperr_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;elsereturn -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;elsereturn -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;elsereturn -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;elsereturn -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;} elsereturn -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.mzph.cn/news/168094.shtml

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

相关文章

ES之x-pack-core-7.14.2许可证修改为白金版

X-Pack是什么 X-pack是elasticsearch的一个扩展包&#xff0c;将安全&#xff0c;警告&#xff0c;监视&#xff0c;图形和报告功能捆绑在一个易于安装的软件包中&#xff0c;虽然x-pack被设计为一个无缝的工作&#xff0c;但是你可以轻松的启用或者关闭一些功能。 主要分一下步…

WebSocket 鉴权策略与技巧详解

WebSocket 作为实时通信的利器&#xff0c;越来越受到开发者的青睐。然而&#xff0c;为了确保通信的安全性和合法性&#xff0c;鉴权成为不可或缺的一环。本文将深入探讨 WebSocket 的鉴权机制&#xff0c;为你呈现一揽子的解决方案&#xff0c;确保你的 WebSocket 通信得心应…

【Qt之QTextDocument】使用及表格显示富文本解决方案

【Qt之QTextDocument】使用 描述常用方法及示例使用QTextList使用QTextBlock使用QTextTable表格显示富文本结论 描述 QTextDocument类保存格式化的文本。 QTextDocument是结构化富文本文档的容器&#xff0c;支持样式文本和各种文档元素&#xff0c;如列表、表格、框架和图像。…

手把手教你安装 Visual Studio 2022 及其简单使用

软件下载 打开 Visual Studio 官网&#xff0c;个人选择免费的Community社区版就够用了。 软件安装 双击运行安装程序&#xff1a; 点击继续 即可&#xff1a; 等待加载完成&#xff1a; 可以看到 Visual Studio 2022 对应不同的开发需求提供了若干工作负载&#xff0c;这里以…

​3ds Max插件CG MAGIC图形板块为您提升线条效率!

​通过3ds Max软件进行绘图操作时&#xff0c;大多绊住各位设计师们作图速度的往往都是一些细微的琐事&#xff0c;重复一变一变的调整修改等问题。 今天说到这个绘图线条来回调整解决方法就是3ds Max插件CG MAGIC。 Max插件CG MAGIC作为一款智能化的辅助插件&#xff0c;致力于…

flutter编译和构建鸿蒙应用程序(windows环境)

flutter编译和构建鸿蒙应用程序&#xff08;windows环境&#xff09; 问题背景 针对 OpenHarmony 的 Flutter 版本已经开源&#xff0c;参考 https://gitee.com/openharmony-sig/flutter_flutter。 本文为实践该流程&#xff0c;实现flutter打包鸿蒙hap包的流程。目前流程已经…

Ps:画笔工具的基本操作

画笔工具 Brush Tool是 Ps 中最常用的工具&#xff0c;广泛地用于绘画与修饰工作。 虽然多数操作可在画笔工具的工具选项栏中选择执行&#xff0c;但是如果能记住相应的快捷键可大大提高工作效率。 熟练掌握画笔工具的操作对于使用其他工具也非常有益&#xff0c;因为 Ps 中许多…

CSS-长度单位篇

px&#xff1a;像素em&#xff1a;相对元素font-size的倍数rem&#xff1a;相对根字体大小&#xff0c;html标签就是根%&#xff1a;相对父元素计算 注意&#xff1a;CSS中设置长度&#xff0c;必须加单位&#xff0c;否则样式无效&#xff01;

Maven - 打包之争:Jar vs. Shade vs. Assembly

文章目录 Pre概述Jar 打包方式_maven-jar-pluginOverview使用官方文档 Shade 打包方式_maven-shade-pluginOverview使用将部分jar包添加或排除将依赖jar包内部资源添加或排除自动将所有不使用的类排除将依赖的类重命名并打包进来 &#xff08;隔离方案&#xff09;修改包的后缀…

【自主探索】基于 frontier_exploration 的单个机器人自主探索建图

文章目录 一、概述1、功能2、要求 二、使用方法1、用于运行演示2、用于开发人员2.1. 探索无/地图数据2.2. 使用 /map 数据进行探索 三、提供的组件1、explore_client1.1. 调用的操作1.2. 订阅主题1.3. 发布主题 2、explore_server2.1. 提供的操作2.2. 调用的操作2.3. 调用的服务…

web前端开发基础----标准流布局和非标准流布局

1&#xff0c;标准流布局 标准流&#xff0c;也称文档流或普通流&#xff0c;是所有元素默认的布局方式。 在标准流中&#xff0c;元素按照其在 HTML 中出现的顺序&#xff0c;自上而下依次排列&#xff0c;并占据其父容器内的可用空间。 标准流中的元素按照其自然尺寸和位置进…

uniapp实现多时间段设置

功能说明&#xff1a; 1 点击新增时间&#xff0c;出现一个默认时间段模板&#xff0c;不能提交 2 点击“新增时间文本”&#xff0c;弹出弹窗&#xff0c;选择时间&#xff0c;不允许开始时间和结束时间同时为00:00&#xff0c; <view class"item_cont"> …

TCP/IP协议:最流行的电子邮件协议SMTP(简单邮件传输协议)详解

SMTP 是一种电子邮件协议&#xff0c;用于通过互联网从一个电子邮件帐户向另一个电子邮件帐户发送电子邮件。它是TCP/IP协议应用层的一部分。作为一种电子邮件协议&#xff0c;它建立了不同电子邮件客户端和帐户之间轻松信息交换的规则。这样&#xff0c;简单邮件传输协议就可以…

ArkUI开发进阶—@Builder函数@BuilderParam装饰器的妙用与场景应用

ArkUI开发进阶—@Builder函数@BuilderParam装饰器的妙用与场景应用 HarmonyOS,作为一款全场景分布式操作系统,为了推动更广泛的应用开发,采用了一种先进而灵活的编程语言——ArkTS。ArkTS是在TypeScript(TS)的基础上发展而来,为HarmonyOS提供了丰富的应用开发工具,使开…

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;汽车已加满油(油箱中的…

麒麟linux离线安装dotnet core

1. 下载 dotnet core,以3.1为例 下载地址: 下载 .NET Core 3.1 (Linux、macOS 和 Windows) 查看linux cpu类型,然后根据类型下载 uname -m #结果是: aarch64 2. 放到指定目录,比如:/usr/dotnet 3. 解压dotnet-sdk-3.1.426-linux-arm64.tar.gz cd /usr/dotnet tar –zxvf a…

Ubuntu 22.04.3编译AOSP13刷机

文章目录 设备信息下载AOSP并切换分支获取设备驱动编译系统编译遇到的问题Cannot allocate memoryUbuntu设置USB调试刷机参考链接 设备信息 手机&#xff1a;Pixel 4XL 下载AOSP并切换分支 在清华大学开源软件镜像站下载初始化包aosp-latest.tar。 解压缩&#xff0c;切换到…

解决LocalDateTime传输前端为时间的数组

问题出现如下&#xff1a; 问题出现原因&#xff1a; 默认序列化情况下会使用SerializationFeature.WRITE_DATES_AS_TIMESTAMPS。使用这个解析时就会打印出数组。 解决方法&#xff1a; 我在全文搜索处理方法总结如下&#xff1a; 1.前端自定义函数来书写 ,cols: [[ //表头{…

Langchain的Agents介绍

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Redis -- 介绍

1、NoSQL: 指的是非关系型数据库&#xff0c;主要分成四大类&#xff1a;键值存储数据库、列存储数据库、文档型数据库、图形数据库。 2、什么是Redis&#xff1a; Redis是一种基于内存的数据库&#xff0c;一般用于做缓存的中间件。 3、Redis的主要的特点&#xff1a; 1、Rd…