ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

C++ 仿muduo高并发服务器:Socket模块

C++ 仿muduo高并发服务器:Socket模块 C 仿muduo高并发服务器Socket模块1. 为什么需要封装 SocketLinux 原生 socket API 是一组 C 函数直接使用存在以下问题参数复杂容易出错如sockaddr类型转换、字节序转换。资源管理麻烦fd 需要手动close。错误处理重复每个调用都要判断返回值、打印日志。阻塞/非阻塞、地址重用等选项设置分散。封装成Socket类后提供面向对象的接口语义清晰。利用 RAII 自动关闭 fd需完善析构函数。统一错误日志输出。方便在 Reactor 模型中作为基础组件使用。2. 类定义概览#defineMAX_LISTEN1024classSocket{private:int_sockfd;public:Socket(){}Socket(intfd):_sockfd(fd){}~Socket(){}intFd(){return_sockfd;}boolCreate();boolBind(conststd::stringip,uint16_tport);boolListen(intbacklogMAX_LISTEN);boolConnect(conststd::stringip,uint16_tport);intAccept();ssize_tRecv(void*buf,size_t len,intflag0);ssize_tNonBlockRecv(void*buf,size_t len);ssize_tSend(constvoid*buf,size_t len,intflag0);ssize_tNonBlockSend(void*buf,size_t len);voidClose();boolCreaterServer(uint16_tport,conststd::stringip0.0.0.0,boolblock_flagfalse);boolCreateClient(uint16_tport,conststd::stringip);voidReuseAddress();voidNonBlock();};3. 成员变量与构造/析构int_sockfd;_sockfd套接字文件描述符-1表示无效。Socket():_sockfd(-1){}Socket(intfd):_sockfd(fd){}~Socket(){}析构函数应调用Close()自动关闭 fd当前为空存在资源泄漏风险。改进建议后期我们再加上~Socket(){Close();}4. 核心函数详解4.1Create()创建套接字boolCreate(){_sockfdsocket(AF_INET,SOCK_STREAM,IPPROTO_TCP);if(_sockfd0){ERR_LOG(CREATE SOCKET FAILED!!);returnfalse;}returntrue;}AF_INETIPv4 协议族。SOCK_STREAMTCP 流式套接字。IPPROTO_TCPTCP 协议。成功返回非负 fd失败返回-1。4.2Bind()绑定地址boolBind(conststd::stringip,uint16_tport){structsockaddr_inaddr;addr.sin_familyAF_INET;addr.sin_porthtons(port);// 端口转网络字节序addr.sin_addr.s_addrinet_addr(ip.c_str());// IP 转网络字节序socklen_t lensizeof(structsockaddr_in);intretbind(_sockfd,(structsockaddr*)addr,len);if(ret0){ERR_LOG(BIND ADDRESS FAILED!);returnfalse;}returntrue;}htons主机字节序 → 网络字节序16 位。inet_addr点分十进制 IP → 网络字节序 32 位整数。服务器必须bind客户端通常不需要。4.3Listen()开始监听boolListen(intbacklogMAX_LISTEN){intretlisten(_sockfd,backlog);if(ret0){ERR_LOG(SOCKET LISTEN FAILED!);returnfalse;}returntrue;}backlog已完成连接队列的最大长度默认MAX_LISTEN1024。将主动套接字变为被动监听套接字内核开始接受连接。4.4Connect()客户端发起连接boolConnect(conststd::stringip,uint16_tport){structsockaddr_inaddr;addr.sin_familyAF_INET;addr.sin_porthtons(port);addr.sin_addr.s_addrinet_addr(ip.c_str());socklen_t lensizeof(structsockaddr_in);intretconnect(_sockfd,(structsockaddr*)addr,len);if(ret0){ERR_LOG(CONNECT SERVER FAILED!);returnfalse;}returntrue;}向服务器发起 TCP 连接触发三次握手。阻塞模式下会等待连接完成。4.5Accept()接受新连接intAccept(){intnewfdaccept(_sockfd,NULL,NULL);if(newfd0){ERR_LOG(SOCKET ACCEPT FAILED!);return-1;}returnnewfd;}从已完成连接队列取出一个连接返回新的通信 fd。监听 fd 继续监听新 fd 用于和客户端通信。非阻塞模式下无连接时返回-1errno为EAGAIN。4.6Recv()与NonBlockRecv()ssize_tRecv(void*buf,size_t len,intflag0){ssize_t retrecv(_sockfd,buf,len,flag);if(ret0){if(errnoEAGAIN||errnoEINTR){return0;// 暂时无数据}ERR_LOG(SCOKET RECV FAILED!);// 拼写错误SCOKET - SOCKETreturn-1;}returnret;}ssize_tNonBlockRecv(void*buf,size_t len){returnRecv(buf,len,MSG_DONTWAIT);}recv返回实际接收字节数。ret 0对端关闭连接。ret 0EAGAIN/EWOULDBLOCK非阻塞下无数据。EINTR被信号中断。其他真正错误。MSG_DONTWAIT本次接收非阻塞不改变套接字整体属性。4.7Send()与NonBlockSend()ssize_tSend(constvoid*buf,size_t len,intflag0){ssize_t retsend(_sockfd,buf,len,flag);if(ret0){ERR_LOG(SOCKET SEND FAILED!);return-1;}returnret;}ssize_tNonBlockSend(void*buf,size_t len){returnSend(buf,len,MSG_DONTWAIT);}返回实际发送字节数。非阻塞下可能只发送部分数据需要应用层缓冲剩余数据。SIGPIPE信号需忽略否则向已关闭连接发送会崩溃。4.8Close()关闭套接字voidClose(){if(_sockfd!-1)close(_sockfd);_sockfd-1;}关闭 fd置为-1防止重复关闭。4.9CreaterServer()一键创建服务端boolCreaterServer(uint16_tport,conststd::stringip0.0.0.0,boolblock_flagfalse){if(Create()false)returnfalse;if(block_flag)NonBlock();// 注意block_flag 为 true 时反而设非阻塞命名相反if(Bind(ip,port)false)returnfalse;if(Listen()false)returnfalse;ReuseAddress();// 建议移到 Bind 之前returntrue;}默认ip 0.0.0.0监听所有网卡。block_flag命名有歧义实际true表示非阻塞。ReuseAddress()应在Bind()前调用。4.10CreateClient()一键创建客户端boolCreateClient(uint16_tport,conststd::stringip){if(Create()false)returnfalse;if(Connect(ip,port)false)returnfalse;returntrue;}4.11ReuseAddress()地址端口重用voidReuseAddress(){intval1;setsockopt(_sockfd,SOL_SOCKET,SO_REUSEADDR,(void*)val,sizeof(int));val1;setsockopt(_sockfd,SOL_SOCKET,SO_REUSEPORT,(void*)val,sizeof(int));}SO_REUSEADDR允许绑定处于TIME_WAIT的端口服务器可立即重启。SO_REUSEPORT允许多个套接字绑定相同 IP:端口实现负载均衡。val 1表示开启选项0表示关闭。SOL_SOCKET通用套接字层选项。4.12NonBlock()设置非阻塞voidNonBlock(){intflagfcntl(_sockfd,F_GETFL,0);fcntl(_sockfd,F_SETFL,flag|O_NONBLOCK);}获取当前文件状态标志加上O_NONBLOCK再设置回去。非阻塞是 Reactor 模型的基础防止accept/recv/send阻塞 EventLoop 线程。5. 关键知识点知识点说明字节序转换htons/htonl主机转网络ntohs/ntohl网络转主机地址转换inet_addr字符串转网络字节序推荐inet_pton错误处理errnoEAGAIN/EWOULDBLOCK表示非阻塞无数据EINTR表示被信号中断阻塞 vs 非阻塞非阻塞配合 epoll避免线程卡死地址重用SO_REUSEADDR解决 TIME_WAITSO_REUSEPORT支持多进程监听RAII析构自动关闭 fd避免资源泄漏SIGPIPE需忽略防止向已关闭连接发送数据导致进程退出6. 使用示例6.1 服务端#includeserver.hppintmain(){Socket lst_sock;lst_sock.CreaterServer(8500);// 默认阻塞while(1){intnewfdlst_sock.Accept();if(newfd0)continue;Socketcli_sock(newfd);charbuf[1024]{0};intretcli_sock.Recv(buf,1023);if(ret0){cli_sock.Close();continue;}cli_sock.Send(buf,ret);cli_sock.Close();}lst_sock.Close();return0;}6.2 客户端#includeserver.hppintmain(){Socket cli_sock;cli_sock.CreateClient(8500,127.0.0.1);std::string strhello;cli_sock.Send(str.c_str(),str.size());charbuf[1024]{0};cli_sock.Recv(buf,1023);DBG_LOG(%s,buf);return0;}7. 总结Socket类是对 Linux 原生 socket API 的轻量级封装核心接口创建Create绑定Bind监听Listen连接Connect接受Accept收发Recv/Send支持非阻塞版本关闭Close辅助ReuseAddress、NonBlock
返回列表