?🌈個人主頁:Fan_558
🔥 系列專欄:仿muduo
🌹關注我💪🏻帶你學更多知識
文章目錄
- 前言
- Buffer模塊
- Socket模塊
- 小結
前言
這章將會向你介紹仿muduo高性能服務器組件的buffer模塊與socket模塊的實現
Buffer模塊
設計思想
實現思想:
1、實現緩沖區得有一塊內存空間,采用vector,string字符串的操作遇到’\0’就停止了,網絡操作中什么樣的數據都有,'\0’可能也有,string大部分的操作都是字符串操作,所以不太行
2、記錄當前的讀取數據位置與當前的寫入數據位置,避免每次寫入數據需要重新遍歷數組找寫入讀入位置
3、考慮整體緩沖區空閑空間是否足夠 (因為讀位置也會向后偏移,前邊有可能會有空間) 足夠:則將數據(讀位置開始)移動到起始位置即可
不夠:擴容,從當前寫位置開始擴容足夠大小 數據一旦寫入成功,當前寫位置就要向后偏移
4、讀取數據/寫入數據
當前的讀取/寫入位置指向哪里,就從哪里開始讀取/寫入,前提是有數據可讀/有空間可寫,讀取/寫入完數據,讀偏移/寫偏移向后偏移
為了方便查閱
代碼如下:
class Buffer{
private:std::vector<char> _buffer; //使用vector進行內存空間管理uint64_t _reader_idx; //讀偏移uint64_t _writer_idx; //寫偏移
public:Buffer():_reader_idx(0), _writer_idx(0) ,_buffer(BUFFER_SIZE) {}//獲取_buffer起始元素的地址char* begin() {return &*_buffer.begin();}//獲取當前寫入起始地址(_buffer的空間起始地址,加上寫偏移量char* WritePos() { return begin() + _writer_idx; }//獲取當前讀取起始地址(_buffer的空間起始地址,加上讀偏移量char* ReadPos() { return begin() + _reader_idx; }//獲取緩沖區末尾空閑空閑大小--寫偏移之后的空閑空間uint64_t TailIdleSize() {return _buffer.size() - _writer_idx; }//獲取緩沖區起始地址空閑空間大小--讀偏移之前的空閑空間uint64_t HeadIdleSize() {return _reader_idx; }//獲取可讀數據大小uint64_t ReadAbleSize() {return _writer_idx - _reader_idx; }//讀取數據后,將讀偏移向后移動void MoveReadOffest(uint64_t len) { //向后移動的大小,必須小于可讀數據大小assert(len <= ReadAbleSize());_reader_idx += len; }//寫入數據后,將寫偏移向后移動void MoveWriteOffest(uint64_t len) { _writer_idx += len; }//確保可寫空間足夠(整體空閑空間夠了就移動數據,否則就擴容)void EnsureWriteSpace(uint64_t len){//如果末尾空閑空間大小足夠,直接返回if(len < TailIdleSize()) return;//如果不夠,判斷加上起始位置的空閑空間大小是否足夠,夠了就將可讀數據移動到起始位置else if(len <= HeadIdleSize() + TailIdleSize()) {uint64_t sz = ReadAbleSize(); //可讀數據大小_reader_idx = 0; //更新讀偏移_writer_idx = sz; //更新寫偏移return;}//總體空間不夠,則需要擴容,不移動數據,直接給寫偏移之后擴容足夠空間即可else _buffer.resize(_writer_idx + len);}//寫入數據void Write(const void* data, uint64_t len){//保證是否有足夠空間EnsureWriteSpace(len);const char* d = (const char* )data;//拷貝數據到buffer當中std::copy(d, d + len, WritePos());}void WriteAndPush(const void* data, uint64_t len){Write(data, len);MoveWriteOffest(len);}//寫入一個字符串void WriteString(const std::string &data){Write(data.c_str(), data.size());}//向buffer中寫入一個字符串并向后移動writevoid WriteStringAndPush(const std::string &data){WriteString(data);MoveWriteOffest(data.size());}//把一個buffer類型的數據寫入void WriteBuffer(Buffer &data){Write(data.ReadPos(), data.ReadAbleSize());}//向buffer中寫入一個并向后移動writevoid WriteBufferAndPush(Buffer &data){WriteBuffer(data);MoveWriteOffest(data.ReadAbleSize());}//讀取數據void Read(void* buf, uint64_t len){assert(len <= ReadAbleSize());//保持參數類型一致std::copy(ReadPos(), ReadPos() + len, (char*)buf);}void ReadAndPop(void* buf, uint64_t len){Read(buf, len);MoveReadOffest(len);}//把讀取的數據當作一個string返回 std::string ReadAsString (uint64_t len){assert(len <= ReadAbleSize());std::string str;str.resize(len);//從緩沖區中讀取長度為len的數據,并將其存儲到字符串str的內存地址開始處的位置Read(&str[0], len);return str;}//讀取一個string并向后移動(確保下一次不會重復讀取)std::string ReadAsStringAndPop(uint64_t len){assert(len <= ReadAbleSize());std::string str = ReadAsString(len);MoveReadOffest(len);return str;}/*由于后面我們的高并發服務器會支持應用層協議的HTTP,而在HTTP協議中通常就是讀取一行的數據,因為請求行和請求報頭以及響應行和響應報頭都是以\r\n作為分隔符的,都是一行行的數據所以我們的緩沖區也提供一個查找換行字符的位置*/char* FindCRLF(){//在可讀數據范圍內查找第一個出現的換行符的位置char* res = (char*)memchr(ReadPos(), '\n', ReadAbleSize());return res;}//獲取一行數據std::string Getline(){char* pos = FindCRLF();if(pos == nullptr) return "";/*將換行符\n前的數據讀出,+1:包括換行符(不然的話下一次再查找,換行符就在開頭) */return ReadAsString(pos - ReadPos() + 1); }//讀出一行數據后,將讀偏移向后移std::string GetLineAndPop(){std::string str = Getline();MoveReadOffest(str.size());return str;}//清空緩沖區void clear(){//只需要將偏移量歸零_writer_idx = _reader_idx = 0;}
};
Socket模塊
設計思想:
在該模塊當中除了對socket套接字原有的操作進行封裝,還提供了直接創建服務端和客戶端連接的接口
為了方便查閱
代碼如下
#define MAX_LISTEN 1024
class Socket{private:int _sockfd;public:Socket():_sockfd(-1){}Socket(int fd):_sockfd(fd){}//關閉套接字~Socket() { Close(); }int Fd(){return _sockfd;}//創建套接字bool Create(){//int socket(int domain, int type, int protocol) AF_INET: 表示使用ipv4地址族 SOCK_STREM: 表示創建面向連接的套接字類(TCP) IPPROTO_TCP: 表示使用TCP協議_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if(_sockfd < 0){ERR_LOG("CREATE SOCKET FAILEDQ!");return false;}return true;}//綁定地址信息bool Bind(const std::string &ip, uint16_t port){struct sockaddr_in addr;addr.sin_family = AF_INET; //ipv4地址域類型addr.sin_port = htons(port); //將端口號通過主機轉網絡字節序addr.sin_addr.s_addr = inet_addr(ip.c_str()); //將IP地址轉化為網絡字節序的32位ipv4地址socklen_t len = sizeof(struct sockaddr_in);//int bind(int socket, const struct sockaddr *addr. socklen_t addrlen);int ret = bind(_sockfd, (struct sockaddr*)&addr, len);if(ret < 0){ERR_LOG("BIND ADDRESS FAILEDQ!");return false;}return true;}//開始監聽bool Listen(int backlog = MAX_LISTEN){int ret = listen(_sockfd, backlog);if(ret < 0){ERR_LOG("SOCKET LISTEN FAILED!");return false;}return true;}//向服務器發起連接(傳入服務器的ip和端口信息)bool Connect(const std::string &ip, uint16_t port){//int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr(ip.c_str());socklen_t len = sizeof(struct sockaddr_in);int ret = connect(_sockfd, (struct sockaddr*)&addr, len);if(ret < 0){ERR_LOG("CONNECT SERVER FAILEDQ!");return false;}return true;}//監聽有新連接后,獲取新連接(返回一個文件描述符)int Accept() {int newfd = accept(_sockfd, nullptr, nullptr);if(newfd < 0){ERR_LOG("SOCKET ACCEPT FAILED!");return -1;}return newfd;}//接收數據(ssize_t為有符號整數,size_t無符號整數,默認0為阻塞操作)ssize_t Recv(void* buf, size_t len, int flag = 0){ssize_t ret = recv(_sockfd, buf, len, flag);if(ret <= 0){//EAGAIN 當前socket的接收緩沖區中沒有數據了,在非阻塞的情況下才會有這個錯誤//EINTR 當前socket的阻塞等待被信號打斷了if(errno == EAGAIN || errno == EINTR)return 0;else{ERR_LOG("SOCKET RECV FAILED");return -1;}}return ret; //返回實際接收的數據長度}ssize_t NonBlockRecv(void* buf, size_t len){return Recv(buf, len, MSG_DONTWAIT); // MSG_DONTWAIT 表示當前接收為非阻塞}//發送數據ssize_t Send(const void* buf, size_t len, int flag = 0){ssize_t ret = send(_sockfd, buf, len, flag);if(ret < 0){if(errno == EAGAIN || errno == EINTR){return 0;}ERR_LOG("SOCKET RECV FAILED");return -1;}return ret; //返回實際發送的數據長度}ssize_t NonBlockSend(void* buf, size_t len){Send(buf, len, MSG_DONTWAIT); // MSG_DONTWAIT 表示當前接收為非阻塞}//關閉套接字void Close(){if(_sockfd != -1){close(_sockfd);_sockfd = -1;}}//創建一個服務端連接bool CreateServer(uint16_t port, const std::string &ip = "0.0.0.0", bool block_flag = false){if(Create()==false) return false;//是否啟動非阻塞if(block_flag) NonBlock();if(Bind(ip, port) == false) return false;if(Listen() == false) return false;ReuseAddress();return true;}//創建一個客戶端連接bool CreateClient(uint16_t port, const std::string &ip){if(Create() == false) return false;if(Connect(ip, port) == false) return false;return true;}//設置套接字選項---開啟地址端口重用void ReuseAddress(){// int setsockopt(int fd, int leve, int optname, void *val, int vallen)int val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&val, sizeof(int));val = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEPORT, (void*)&val, sizeof(int));}//設置套接字阻塞屬性---設置為非阻塞void NonBlock(){//int fcntl(int fd, int cmd, ... /* arg */ );int flag = fcntl(_sockfd, F_GETFL, 0);fcntl(_sockfd, F_SETFL, flag | O_NONBLOCK);}
};
小結
今日的項目分享就到這里啦,下一篇將會向你介紹Channel與Poller模塊