muduo库-Socket
Socket类
Socket类是socket 文件描述符(sock
fd)的一个轻量级封装,提供操作底层sock
fd的常用方法。采用RTII方式管理sock fd,但本身并不创建sock
fd,也不打开它,只负责关闭。
提供的public方法主要包括:获取tcp协议栈信息(tcp_info);绑定ip地址(bind);监听套接字(listen);接收连接请求(accept);关闭连接写方向(shutdown),等等。
note
Socket并不提供close sock
fd的public方法,因为析构时,调用close关闭套接字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 class Socket : noncopyable {public : explicit Socket (int sockfd) : sockfd_(sockfd) { } ~Socket (); int fd () const { return sockfd_; } bool getTcpInfo (struct tcp_info*) const ; bool getTcpInfoString (char * buf, int len) const ; void bindAddress (const InetAddress& addr) ; void listen () ; int accept (InetAddress* peeraddr) ; void shutdownWrite () ; void setTcpNoDelay (bool on) ; void setReuseAddr (bool on) ; void setReusePort (bool on) ; void setKeepAlive (bool on) ;private : const int sockfd_; };
其实现主要转交给SocketsOps包装库函数后的包装函数。
Socket的构造与析构
Socket构造和析构很简单:
1 2 3 4 5 6 7 8 explicit Socket (int sockfd) : sockfd_(sockfd) { } Socket::~Socket () { sockets::close (sockfd_); }
Socket类不创建sockfd,其含义取决于构造Socket对象的调用者。如果是由调用socket(2)创建的sockfd,那就是本地套接字;如果是由accept(2)返回的sockfd,那就是accepted
socket,代表一个连接。
例如,Acceptor持有的Socket对象,是由socket(2)创建的,代表一个套接字;
TcpConnection只有一个Socket对象,是由TcpServer在新建TcpConnection对象时传入,而由Acceptor::handleRead()中通过Socket::accept()创建的sockfd参数的实参。
Socket获取Tcp协议栈信息
利用getsockopt + TCP_INFO选项,获取tcp协议栈信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 bool Socket::getTcpInfo (struct tcp_info *tcpi) const { socklen_t len = sizeof (*tcpi); memZero (tcpi, len); return ::getsockopt (sockfd_, SOL_TCP, TCP_INFO, tcpi, &len) == 0 ; }bool Socket::getTcpInfoString (char * buf, int len) const { struct tcp_info tcpi; bool ok = getTcpInfo (&tcpi); if (ok) { snprintf (buf, static_cast <size_t >(len), "unrecovered=%u " "rto=%u ato=%u snd_mss=%u rcv_mss=%u " "lost=%u retrans=%u rtt=%u rttvar=%u " "sshthresh=%u cwnd=%u total_retrans=%u" , tcpi.tcpi_retransmits, tcpi.tcpi_rto, tcpi.tcpi_ato, tcpi.tcpi_snd_mss, tcpi.tcpi_rcv_mss, tcpi.tcpi_lost, tcpi.tcpi_retrans, tcpi.tcpi_rtt, tcpi.tcpi_rttvar, tcpi.tcpi_snd_ssthresh, tcpi.tcpi_snd_cwnd, tcpi.tcpi_total_retrans ); } return ok; }
其他常用接口
比如bindAddress、listen、accept等,与基础网络编程接口bind、listen、accept类似,不过由SocketOps进行轻度包裹。
值得一提的是setTcpNoDelay(),用于设置TCP_NODELAY选项,以禁用Nagle算法,从而不会等到收到ACK才进行下一次数据发送,而是tcp协议栈缓冲存中有数据就立即发送。适用于对性能要求较高的情况。
InetAddress类
InetAddress类对地址信息进行了包装,是sockaddr_in的包装类。
既然是表示ip地址,可以直接用sockaddr_in,为什么要用InetAddress重新包装一下?
因为表示IPv4地址的sockaddr_in是C语言数据类型,并不包含对数据类型的操作,另外,支持IPv6的地址结构是sockaddr_in6。
如果直接使用C风格的sockaddr_in,那么其他类要用来表示地址,不得不使用大量底层C接口。而使用C++
类InetAddress包装sockaddr_in/sockaddr_in6,提供必要的C++接口,可以有效解决参数兼容问题。
InetAddress 声明如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 class InetAddress : public muduo::copyable {public : explicit InetAddress (uint16_t portArg = 0 , bool loopbackOnly = false , bool ipv6 = false ) ; InetAddress (StringArg ip, uint16_t portArg, bool ipv6 = false ); explicit InetAddress (const struct sockaddr_in& addr) : addr_(addr) { } explicit InetAddress (const struct sockaddr_in6& addr) : addr6_(addr) { } sa_family_t family () const { return addr_.sin_family; } string toIp () const ; string toIpPort () const ; uint16_t port () const ; const struct sockaddr * getSockAddr () const { return sockets::sockaddr_cast (&addr6_); } void setSockAddrInet6 (const struct sockaddr_in6& addr6) { addr6_ = addr6; } uint32_t ipv4NetEndian () const ; uint16_t portNetEndian () const { return addr_.sin_port; } static bool resolve (StringArg hostname, InetAddress* result) ; void setScopedId (uint32_t scope_id) ;private : union { struct sockaddr_in addr_; struct sockaddr_in6 addr6_; }; };
InetAddress是值语义的,便于在传递时拷贝。数据成员是一个union,对于IPv4,使用addr_;对于IPv6,则使用addr6_。
5个静态断言(static_assert)确保数据成员大小及联合体内部位段偏移,因为后面会直接将sockaddr_in6转换为sockaddr_in。
InetAddress构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 static const in_addr_t kInaddrAny = INADDR_ANY;static const in_addr_t kInaddrLoopback = INADDR_LOOPBACK; InetAddress::InetAddress (uint16_t portArg, bool loopbackOnly, bool ipv6) { static_assert (offsetof (InetAddress, addr6_) == 0 , "addr6_ offset 0" ); static_assert (offsetof (InetAddress, addr_) == 0 , "addr_ offset 0" ); if (ipv6) { memZero (&addr6_, sizeof (addr6_)); addr6_.sin6_family = AF_INET6; in6_addr ip = loopbackOnly ? in6addr_loopback : in6addr_any; addr6_.sin6_addr = ip; addr6_.sin6_port = sockets::hostToNetwork16 (portArg); } else { memZero (&addr_, sizeof (addr_)); addr_.sin_family = AF_INET; in_addr_t ip = loopbackOnly ? kInaddrLoopback : kInaddrAny; addr_.sin_addr.s_addr = sockets::hostToNetwork32 (ip); addr_.sin_port = sockets::hostToNetwork16 (portArg); } } InetAddress::InetAddress (StringArg ip, uint16_t portArg, bool ipv6) { if (ipv6 || strchr (ip.c_str (), ':' )) { memZero (&addr6_, sizeof (addr6_)); sockets::fromIpPort (ip.c_str (), portArg, &addr6_); } else { memZero (&addr_, sizeof (addr_)); sockets::fromIpPort (ip.c_str (), portArg, &addr_); } }explicit InetAddress (const struct sockaddr_in& addr) : addr_(addr) { }explicit InetAddress (const struct sockaddr_in6& addr) : addr6_(addr) { }
将IP地址信息转换为字符串
将IP地址、端口号转换为字符串形式,这种打印log、debug的时候,是需要常用的方法,可以调用toIp(),
toIpPort()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 string InetAddress::toIp () const { char buf[64 ] = "" ; sockets::toIp (buf, sizeof (buf), getSockAddr ()); return buf; }string InetAddress::toIpPort () const { char buf[64 ] = "" ; sockets::toIpPort (buf, sizeof (buf), getSockAddr ()); return buf; }
将主机名或IPv4地址转换为InetAddress结构对象
可以用InetAddress::resolve
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static __thread char t_resolveBuffer[64 * 1024 ]; bool InetAddress::resolve (StringArg hostname, InetAddress *out) { assert (out != NULL ); struct hostent hent; struct hostent * he = NULL ; int herrno = 0 ; memZero (&hent, sizeof (hent)); int ret = gethostbyname_r (hostname.c_str (), &hent, t_resolveBuffer, sizeof (t_resolveBuffer), &he, &herrno); if (ret == 0 && he != NULL ) { assert (he->h_addrtype == AF_INET && he->h_length == sizeof (uint32_t )); out->addr_.sin_addr = *reinterpret_cast <struct in_addr*>(he->h_addr); return true ; } else { if (ret) { LOG_SYSERR << "InetAddress::resolve" ; } } return false ; }
SocketsOps
SocketsOps准确来说是一个模块,而不是一个class,在sockets命名空间封装了系统底层提供的socket操作,比如socket(),
bind(), listen(), accept(), connect(), close(), read(), readv(),
write(), close(), shutdown()等等。
包裹函数的主要意义是为函数提供基本的出错处理,避免每次调用都要重写一次异常处理,使之更容易融入程序的框架。
我把包裹的函数分为4类:
基础的sock fd操作
便于交互的转换操作
地址类型转型
协议栈信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 int createNonblockingOrDie (sa_family_t family) ;int connect (int sockfd, const struct sockaddr* addr) ;void bindOrDie (int sockfd, const struct sockaddr* addr) ;void listenOrDie (int sockfd) ;int accept (int sockfd, struct sockaddr_in6* addr) ;ssize_t read (int sockfd, void * buf, size_t count) ;ssize_t readv (int sockfd, const struct iovec* iov, int iovcnt) ;ssize_t write (int sockfd, const void * buf, size_t count) ;void close (int sockfd) ;void shutdownWrite (int sockfd) ;void toIpPort (char * buf, size_t size, const struct sockaddr* addr) ;void toIp (char * buf, size_t size, const struct sockaddr* addr) ;void fromIpPort (const char * ip, uint16_t port, struct sockaddr_in* addr) ;void fromIpPort (const char * ip, uint16_t port, struct sockaddr_in6* addr) ;const struct sockaddr * sockaddr_cast (const struct sockaddr_in* addr);const struct sockaddr * sockaddr_cast (const struct sockaddr_in6* addr);struct sockaddr * sockaddr_cast (struct sockaddr_in6* addr);const struct sockaddr_in * sockaddr_in_cast (const struct sockaddr* addr);const struct sockaddr_in6 * sockaddr_in6_cast (const struct sockaddr* addr);int getSocketError (int sockfd) ;struct sockaddr_in6 getLocalAddr (int sockfd);struct sockaddr_in6 getPeerAddr (int sockfd);bool isSelfConnect (int sockfd) ;
createNonblockingOrDie()函数
创建非阻塞sock fd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int sockets::createNonblockingOrDie (sa_family_t family) {#if VALGRIND int sockfd = ::socket (family, SOCK_STREAM, IPPROTO_TCP); if (sockfd < 0 ) { LOG_SYSFATAL << "sockets::createNonblockingOrDie" ; } setNonBlockAndCloseOnExec (sockfd);#else int sockfd = ::socket (family, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP); if (sockfd < 0 ) { LOG_SYSFATAL << "sockets::createNonblockingOrDie" ; }#endif return sockfd; }
connect()函数
请求连接服务器端addr
1 2 3 4 int sockets::connect (int sockfd, const struct sockaddr *addr) { return ::connect (sockfd, addr, static_cast <socklen_t >(sizeof (struct sockaddr_in6))); }
bindOrDie()函数
绑定sock fd与本地地址addr
1 2 3 4 5 6 7 8 void sockets::bindOrDie (int sockfd, const struct sockaddr *addr) { int ret = ::bind (sockfd, addr, static_cast <socklen_t >(sizeof (struct sockaddr_in6))); if (ret < 0 ) { LOG_SYSFATAL << "sockets::bindOrDie" ; } }
listenOrDie()函数
监听本地sock
fd。如果协议支持重传(如TCP协议),那么listen第二个参数backlog会被忽略。
1 2 3 4 5 6 7 8 void sockets::listenOrDie (int sockfd) { int ret = ::listen (sockfd, SOMAXCONN); if (ret < 0 ) { LOG_SYSFATAL << "sockets::listenOrDie" ; } }
accept函数
接受连接请求。被包裹函数accept或accep4,其区别为:accept4一次调用能同时指定SOCK_NONBLOCK和SOCK_CLOEXEC选项;如果要用accept,则还需要额外调用setNonBlockAndCloseOnExec(),来设置sock
fd的non-block、close-on-exec属性。
accept调用出错时,跟log记录错误号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 int sockets::accept (int sockfd, struct sockaddr_in6 *addr) { socklen_t addrlen = static_cast <socklen_t >(sizeof (*addr));#if VALGRIND || defined(NO_ACCEPT4) int connfd = ::accept (sockfd, sockaddr_cast (addr), &addrlen); setNonBlockAndCloseOnExec (connfd);#else int connfd = ::accept4 (sockfd, sockaddr_cast (addr), &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);#endif if (connfd < 0 ) { int savedErrno = errno; LOG_SYSERR << "Socket::accept" ; switch (savedErrno) { case EAGAIN: case ECONNABORTED: case EINTR: case EPROTO: case EMFILE: errno = savedErrno; break ; case EBADF: case EFAULT: case EINVAL: case ENFILE: case ENOBUFS: case ENOMEM: case ENOTSOCK: case EOPNOTSUPP: LOG_FATAL << "unexpected error of ::accept " << savedErrno; default : LOG_FATAL << "unknown error of ::accept " << savedErrno; break ; } } return connfd; }
read()、readv()函数
read直接转发给read(2),没有特殊处理;readv直接转发给readv(2),没有特殊处理。
1 2 3 4 5 6 7 8 9 ssize_t sockets::read (int sockfd, void *buf, size_t count) { return ::read (sockfd, buf, count); }ssize_t sockets::readv (int sockfd, const struct iovec *iov, int iovcnt) { return ::readv (sockfd, iov, iovcnt); }
write()函数
write直接转发给write(2),没有特殊处理;
1 2 3 4 ssize_t sockets::write (int sockfd, const void *buf, size_t count) { return ::write (sockfd, buf, count); }
close()函数
关闭sockfd
1 2 3 4 5 6 7 void sockets::close (int sockfd) { if (::close (sockfd) <0 ) { LOG_SYSERR << "sockets::close" ; } }
shutdownWrite()函数
shutdownWrite关闭连接写方向:shutdown(2) + SHUT_WR
1 2 3 4 5 6 7 void sockets::shutdownWrite (int sockfd) { if (::shutdown (sockfd, SHUT_WR) < 0 ) { LOG_SYSERR << "sockets::shutdownWrite" ; } }
toIpPort(), toIp()函数
将ip地址、port信息由sockaddr对象,转换为字符串。核心调用inet_ntop(2),
将IPv4、IPv6地址由二进制转化为文本。利用snprintf,将ip地址和port文本信息组装到一起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 void sockets::toIpPort (char *buf, size_t size, const struct sockaddr *addr) { if (addr->sa_family == AF_INET6) { buf[0 ] = '[' ; toIp (buf + 1 , size - 1 , addr); size_t end = ::strlen (buf); const struct sockaddr_in6 * addr6 = sockaddr_in6_cast (addr); uint16_t port = sockets::networkToHost16 (addr6->sin6_port); assert (size > end); snprintf (buf + end, size - end, "]:%u" , port); return ; } toIp (buf, size, addr); size_t end = ::strlen (buf); const struct sockaddr_in * addr4 = sockaddr_in_cast (addr); uint16_t port = sockets::networkToHost16 (addr4->sin_port); assert (size > end); snprintf (buf + end, size - end, ":%u" , port); }void sockets::toIp (char * buf, size_t size, const struct sockaddr* addr) { if (addr->sa_family == AF_INET) { assert (size >= INET_ADDRSTRLEN); const struct sockaddr_in * addr4 = sockaddr_in_cast (addr); ::inet_ntop (AF_INET, &addr4->sin_addr, buf, static_cast <socklen_t >(size)); } else if (addr->sa_family == AF_INET6) { assert (size >= INET6_ADDRSTRLEN); const struct sockaddr_in6 * addr6 = sockaddr_in6_cast (addr); ::inet_ntop (AF_INET6, &addr6->sin6_addr, buf, static_cast <socklen_t >(size)); } }
fromIPPort()函数
将ip地址、端口号文本转换为二进制(sockaddr_in/sockaddr_in6),sockaddr_in适用于IPv4,sockaddr_in6适用于IPv6。2个重载函数是toIpPort()的逆过程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 void sockets::fromIpPort (const char *ip, uint16_t port, struct sockaddr_in *addr) { addr->sin_family = AF_INET; addr->sin_port = hostToNetwork16 (port); if (::inet_pton (AF_INET, ip, &addr->sin_addr) <= 0 ) { LOG_SYSERR << "sockets::fromIpPort" ; } }void sockets::fromIpPort (const char *ip, uint16_t port, struct sockaddr_in6 *addr) { addr->sin6_family = AF_INET6; addr->sin6_port = hostToNetwork16 (port); if (::inet_pton (AF_INET6, ip, &addr->sin6_addr) <= 0 ) { LOG_SYSERR << "sockets::fromIpPort" ; } }
地址转型函数
提供不同地址类型之间的转型,如sockaddr_in/sockaddr_in6/sockaddr*,要求成员内存布局必须是一样的。这也是为什么前面用static_assert来断言sockaddr_in/sockaddr_in6成员偏移的原因(offsetof),因为如果成员偏移不一样,也就是说对象的内存布局不一样,通过指针直接转型是不对的。
为什么用static_cast对指针进行转型,而不用reinterpret_cast?
单独的static_cast,是无法将一种指针类型转换为另一种指针类型的,需要先利用implicit_cast(隐式转型)/static_cast(显式转型)将指针类型转换为void/const
void (无类型)指针,然后才能转换为模板类型指针。
而reinterpret_cast可以直接做到,但reinterpret_cast通常并不安全,编译期也不会在编译期报错,通常不推荐使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const struct sockaddr * sockets::sockaddr_cast (const struct sockaddr_in6* addr) { return static_cast <const struct sockaddr*>(implicit_cast <const void *>(addr)); }struct sockaddr * sockets::sockaddr_cast (struct sockaddr_in6* addr) { return static_cast <struct sockaddr*>(implicit_cast <void *>(addr)); }const struct sockaddr * sockets::sockaddr_cast (const struct sockaddr_in* addr) { return static_cast <const struct sockaddr*>(implicit_cast <const void *>(addr)); }const struct sockaddr_in * sockets::sockaddr_in_cast (const struct sockaddr* addr) { return static_cast <const struct sockaddr_in*>(implicit_cast <const void *>(addr)); }const struct sockaddr_in6 * sockets::sockaddr_in6_cast (const struct sockaddr *addr) { return static_cast <const struct sockaddr_in6*>(implicit_cast <const void *>(addr)); }
getSocketError()函数
获取tcp协议栈错误。利用getsockopt +
SO_ERROR选项,获取tcp协议栈内部错误。通常,在处理连接的读写事件时调用,检查是否发生错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int sockets::getSocketError (int sockfd) { int optval; socklen_t optlen = static_cast <socklen_t >(sizeof (optval)); if (::getsockopt (sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen)) { return errno; } else { return optval; } }
getLocalAddr()函数
从连接获取本地ip地址(包括端口号)。不论IPv4,还是IPv6,统一存放到sockaddr_in6结构对象中,因为该对象长度最长。核心调用getsockname(2)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct sockaddr_in6 sockets::getLocalAddr (int sockfd) { struct sockaddr_in6 localaddr; memZero (&localaddr, sizeof (localaddr)); socklen_t addrlen = static_cast <socklen_t >(sizeof (localaddr)); if (::getsockname (sockfd, sockaddr_cast (&localaddr), &addrlen) < 0 ) { LOG_SYSERR << "sockets::getLocalAddr" ; } return localaddr; }
getPeerAddr()函数
获取连接对端的ip地址(包括端口号)。类似于getLocalAddr,地址信息都存放到sockaddr_in6结构对象中。核心调用getpeername(2)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 struct sockaddr_in6 sockets::getPeerAddr (int sockfd) { struct sockaddr_in6 peeraddr; memZero (&peeraddr, sizeof (peeraddr)); socklen_t addrlen = static_cast <socklen_t >(sizeof (peeraddr)); if (::getpeername (sockfd, sockaddr_cast (&peeraddr), &addrlen)) { LOG_SYSERR << "sockets::getPeerAddr" ; } return peeraddr; }
isSelfConnect()函数
检查是否为自连接。利用了getLocalAddr()和getPeerAddr(),检查ip地址是否相同,来判断连接对端地址信息是否为本机。
isSelfConnect()函数同样分IPv4和IPv6两种情况,依据是sockaddr_in6的sin6_family成员。
note
对于IPv4,地址sin_addr.s_addr是32bit,能用“”判断是否相等;而对于IPv6,地址sin6_addr是28byte,无法用“”判断,需要用memcmp来比较二进制位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 bool sockets::isSelfConnect (int sockfd) { struct sockaddr_in6 localaddr = getLocalAddr (sockfd); struct sockaddr_in6 peeraddr = getPeerAddr (sockfd); if (localaddr.sin6_family == AF_INET) { const struct sockaddr_in * laddr4 = reinterpret_cast <struct sockaddr_in*>(&localaddr); const struct sockaddr_in * raddr4 = reinterpret_cast <struct sockaddr_in*>(&peeraddr); return laddr4->sin_port == raddr4->sin_port && laddr4->sin_addr.s_addr == raddr4->sin_addr.s_addr; } else if (localaddr.sin6_family == AF_INET6) { return localaddr.sin6_port == peeraddr.sin6_port && memcmp (&localaddr.sin6_addr, &peeraddr.sin6_addr, sizeof (localaddr.sin6_addr)) != 0 ; } else { return false ; } }
参考
转载自muduo笔记
网络库(六)Socket类及SocketsOps库函数封装