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