仿mudou庫one thread oneloop式并發服務器

前言

我們所要實現的是一個高并發服務器的組件,使服務器的性能更加高效,是一個高并發服務器的組件,并不包含實際的業務。

首先需要先明確我們所要實現的目標是什么

  • 第一點,實現一個高并發的服務器
  • 第二點,在服務器的基礎上提供應用協議的支持

HTTP服務器

Server模塊

服務器模塊是對所有連接以及線程進行管理,讓他們各司其職,在合適的時間做對應的事,最終完成高性能服務器組件的實現,具體管理分為以下幾個方面。

  • 要想獲取新連接,必須先搭建一個監聽套接字來獲取新連接,需要對監聽連接進行管理
  • 獲取新連接之后就有了通信連接的套接字,需要有一個通信連接的管理
  • 如果有一些惡意的客戶端,連接上我的服務器之后并不進行通信,就是占著我服務器的資源,所以就需要進行超時連接的管理。

Buffer模塊

  • 緩沖區模塊,當一次從socket讀取數據時可能讀到的信息不是完整的,因此需要對讀到的數據進行緩存,當服務器應答時,向socket套接字寫入時,可能socket已經滿了,或者寫不了一個完整的數據時,會進入到阻塞狀態,所以應該在套接字可寫入的情況下發送
  • 功能設計:向緩沖區中添加數據;從緩沖區中讀取數據;

Socket模塊

  • 對socket套接字的各項操作的封裝,讓程序中對套接字的操作更加方便
  • 功能設計:創建套接字、綁定地址信息、開始監聽、向服務器發起連接、獲取新連接、接收連接、發送連接、關閉套接字,為了方便我們需要對基礎操作做一個集成,例如:要創建一個監聽套接字,需要一個個的調用,集成一個創建監聽套接字
  • 在基礎操作之上集成的新功能:創建監聽套接字、客戶端連接

Channel模塊

功能設計:

  • 設置可寫事件監控、設置可讀事件監控、
  • 刪除可寫事件監控、刪除可讀事件監控、刪除所有事件監控
  • 判斷是否設置了可寫事件、判斷是否設置了可讀事件
  • 可讀、可寫、掛斷、錯誤、任意五個事件需要處理,就需要5個回調函數

Poller模塊

  • Poller模塊是對IO多路轉接epoll的封裝,對于一個描述符的可讀、可寫的事件監控操作更加簡單。
  • 功能設計:添加事件監控、修改事件監控、移除事件監控

Connection模塊

  • 是對連接、通信管理的模塊,前面的模塊是單獨獨立的模塊,根據Connection模塊所實現的內容,需要將前面獨立模塊組織起來,Connection模塊中一個連接有任何的怎么處理都是有這個模塊來進行處理的,因為組件的設計也不知道使用者要如何處理事件,因此只能提供一些回調函數由使用者來處理。
  • 功能設計:關閉連接、發送數據、 連接建立完成的回調、新數據接收成功的回調、連接關閉時的回調、產生任何事件進行的回調。
  • 不同協議有不同的處理方式,所以需要有一個協議切換的功能,本質上是替換回調函數,當有一些惡意的連接,連上之后很長時間不發,數據,一直占用的資源,所以需要有超時連接釋放的功能,啟動非活躍連接超時釋放的功能、該功能可以默認是關閉的,在需要使用的時候再打開,但是在有些情況不需要該功能,所以該功能最好做成可選的功能,所以再添加一個關閉非活躍連接超時釋放的功能。

Acceptor模塊

  • Acceptor模塊也是一個管理模塊,Connection模塊是針對通信套接字進行管理的模塊,Acceptor模塊是針對監聽套接字進行管理的模塊,都需要做哪些管理呢?
  • 當一個監聽套接字已經開始監聽了,通過Socke模塊中的調用Accept成員獲取新建連接,Accept返回的是一個文件描述符,文件描述符對于我們來說太原始了,接下來需要多通信套接字進行操作,所以我們需要將該文件描述符封裝成Connection對象,該連接是一個新連接,是在此處設置相關的回調。
  • 功能設計:當前Acceptor模塊也不知道具體的回調函數,所以需要對外提供一個回調函數設置功能

TimerQueue模塊

  • 定時任務模塊,讓一個任務在指定的時間之后被執行
  • 功能設計:添加定時任務、刷新定時任務、取消定時任務

EventLoop模塊

功能設計:
? ? ? ? 將一個任務添加到執行任務的隊列的功能,因為一個連接所有的操作都要在EventLoop中完成,但是一個EventLoop要監控很多個連接,假設上層的線程池處理完一個業務,要發送數據,發送數據不能在自己的線程完成,發送的任務要在EventLoop模塊中完成,這樣才能避免線程安全的問題。一個線程一個EventLoop,一個EventLoop有多個Connection(一個EventLoop不止監控一個連接,要監控很多個連接),一個Connection只能在一個EventLoop中

對于服務器中所有的事件都是由EventLoop模塊來完成的,為什么要這樣去做呢?

? ? ? ?因為每一個Connection連接都會綁定一個EventLoop模塊和線程,因為外界對于連接的所有操作都要放到同一個線程中進行操作,EventLoop模塊為了保證整個服務器的線程安全問題,對于Connection的所有操作?定要在其對應的EventLoop線程內完成,不能在其他線程中進行。

具體操作流程:

  1. 通過Poller模塊對當前模塊管理內的所有描述符進?IO事件監控,有描述符事件就緒后,通過描述符對應的Channel進?事件處理。
  2. 所有就緒的描述符IO事件處理完畢后,對任務隊列中的所有操作順序進?執?。
  3. 由于epoll的事件監控,有可能會因為沒有事件到來?持續阻塞,導致任務隊列中的任務不能及時得到執?,因此創建了eventfd,添加到Poller的事件監控中,?于實現每次向任務隊列添加任務的時
  4. 候,通過向eventfd寫?數據來喚醒epoll的阻塞。

TcpSever模塊

前面的所有模塊不是功能模塊就是管理模塊,而這個模塊是提供給使用者,讓組件的使用者使用起來更加容易。

功能設計:

  • 對監聽連接的管理,獲取一個新連接之后如何處理,由TcpSever模塊設置
  • 對通信連接的管理,一個連接產生了某個事件如何處理,由TcpSever模塊設置
  • 對超時連接的管理,連接非活躍超時是否關閉連接,由TcpSever模塊設置
  • 對事件監控的管理,啟動多少個線程,有多少個EventLoop,由TcpSever模塊設置
  • 事件回調函數的設置,設置給TcpSever,TcpSever設置給各個Connection連接

前置知識

1. Linux系統提供的定時功能

創建一個定時器

#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
  • clockid參數:CLOCK_REALTIME啟動時以當前系統時間為起始時間開始,使用該方式為基準值,如果在已經啟動定時器后修改了系統時間就會發生問題,CLOCK_MONOTONIC以系統啟動開始遞增的時間為基準值,不會隨著系統時間改變而改變
  • flags:0表示阻塞操作
  • 返回值:返回一個文件描述符

啟動定時器

int timerfd_settime(int fd, int flags, const struct itimerspec *new_value,struct itimerspec *old_value);
  • fd參數:timerfd_create的返回值
  • flags參數:默認設置為0,表示使用相對時間
  • new_value參數:設置的超時時間
  • old_value參數:用于接收當前定時器原有的超時時間設置
struct timespace
{time_t tv_sec;/*秒*/long tv_nsec;/*納米*/
}
struct itimerspec
{struct timespec it_interval;/*第一次超時后以后每次多長時間超時*/struct timespec it_value; /*啟動定時器后第一次多長時間超時*/
}

2. 正則庫的使用

對HTTP協議請求行的提取

3.任意類型存儲的設計實現

? ?我們的組件內部從socket中讀到的信息存在多種情況,有可能不足一條完整的信息,有可能是多條數據,這就需要記錄當前請求處理到那條信息了,一個連接有一個用來記錄上下文信息的。我們當前實現的組件是支持任何協議的,并不是只支持HTTP協議,所以記錄處理當前到什么階段的信息的容器應該是保存任意類型的容器。 在boost庫和C++17給我們提供了通用類型any來使用,但是考慮到代碼的移植性和版本適應性,不用boost庫和C++17給提供的any,自己實現一個any通用類型。 any類的設計,首先any不能通過模版來實現,因為在實例化any對象時需要指定T類型,使用類嵌套類, 內部類是一個模版,外部類的成員變量是內部類對象的指針,但是在實例化any對象時仍需要指定T類型,怎么解決,使用多態思路解決,外部類的成員變量是父類對象的指針,那在實例化any對象時就不用指定T類型。

4. C++17中any的使用

Buffer模塊

對數據的暫時保存,能夠放入數據和取出數據,緩沖區底層空間使用的容器是vector,而不采用string來管理空間,是因為字符串操作時遇到’\0’字符串結尾標志,就停止操作,但是我們網絡傳輸數據時會遇到各種數據,可能傳輸的數據中有0,我們并不能因為遇到0就停止處理。

所需成員變量:默認空間大小、當前讀取數據的位置、當前寫入數據的位置

具體操作思路:

寫入數據:當前寫入位置指向哪里,就從哪里開始寫入,如果后續剩余空間不夠了,先看整體緩沖區空閑空間是否足夠,

  • 足夠:將數據移動到起始位置即可,然后再將數據插入到緩沖區中。
  • 不夠:為了保證程序的效率,就不將數據移動到起始位置,直接擴容,從當前寫位置開始擴容足夠大小

數據一旦寫入成功,當前寫位置就要向后偏移。

讀取數據:當前的數據位置指向哪里,就從哪里開始讀取,前提是有數據可讀

可讀數據大小:當前寫入位置-當前讀取位置

日志宏函數的編寫

#include <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *stream, const char *format, ...);

但是打印的日志信息中沒有文件名、行號的信息

  • FILE:宏標識,是當前文件的文件名字符串
  • LINE:宏標識,是當前所在的行號
#define LOG(msg) fprintf(stdout, "[%s:%d]%s", __FILE__, __LINE__, msg)

目前的日志宏只能打印固定的字符串,要想設計出通用的日志宏,對于打印的格式是不知道的,以及參數也是不確定的(參數類型、參數個數)。

  • 將打印的格式作為參數傳進來
  • …:表示形參個數不定
  • VA_ARGS:代表傳的不定參
#define LOG(format, ...) fprintf(stdout, "[%s:%d]" format, __FILE__, __LINE__, __VA_ARGS__)

打印格式format會和"[%s:%d]"進行拼接

但是我們打印日志是習慣只傳一個打印的信息

在編譯時發生了編譯報錯,這時因為宏函數的接口是LOG(format, …),而我們調用宏函數是LOG(str.c_str()),str.c_str()作為format形參,但是沒有傳遞不定參數,所以編譯報錯

  • ##VA_ARGS:給__VA_ARGS__前加上##表示有不定參數就格式化打印,沒有就不進行格式化打印
#define LOG(format, ...) fprintf(stdout, "[%s:%d]" format, __FILE__, __LINE__, ##__VA_ARGS__)

將我們的日志答應更加完善一些,加上時間,讓我們能夠方便知道這條信息是什么時候打印的,在項目完成之后會有一個超市測試都會用到這個時間。

#include <time.h>
size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);
  • 將tm結構體中保存的時間以指定的格式轉換成字符串

宏代碼必須在一行,通過續航符’'對代碼續航

添加日志等級

通過一鍵控制讓我們的這個項目中不同等級的日志信息打印受到控制

沒有打印出來的原因是因為我們設置的打印日志等級是DEBUG,而我們所打印的日志等級是INFO,所以并沒有打印,我們將日志打印的等級設置為INFO,就能夠看到打印的信息

Socket模塊

設置套接字選項,開啟地址端口重用

   一個連接綁定了地址和端口號之后,一旦主動斷開連接,就會進入到timewait的保護狀態,進入到這個狀態之后我們的套接字并不會立即被釋放,此時地址和端口會被占用,這個通常是用來保護客戶端的,但是在服務器的使用過程中服務器一旦出現問題,如:崩潰、退出,有可能會造成服務器無法立即重新啟動,所以要開啟地址重用,讓該地址和端口重新被用起來。

send函數聲明:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

參數解釋:

  • sockfd:套接字描述符,表示已連接的套接字。
  • buf:指向要發送的數據緩沖區。
  • len:要發送的數據字節數。
  • flags:控制數據發送行為的標志,可以為 0 或者使用一些選項(例如 MSG_DONTWAIT,非阻塞發送)

返回值:

  • 成功時,返回發送的字節數。
  • 失敗時,返回 -1,并設置相應的 errno 錯誤碼。

recv 函數函數聲明:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

參數解釋:

  • sockfd:套接字描述符,表示已連接的套接字。
  • buf:指向接收數據的緩沖區。
  • len:緩沖區的大小,最大接收字節數。
  • flags:控制接收行為的標志,如 MSG_WAITALL(等待接收完整數據),MSG_PEEK(查看緩沖區中的數據,但不將其從緩沖區移除)。

返回值:

  • 成功時,返回接收到的字節數。
  • 如果對端正常關閉連接,返回 0。
  • 失敗時,返回 -1,并設置 errno

設置套接字選項

#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level,   int optname, const void *val, socklen_t optlen);
  • sockfd:套接字描述符
  • level:設置的選項等級,須填寫為SOL_SOCKET
  • optname:設置的選項的名稱

查看套接字選項有哪些

設置文件描述符屬性

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int op, ... /* arg */ );

size_t和ssize_t類型

  • size_t無符號類型
  • ssize_t有符號類型,包含在<unistd.h>的頭文件中

  • 一直循環報出socket連接失敗的錯誤信息,只是因為創建的監聽套接字設置為了非阻塞屬性

Channel模塊

EPOLLIN     //可讀事件監控
EPOLLOUT    //可寫事件監控
EPOLLRDHUP  //用于檢測TCP連接的半關閉狀態,檢測對端關閉了連接或關閉了連接的寫端
EPOLLHUP    //斷開
EPOLLERR    //錯誤
EPOLLPRI    //優先數據

EPOLLRDHUP 是 Linux epoll 機制中的一個事件標志,它用于檢測對端是否關閉了連接(或至少關閉了寫方向),避免了進行不必要的 read() 調用,提高了事件驅動程序的效率。TCP 半關閉是 TCP 協議提供的一種特性,允許連接的一端在停止發送數據后,仍然能夠接收來自另一端的數據。

與 EPOLLHUP 的區別:

  • EPOLLRDHUP: 對端關閉了連接(或寫端),連接可能仍處于半關閉狀態(本端還可以寫數據)。
  • EPOLLHUP: 表示發生了掛起。這通常意味著連接完全斷開(雙方都已關閉,或者發生了錯誤導致連接不可用)。收到 EPOLLHUP 后,套接字通常已經不可用。

EPOLLERR表示在關聯的套接字發生了一個錯誤,收到?EPOLLERR?通常意味著該套接字連接已經不可用或處于錯誤狀態,后續的讀寫操作很可能會失敗。

EPOLLPRI?用于表示帶外數據或緊急數據可讀通知,在 TCP 中,帶外數據通過設置報文頭的?URG?標志和緊急指針實現。

Poller模塊

epoll的操作句柄

struct epoll_event結構的數組,用來記錄每次有哪些套接字上的事件就緒了

套接字和Channel對象的哈希映射

  • 將套接字設置進內核進行監控,通過Channel才能知道套接字需要監控什么事件
  • 當套接字上的時間就緒了,通過套接字在hash表中找到對應的Channel,才能知道事件如何處理。

暫時將Poller和Chanel進行連調,目的是為了先對Poller類和Chanel類代碼測試,及時發現有什么語法或者功能錯誤

gdb ./server調試運行程序

bt進入函數調用棧,發現出現的錯誤是在server.hpp的594行

這是因為客戶端程序終止,服務端程序recv()就會返回0,我們的操作是移除監控、delete chanel,chanel就不存在了,后面再調用_Event()回調函數就會出錯,

還有一個BUG:HandleClose操作中先是移除,然后才能關閉fd,否則報錯

eventLoop模塊

eventfd?是?Linux?內核提供的一種用于線程或進程間通知的機制,它通過一個文件描述符來實現進程間的同步和事件通知。該函數在多線程和多進程編程中非常有用,尤其是在實現事件驅動和異步通知機制時。

#include <sys/eventfd.h>
int eventfd(unsigned int initval, int flags);

參數說明:

  • initval:當創建?eventfd?時,內核會初始化一個內部計數器,并將其值設置為?initval
  • flsgs:控制?eventfd?行為的標志位
  • EFD_CLOEXEC: 設置文件描述符的 close-on-exec 標志,表示在執行 exec 系列函數時自動關閉該文件描述符。
  • EFD_NONBLOCK: 設置文件描述符為非阻塞模式。如果計數器為 0,讀操作將立即返回 EAGAIN 錯誤,而不是阻塞。
  • EFD_SEMAPHORE: 將 eventfd 設置為信號量模式。在這種模式下,每次讀操作只會將計數器減 1,而不是讀取整個計數器的值。

返回值:

  • 成功時,返回一個新的文件描述符,用于后續的讀寫操作。
  • 失敗時,返回?-1?并設置?errno?以指示錯誤原因。

使用場景:

  • 線程間通信:一個線程可以通過寫入 eventfd 來通知另一個線程。
  • 進程間通信:父子進程或無關進程可以通過共享 eventfd 文件描述符來進行事件通知。
  • 與 epoll 或 select 結合使用:eventfd 可以與其他 I/O 多路復用機制一起使用,以實現高效的事件驅動編程。

每次給eventfd中寫入一個1,就表示通知一次,連續寫入三次后,再去read讀取出來的數字就是3,讀取之后計數清0。

  • 在EventLoop模塊中實現線程間的事件通知功能,每當向eventfd中寫入一個數值用于表示事件通知的次數。

怎么保證一個連接的所有操作都在eventLoop對應的線程中?

? ?給EventLoop模塊中添加一個任務隊列,對連接的所有操作進行一次封裝,對連接的操作并不是直接執行,而是當成任務添加到任務隊列中。

Connection模塊

管理:

  1. 套接字的管理,能夠進行套接子的操作
  2. 連接事件的管理:可讀、可寫、錯誤、掛斷、任意
  3. 緩沖區的管理,便于socket數據的發送和接收
  4. 上下文的管理,記錄請求數據的處理過程
  5. 回調函數的管理(我們只是提供一個服務器組件,數據接收后該如何處理,需要由用戶來決定,因此必須有業務處理回調函數,并且一個連接建立完成之后該如何處理,也由用戶來決定,例如: 假設有一個聊天的場景,有一個連接上來了,需要給其他連接通知有新連接上來了,因此也需要連接建立成功后的回調函數,也是由用戶來設置的,以及連接關閉前該如何處理的回調函數,任意事件產生的回調函數,例如:進行了IO就需要刷新一次活躍度)

功能:

  1. 發送數據的功能,這個接口是提供給用戶發送數據的接口(這個接口內部并不是直接發送,而是把數據放到發送緩沖區,然后啟動寫事件監控)
  2. 關閉連接功能,這個接口是提供給用戶關閉連接的接口(在實際釋放連接之前,先看輸入和輸出緩沖區是否有數據待處理,如果沒有直接釋放連接,如果有將連接設置為待釋放狀態)
  3. 啟動非活躍連接超時銷毀功能
  4. 取消非活躍連接超時銷毀功能(有可能我們的連接就是長連接,所以就不需要超時連接銷毀功能)
  5. 協議切換

存在這樣一個場景:Connection模塊是對連接管理的模塊,對連接的所有操作都是通過這個模塊來完成的,對連接進行操作的時候,但是連接已經被釋放了,導致內存訪問錯誤,最終程序崩潰,假設一個線程對連接進行了釋放,但是另一個線程需要對連接進行操作,將這兩個操作如果放到任務隊列中,會有一個時序的問題,假如先執行的釋放連接的操作,后執行對連接操作的任務,這就出現了問題。

解決方法:使用智能指針shared_ptr對Connection對象進行管理,只要計數不為0就不會釋放Connection對象,這就能夠保證任意一個地方對Connection對象進行操作的時候,都保存了一份shared_ptr,因此就算其他地方進行操作,也只是對shared_ptr的計數-1,并不會導致Connection的實際釋放。

LoopThread模塊

將線程和LoopThread模塊整合起來

關鍵點:

  1. EventLoop模塊和線程一一對應的
  2. 實例化EventLoop對象,在構造時就會初始化thread_id

LoopThreadPool模塊:針對LoopThread設計一個線程池

TcpSever模塊

測試服務器組件

void OnConnected(const PtrConnection &conn) {DEBUG_LOG("NEW CONNECTION:%p", conn.get());
}
void OnClosed(const PtrConnection &conn) {DEBUG_LOG("CLOSE CONNECTION:%p", conn.get());
}
void OnMessage(const PtrConnection &conn, Buffer *buf) {DEBUG_LOG("%s", buf->ReadPos());buf->ReadIdxMove(buf->ReadAbleSize());std::string str = "Hello World";conn->Send(str.c_str(), str.size());conn->Shutdown();
}int main()
{TcpServer server(8500);//server.EnableInactiveRelease(10);server.SetThreadCount(2);server.SetConnectedCallBack(std::bind(OnConnected, std::placeholders::_1));server.SetCloseCallBack(std::bind(OnClosed, std::placeholders::_1));server.SetMessageCallBack(std::bind(OnMessage, std::placeholders::_1, std::placeholders::_2));server.Start();return 0;
}

啟動運行成程序發現程序崩潰了,一般是邏輯上的問題,這種報錯相對不是很好找,需要調試來觀察邏輯上哪的問題

調試運行server程序

運行客戶端./client

查看函數調用棧

從棧頂開始看,說在1189行有個錯誤

單憑這些也看不出來到底是啥問題,這行代碼就是返回_loops中的元素,我們接下來通過在這里打一個調試斷點來看一下_next_idx的值是多少,和_loops的相關信息

break …/source/server.hpp:1189打一個斷點然后重新再運行一下,服務端程序運行起來后客戶端程序也重新運行起來

查看對應值

顯示_loops這個容器成員為0,那就會造成越界訪問,這個問題出現在哪呢

我們在創建TcpServer對象時就創建線程池,此時線程池中從屬線程的個數為0,但接著又設置了線程數為2,這就形成了邏輯上的問題,所以應該啟動TcpSever時再調用Creat()創建從屬線程(創建從屬線程時就啟動了EventLoop)

修改后再次運行程序,發現程序能夠正常運行

測試提供給組件使用這關閉連接的功能是否正常

NetWork類

有這樣一種情況就是主機A和主機B進行通信,A端關閉了自己的連接,但是B端還繼續進行寫,這時B主機的操作系統就會發送SIGPIPE信號來終止進程,這就會導致程序的退出,所以需要忽略這個信號,不讓進程退出。

Http協議模塊

Util模塊

需要提供的工具接口

  1. 文件的讀寫(向文件寫入、讀取文件內容,例如客戶端可能需要上傳一個文件,那就涉及到向文件中寫入,如果客戶端需要請求資源例如獲取一個網頁(html),這就涉及到讀取文件)
  2. URL的編碼和解碼
  3. 通過狀態碼獲取對應的描述信息
  4. 根據文件后綴名獲取mime(瀏覽器通常采用MIME類型而不是文件擴展名來確定如何處理URL和文件內容,所以在響應頭需要添加標準的mimetype,否則瀏覽器將不能以正常的方式來處理解析文件內容)
  5. 判斷一個文件是否是目錄
  6. 判斷一個文件是否是一個普通文件
  7. 判斷HTTP請求的資源路徑是否是有效性(不能讓用戶請求目錄下的任意資源)

HttpRequest模塊

接收到一個請求,按照HTTP請求格式進行解析,解析出各個關鍵要素放到Request中

關鍵的成員變量:請求方法、URL、協議版本、頭部字段、正文

需要提供的接口:

  1. 頭部字段的插入和獲取
  2. 查詢字符串的插入和獲取
  3. 長連接和短連接的判斷
  4. 正文長度的獲取

    HttpResponse模塊

    進行業務處理的同時,向Response中填充響應要素,完畢后將其組織成為HTTO響應格式的數據,發送給客戶端

    關鍵成員變量:協議版本、響應狀態碼、狀態碼描述信息、頭部字段、響應正文

    需要提供的接口:

    1. 頭部字段的插入和獲取
    2. 長連接短連接的設置與判斷
    3. 正文的設置

    Util模塊

    用于查看文件的屬性,使用stat()只能查看已存在的文件,否則會返回0

    #include <sys/stat.h>int stat(const char *pathname, struct stat *statbuf);
    int fstat(int fd, struct stat *statbuf);
    int lstat(const char *pathname, struct stat *statbuf);
    
    • pathname:用于指定一個需要查看屬性的文件路徑。
    • buf:struct stat 類型指針,用于指向一個 struct stat 結構體變量。調用 stat 函數的時候需要傳入一個 struct stat 變量的指針,獲取到的文件屬性信息就記錄在 struct stat 結構體中。
    • 返回值:成功返回 0;失敗返回-1,并設置 error。
    
    struct stat 
    { dev_t st_dev; /* 文件所在設備的 ID */ ino_t st_ino; /* 文件對應 inode 節點編號 */ mode_t st_mode; /* 文件對應的模式 */ nlink_t st_nlink; /* 文件的鏈接數 */ uid_t st_uid; /* 文件所有者的用戶 ID */ gid_t st_gid; /* 文件所有者的組 ID */ dev_t st_rdev; /* 設備號(指針對設備文件) */ off_t st_size; /* 文件大小(以字節為單位) */ blksize_t st_blksize; /* 文件內容存儲的塊大小 */ blkcnt_t st_blocks; /* 文件內容所占塊數 */ struct timespec st_atim; /* 文件最后被訪問的時間 */ struct timespec st_mtim; /* 文件內容最后被修改的時間 */ struct timespec st_ctim; /* 文件狀態最后被改變的時間 */
    };
    

    通過向一下宏函數傳st.st_mode類型的變量能夠判斷出是否是對應文件類型

    #define S_ISLNK(m)      (((m) & S_IFMT) == S_IFLNK)
    #define S_ISREG(m)      (((m) & S_IFMT) == S_IFREG)
    #define S_ISDIR(m)      (((m) & S_IFMT) == S_IFDIR)
    #define S_ISCHR(m)      (((m) & S_IFMT) == S_IFCHR)
    #define S_ISBLK(m)      (((m) & S_IFMT) == S_IFBLK)
    #define S_ISFIFO(m)     (((m) & S_IFMT) == S_IFIFO)
    #define S_ISSOCK(m)     (((m) & S_IFMT) == S_IFSOCK)
    

    HttpRequest模塊

    HTTP請求大的分為請求行、請求頭部、空行、正文,

         請求行中包括:請求方法、URL、協議版本,URL包含:資源路徑、查詢字符串(GET /serach?word=qq&en=utf8 HTTP/1.1)請求頭部(請求頭部是key: value\r\nkey: value這樣格式的信息)Content-Length: 0\r\n(請求正文中內容的長度)
    

    Connection: keep-alive/close(不存在Connection字段就是短連接、存在但是close也是短連 接、存在了是keep-alive那就是長連接)

    空行

    正文

    綜上能夠得出HttpReques需要的要素:請求方法、資源路徑、查詢字符串、協議版本,頭部字段、正文、_matches(std::smath _matches)

    為了便于訪問,將成員變量設置為公有成員。

    所需接口

    1. 提供查詢字符串和頭部字段的查詢、獲取、插入功能(查詢字符串和頭部字段分別使用一個哈希表來存儲起來)
    2. 獲取正文長度
    3. 判斷長連接還是短連接

    HttpResponse模塊

    http響應分為響應行、頭部、空行、響應正文 ???????

    響應行包括:協議版本、狀態碼、狀態碼描述(但是HttpResponse需要存儲的關鍵性信息就一個狀態碼,狀態碼描述信息不需要存儲,可以通過狀態碼來獲取,協議版本也不需要存儲,協議版本在HttpRequest中有存儲)

    頭部字段

    響應正文

    綜上HttpResponse類中需要的成員變量有狀態碼、頭部字段、響應正文、重定向信息(重定向信息分為兩個,是否進行重定向、重定向的路徑)

    所需接口

    1. 頭部字段的添加、獲取、查詢
    2. 響應正文設置
    3. 重定向的設置
    4. 長短鏈接的判斷

    請求接收的上下文模塊

    用于記錄HTTP請求接收的進度

    有可能出現接收的數據并不是完整的HTTP請求數據,也就是請求的處理需要在多次收到數據后才能完成,因此在每次處理的時候,就需要將處理進度記錄起來,以便于下次從當前進度繼續向下處理。

    需要的成員變量

    有一個用于記錄當前這個HttpRequest處理到什么階段(接收請求行階段、接收頭部字段、接收正文階段、接收完畢階段、接收數據出錯)

    響應狀態碼(因為在請求報文解析的過程中,可能出現各種不同的問題,訪問的資源不對、沒有權限)

    已經接受并處理的請求信息

    所需要提供的接口

    提取和解析對應的部分

    返回解析完畢的請求信息

    HttpSever模塊

    #include <stdio.h> int printf(const char *format, ...); int fprintf(FILE *stream, const char *format, ...);

    本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
    如若轉載,請注明出處:http://www.pswp.cn/news/913408.shtml
    繁體地址,請注明出處:http://hk.pswp.cn/news/913408.shtml
    英文地址,請注明出處:http://en.pswp.cn/news/913408.shtml

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

    相關文章

    超詳細的私有化安裝部署Dify服務以及安裝過程中問題處理

    一、什么是Dify Dify 是一款開源的大語言模型(LLM) 應用開發平臺。它融合了后端即服務&#xff08;Backend as Service&#xff09;和 LLMOps 的理念&#xff0c;使開發者可以快速搭建生產級的生成式 AI 應用。即使你是非技術人員&#xff0c;也能參與到 AI 應用的定義和數據…

    國產DSP,QXS320F280049,QXS320F28377D,QXS320F2800137,QXS320F28034

    自定義指令集&#xff0c;自研內核架構&#xff0c;基于eclipse自研IDE&#xff0c;工具鏈&#xff0c;算法庫。 根據自研QXS320F280049&#xff0c;做了600W和2KW數字電源方案&#xff0c;1.5KW電機方案&#xff0c;目前已在市場大量投產。 QXS320F290049應用于數字電源&#…

    dotnet publish 發布后的項目,例如asp.net core mvc項目如何在ubuntu中運行,并可外部訪問

    復制到 Ubuntu 上的是使用 Visual Studio 或 dotnet publish 命令生成的 發布后的輸出文件&#xff08;publish output&#xff09;&#xff0c;而不是原始項目源代碼。在這種情況下&#xff0c;確實沒有 .csproj 文件&#xff0c;所以不能直接用 dotnet run 啟動。但你可以通過…

    Linux多線程(十二)之【生產者消費者模型】

    文章目錄生產者消費者模型為何要使用生產者消費者模型生產者消費者模型優點基于BlockingQueue的生產者消費者模型BlockingQueueC queue模擬阻塞隊列的生產消費模型單線程生產消費模型多線程生產消費模型生產者消費者模型 consumer/productor 321原則(便于記憶) 為何要使用生產…

    MySQL表的操作(3)

    文章目錄前言一、創建表創建表時指定屬性二、查看表查看表結構查看建表消息三、修改表修改列屬性修改列名修改表名四、刪除表總結前言 Hello! 那我們乘勝追擊&#xff0c;開始 表的操作&#xff01; 一、創建表 首先創建一個 數據庫 testForTable mysql> create database i…

    從“人工智障”到“智能助手”:集成為什么能拯救AI用戶體驗?

    幾年前&#xff0c;當人們滿懷期待地與AI語音助手對話時&#xff0c;常常遭遇令人啼笑皆非的回應——“抱歉&#xff0c;我不明白你在說什么”“請再說一遍”甚至答非所問。AI被戲稱為“人工智障”&#xff0c;用戶體驗一度讓人失望。然而&#xff0c;近年來&#xff0c;隨著技…

    Uniapp 自定義TabBar + 動態菜單實現教程(Vuex狀態管理詳解)

    大家好&#xff0c;我是一諾。今天跟大家分享一下uniapp 封裝自定義底部導航欄&#xff08;TabBar&#xff09; 過程中的思考和實踐。通過本文&#xff0c;你將學會如何打造一個功能完善、可自由定制的TabBar組件&#xff01; 先看效果&#xff1a; 支持自定義圖標和樣式動態顯…

    MySQL數據庫主從復制

    概述1、master開啟二進制日志記錄2、slave開啟IO進程&#xff0c;從master中讀取二進制日志并寫入slave的中繼日志3、slave開啟SQL進程&#xff0c;從中繼日志中讀取二進制日志并進行重放4、最終&#xff0c;達到slave與master中數據一致的狀態&#xff0c;我們稱作為主從復制的…

    Rancher Server + Kubernets搭建云原生集群平臺

    目錄Rancher Server Kubernets搭建云原生集群平臺一、環境準備1、軟件準備2、環境規劃3、掛載數據盤二、虛擬機初始化基礎配置&#xff08;所有節點都需要操作&#xff09;1、執行時間服務器腳本&#xff08;包括配置hostName主機名&#xff09;2、配置hosts文件3、配置各節點…

    Java學習第八部分——泛型

    目錄 一、概述 &#xff08;一&#xff09;定義 &#xff08;二&#xff09;作用 &#xff08;三&#xff09;引入原因 二、使用 &#xff08;一&#xff09;類 &#xff08;二&#xff09;接口 &#xff08;三&#xff09;方法 三、類型參數 &#xff08;一&#xf…

    定時點擊二次鼠標 定時點擊鼠標

    定時點擊二次鼠標 定時點擊鼠標 今天分享一個定時點擊兩次的小工具。 我們在生活中&#xff0c;可能會遇到一些定時點擊的任務。比如說在晚上9點去發送一個群發&#xff0c;或者倒計時點擊一個按鈕。那么可以使用這個工具&#xff0c;僅適用于Windows電腦。 #定時點擊鼠標 #倒計…

    Linux網絡配置與故障排除完全指南

    1. ifconfig命令 - 網絡接口配置器 ifconfig&#xff08;interface configurator&#xff09;是Linux系統中最基礎的網絡配置工具。該命令可以初始化網絡接口、分配IP地址、啟用或禁用接口&#xff0c;同時還能查看接口的詳細信息。 查看網絡接口信息 # ifconfig eth0 …

    Python Pytest-Benchmark詳解:精準性能測試的利器

    在軟件開發的迭代過程中&#xff0c;性能優化如同精密手術&#xff0c;需要精準的測量工具。Pytest-Benchmark作為pytest生態中的性能測試插件&#xff0c;憑借其無縫集成能力和專業統計功能&#xff0c;成為Python開發者進行基準測試的首選工具。本文將深入解析其技術特性與實…

    60天python訓練營打卡day51

    學習目標&#xff1a; 60天python訓練營打卡 學習內容&#xff1a; DAY 51 復習日 作業&#xff1a;day43的時候我們安排大家對自己找的數據集用簡單cnn訓練&#xff0c;現在可以嘗試下借助這幾天的知識來實現精度的進一步提高 學習時間&#xff1a; 2025.07.04 浙大疏錦行…

    支持向量機(SVM)在肺部CT圖像分類(肺癌檢測)中的實現與優化

    ?? 博主簡介:CSDN博客專家、CSDN平臺優質創作者,高級開發工程師,數學專業,10年以上C/C++, C#, Java等多種編程語言開發經驗,擁有高級工程師證書;擅長C/C++、C#等開發語言,熟悉Java常用開發技術,能熟練應用常用數據庫SQL server,Oracle,mysql,postgresql等進行開發應用…

    YOLOv3-SPP 深度解析:引入 SPP 結構,顯著提升目標檢測性能!

    ? YOLOv3-SPP 技術詳解 一、前言 YOLOv3-SPP 是在 YOLOv3 基礎上加入 SPP&#xff08;Spatial Pyramid Pooling&#xff09;模塊的一種改進版本&#xff0c;旨在提升模型對不同尺度目標的識別能力&#xff0c;尤其是在大目標檢測方面表現更優。 它由 Alexey Bochkovskiy 在…

    負載均衡--常見負載均衡算法

    負載均衡算法可以分為兩類&#xff1a;靜態負載均衡算法和動態負載均衡算法。 1、靜態負載均衡算法包括&#xff1a;輪詢&#xff0c;比率&#xff0c;優先權 輪詢&#xff08;Round Robin&#xff09;&#xff1a;順序循環將請求一次順序循環地連接每個服務器。當其中某個服務…

    深入解析GCC:開源的編譯器之王

    在編程世界中&#xff0c;編譯器是將人類可讀代碼轉化為機器指令的關鍵橋梁。而GCC&#xff08;GNU Compiler Collection&#xff09; 無疑是這個領域最耀眼的明星之一。作為開源世界的基石&#xff0c;它支撐著Linux內核、眾多開源項目和商業軟件的構建。今天&#xff0c;我們…

    https和http有什么區別

    目錄 一、核心區別&#xff1a;是否基于加密傳輸 二、底層傳輸機制差異 三、HTTPS 的加密原理 四、應用場景差異 五、其他細節區別 總結 在網絡通信中&#xff0c;HTTP&#xff08;Hypertext Transfer Protocol&#xff0c;超文本傳輸協議&#xff09; 和HTTPS&#xff0…

    CSS3 文本效果詳解

    CSS3 文本效果詳解 引言 隨著Web技術的發展,CSS3為前端設計師和開發者提供了豐富的文本效果選項。這些效果不僅能夠增強網頁的美觀性,還能提升用戶體驗。本文將詳細介紹CSS3中的文本效果,包括文本陰影、文本描邊、文本裝飾、文本換行、文本大小寫等,并探討如何在實際項目…