現(xiàn)高并發(fā)服務(wù)器——端對(duì)端Socket.hpp)
目錄摘要注釋3.1 回顧3.2 新知識(shí)3.3 注意3.4 反思正文4.1 生命周期與返回值約定4.2 服務(wù)端路徑4.3 收發(fā)與阻塞4.4 開(kāi)發(fā)中遇到的問(wèn)題總結(jié)常見(jiàn)問(wèn)題標(biāo)簽摘要Socket.hpp 是 header-only 的單句柄 TCP 封裝唯一成員為int _sockfd默認(rèn)構(gòu)造置 -1析構(gòu)close后復(fù)位。對(duì)外提供Create / Bind / Listen / Accept / Connect / Recv / Send / Close另有SetNonBlock、SetNonBlockRecv / SetNonBlockSend與CreateServer / CreateClient兩個(gè)組合入口。價(jià)值在于把系統(tǒng)調(diào)用返回值判定、sockaddr_in填充與字節(jié)序轉(zhuǎn)換收在一處。注釋段自述本實(shí)現(xiàn)保證 HTTP 請(qǐng)求的服務(wù)端而非客戶端。代碼倉(cāng)庫(kù)https://gitee.com/btshadow/Ubuntu_learning/blob/master/October/10-17/muduo/src/server.hpp注釋3.1 回顧htons/ntohsTCP/IP 規(guī)定用大端序即網(wǎng)絡(luò)字節(jié)序本機(jī)算出要發(fā)到網(wǎng)絡(luò)用htons/htonl從網(wǎng)絡(luò)收到轉(zhuǎn)本機(jī)用ntohs/ntohl。Bind與Connect里的htons(port)屬于服務(wù)端自身綁定、要讓客戶端看到該端口的那一側(cè)。accept 二、三參數(shù)第二參數(shù)是輸出參數(shù)存放對(duì)端地址第三參數(shù)是值-結(jié)果參數(shù)傳入時(shí)為 addr 緩沖區(qū)大小返回時(shí)為實(shí)際寫(xiě)入長(zhǎng)度見(jiàn) bug3。ET 模式epoll 的邊緣觸發(fā)只有狀態(tài)變化從無(wú)到有/從有到多時(shí)通知一次對(duì)比 LT。本文件未含 epoll僅記錄。3.2 新知識(shí)noexcept(true)等價(jià)于noexcept是不拋異常的顯式聲明內(nèi)部真拋了異常則直接std::terminate()。承諾的是不拋 C 異常不是調(diào)用一定成功。價(jià)值在于移動(dòng)構(gòu)造/移動(dòng)賦值標(biāo) noexcept 后std::vector等容器擴(kuò)容走移動(dòng)而非拷貝。只在確實(shí)不拋處用移動(dòng)構(gòu)造、swap、析構(gòu)、getter可能分配內(nèi)存或拋業(yè)務(wù)異常的不標(biāo)。本文件中帶 noexcept 的只有注釋抄錄的listen原型。3.3 注意sockaddr_in在 bind 與 connect 前有必要清空Bind、Connect各有memsetaccept 時(shí)沒(méi)必要因?yàn)?accept 內(nèi)是覆蓋式寫(xiě)入Accept未 memset。EAGAIN與EINTR作為讀/寫(xiě)異常EAGAIN 關(guān)聯(lián)非阻塞MSG_DONTWAITEINTR 關(guān)聯(lián)信號(hào)與非阻塞關(guān)系小Recv、Send的 errno 判定。客戶端無(wú)需 bind客戶端間各自獨(dú)立且難以手動(dòng)管理內(nèi)核已代做故CreateClient只有 Create Connect。3.4 反思測(cè)試時(shí)太大意了主要是不想測(cè)試、想快點(diǎn)結(jié)束結(jié)果代碼本身沒(méi)有 bug卻因測(cè)試方式以為有 bug。正文4.1 生命周期與返回值約定Socket()置_sockfd -1Socket(int)接管外部 fdFd()取值析構(gòu)close后置 -1。Close()只close不復(fù)位_sockfd故析構(gòu)時(shí)會(huì)對(duì)同一 fd 再close一次。每個(gè)系統(tǒng)調(diào)用失敗路徑都走ERR_LOG成功的組合入口用INF_LOG。4.2 服務(wù)端路徑CreateServer(port, ip 0.0.0.0)依次Create→Bind→Listen成功后INF_LOG并調(diào)SetReuseAddrPort。Create用socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)Bind先memset再填sin_family、htons(port)地址由inet_pton寫(xiě)入sin_addr舊的inet_addr寫(xiě)法留在注釋中Listen缺省MAX_LISTEN 1024。SetReuseAddrPort對(duì)SO_REUSEADDR與SO_REUSEPORT各setsockopt一次位置在 bind/listen 之后失敗只記錄。4.3 收發(fā)與阻塞Recv/Send直接轉(zhuǎn)發(fā)系統(tǒng)調(diào)用n 0時(shí)若 errno 為EAGAIN或EINTR返回 0否則ERR_LOG后返回 -1Send另有n 0的獨(dú)立日志分支。SetNonBlockRecv/SetNonBlockSend以MSG_DONTWAIT調(diào)用。SetNonBlock用F_GETFL取標(biāo)志再F_SETFL | O_NONBLOCK??蛻舳朔较駽reateClient只有 Create 與 Connect不含 BindConnect同樣 memset、htons、inet_pton。4.4 開(kāi)發(fā)中遇到的問(wèn)題bug1INADDR_ANY是 0 不是字符串極易誤會(huì)注釋記 [30min]。原寫(xiě)法const std::string ip INADDR_ANY已注釋掉現(xiàn)默認(rèn)值為0.0.0.0仍交給inet_pton轉(zhuǎn)換。bug2測(cè)試時(shí)注意監(jiān)聽(tīng)套接字不能用于通信。標(biāo)注位于Send()內(nèi)。bug3accept 的第三參數(shù)同時(shí)作為輸入輸出參數(shù)調(diào)用前須給出緩沖區(qū)大小// Accept()// 原socklen_t len;socklen_t lensizeof(client_addr);總結(jié)封裝邊界是套接字生命周期加系統(tǒng)調(diào)用的返回判定三條注意分別落在Bind/Connect的 memset、Recv/Send的 errno 分支、CreateClient不 Bindbug3 正是值-結(jié)果參數(shù)未傳初值。bug1 屬類型不匹配bug2 屬套接字角色混用。常見(jiàn)問(wèn)題為何Bind、Connect要 memset 而Accept不用前兩者是值傳入Accept內(nèi)覆蓋式寫(xiě)入client_addr無(wú)需清空。為何INADDR_ANY不能當(dāng)std::string默認(rèn)參數(shù)它是 0 而非字符串bug1現(xiàn)用0.0.0.0經(jīng)inet_pton轉(zhuǎn)換。Recv/Send中EAGAIN與EINTR為何都返回 0二者都不視為致命錯(cuò)誤交由上層下輪再試。bug3 中 len 不初始化會(huì)怎樣accept把它當(dāng)輸入緩沖區(qū)大小未給值即未定義故須socklen_t len sizeof(client_addr)。標(biāo)簽C、TCP Socket 封裝、inet_pton、EAGAIN/EINTR、header-only【統(tǒng)計(jì)全文約 1180 字 / 原代碼 256 行 / 比例約 4.6注釋字段小節(jié) 4 個(gè) / 成文 bug 3 處】