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.pswp.cn/news/168094.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/168094.shtml
英文地址,請注明出處:http://en.pswp.cn/news/168094.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Vue中項目進行文件壓縮與解壓縮 (接口返回文件的url壓縮包前端解析并展示出來,保存的時候在壓縮后放到接口入參進行保存)

安裝 npm install pako在Vue組件中引入pako&#xff1a; import pako from pako;接口返回的url是這個字段 tableSsjsonUrl 其實打開就是壓縮包const source await tableFileUrl ({ id: this.$route.query.id}); if(source.code 0) {this.titleName source.data.tableNam…

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;如列表、表格、框架和圖像。…

CANopen協議【SDO】

SDO&#xff1a; SDO是讀寫單個寄存器。主要用于配置伺服驅動器參數。 1 收發功能&#xff1a; //public unsafe struct VCI_CAN_OBJ //CAN數據幀 【單幀對象】//{// public uint ID;// 幀ID。 【11bit】數據右對齊。 詳情請參照&#xff1a; 《8.附件1&#xff1…

Python 模塊

目錄 模塊 6.1. 模塊詳解 6.1.1. 以腳本方式執行模塊 6.1.2. 模塊搜索路徑 6.1.3. “已編譯的” Python 文件 6.2. 標準模塊 6.3. dir() 函數 6.4. 包 6.4.1. 從包中導入 * 6.4.2. 相對導入 6.4.3. 多目錄中的包 模塊 退出 Python 解釋器后&#xff0c;再次進入時&a…

手把手教你安裝 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包的流程。目前流程已經…

flink的java.lang.IllegalStateException: Buffer pool is destroyed 異常

背景 最近flink的在線應用出現錯誤java.lang.IllegalStateException: Buffer pool is destroyed&#xff0c;本文記錄下這個錯誤的原因 錯誤原因 詳細的日志堆棧如下: Caused by: java.lang.IllegalStateException: Buffer pool is destroyed. at org.apache.flink.runtime…

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;修改包的后綴…

ElasticSearch之cat component templates API

命令樣例如下&#xff1a; curl -X GET "https://localhost:9200/_cat/component_templates?vtrue&pretty" --cacert $ES_HOME/config/certs/http_ca.crt -u "elastic:ohCxPHQBEs5*lo7F9"執行結果輸出如下&#xff1a; name …

Qt/QML編程學習之心得:一個QML工程的學習筆記(十)

前言: 到底什么是Qt Quick呢?因為Qt Quick是Qt新引入的,Qt Quick由Qt Quick模塊提供,它是一個編寫QML應用的標準庫。Qt Quick模塊提供了兩種接口:使用QML語言創建應用的QML接口和使用C++語言擴展QML的C++接口。使用Qt Quick模塊,設計人員和開發人員可以輕松地構建流暢的…

【自主探索】基于 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. 調用的服務…

【科技素養】藍橋杯STEMA 科技素養組模擬練習試卷6

1、將一個空塑料袋與一個裝滿空氣的塑料袋放到秤上稱重。在兩個塑料袋本身完全一樣的情況下&#xff0c;得出的結果會是 A、空塑料袋更重 B、裝滿空氣的塑料袋更重 C、兩個塑料袋一樣重 D、無法判斷 答案&#xff1a;C 2、全球變暖帶來的一個主要影響就是海平面的上升。造…

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;簡單郵件傳輸協議就可以…