
在上篇博文中,我们讨论了tbnet库中的buffer结构,这部分内容很底层,一般给予上层应用的玩家很少能够接触到,而今天我们将要讨论另外一个也是很底层的东西,谈到socket,估计很多人都自己写过将要socket方面的东西,我很早之前使用过,当时给我的感觉就是一个字:烦,尤其是在对socket进行异步读写的时候,估计大部分人也有这种感觉,而如今很多的公司基本上都会对socket进行封装,给上层的应用提供接口支持,采用这种方式一个很大的优点就是:为上层开发者剔除掉了很大的一部分编码负担,那接下来,我们就来看看tbnet库的socket的实现吧,代码如下:
class Socket {public:... bool setAddress (const char *address, const int port); /* * Á¬½Óµ½_addressÉÏ * * @return ÊÇ·ñ³É¹¦ */ bool connect();... void close(); /* * ¹Ø±Õ¶Áд */ void shutdown(); /** * ʹÓÃUDPµÄsocket * * @return ÊÇ·ñ³É¹¦ */ bool createUDP();... int getSocketHandle(); /* * ·µ»ØIOComponent * * @return IOComponent */ IOComponent *getIOComponent(); /* * ÉèÖÃIOComponent * * @param IOComponent */ void setIOComponent(IOComponent *ioc); /* * дÊý¾Ý */ int write(const void *data, int len); /* * ¶ÁÊý¾Ý */ int read(void *data, int len);... bool setKeepAlive(bool on) { return setIntOption(SO_KEEPALIVE, on ? 1 : 0); } /* * setReuseAddress */ bool setReuseAddress(bool on) { return setIntOption(SO_REUSEADDR, on ? 1 : 0); } /* * setSoLinger */ bool setSoLinger (bool doLinger, int seconds); /* * setTcpNoDelay */ bool setTcpNoDelay(bool noDelay);... uint64_t getId(); uint64_t getPeerId(); /** * µÃµ½±¾µØ¶Ë¿Ú */ int getLocalPort(); /* * µÃµ½×îºóµÄ´íÎó */ static int getLastError() { return errno; }protected: struct sockaddr_in _address; // µØÖ· int _socketHandle; // socketÎļþ¾ä±ú IOComponent *_iocomponent; static tbsys::CThreadMutex _dnsMutex; //¡¡¶àʵÀýÓÃÒ»¸ödnsMutex};
首先我们来看看其成员变量,最主要的两个变量就是一个句柄,一个地址,至于_iocomponent则是指定该socket的归属问题,这里面还用到了锁,这部分内容不再我们的讨论范围,有时间的话,在来分析,在socket的封装类中,分为了三个部分:1)初始化部分;2)I/O部分;3)设置socket变量,在初始化部分,代码如下:
bool Socket::setAddress (const char *address, const int port) { // ³õʼ»¯ memset(static_cast<void *>(&_address), 0, sizeof(_address)); _address.sin_family = AF_INET; _address.sin_port = htons(static_cast<short>(port)); bool rc = true; // ÊÇ¿Õ×Ö·û£¬ÉèÖóÉINADDR_ANY if (address == NULL || address[0] == '') { _address.sin_addr.s_addr = htonl(INADDR_ANY); } else { char c; const char *p = address; bool isIPAddr = true; // ÊÇipµØÖ·¸ñʽÂð? while ((c = (*p++)) != '') { if ((c != '.') && (!((c >= '0') && (c <= '9')))) { isIPAddr = false; break; } } if (isIPAddr) { _address.sin_addr.s_addr = inet_addr(address); } else { // ÊÇÓòÃû£¬½âÎöһϠ_dnsMutex.lock(); struct hostent *myHostEnt = gethostbyname(address); if (myHostEnt != NULL) { memcpy(&(_address.sin_addr), *(myHostEnt->h_addr_list), sizeof(struct in_addr)); } else { rc = false; } _dnsMutex.unlock(); } } return rc;}
这段代码中主要是用于设置socket的ip和port,其中一些解析ip地址的部分可以稍微看看,还有由于在解析ip地址使用了不安全的接口,故在此使用了锁,其他的初始化代码基本都是将socket系统函数进行了封装,代码如下:
bool Socket::checkSocketHandle() { if (_socketHandle == -1 && (_socketHandle = socket(AF_INET, SOCK_STREAM, 0)) == -1) { return false; } return true;}/* * Á¬½Óµ½_addressÉÏ * * @return ÊÇ·ñ³É¹¦ */bool Socket::connect() { if (!checkSocketHandle()) { return false; } TBSYS_LOG(DEBUG, "´ò¿ª, fd=%d, addr=%s", _socketHandle, getAddr().c_str()); return (0 == ::connect(_socketHandle, (struct sockaddr *)&_address, sizeof(_address)));}
接下来,我们来看看I/O相关方面的操作吧,代码如下:
int Socket::write (const void *data, int len) { if (_socketHandle == -1) { return -1; } int res; do { res = ::write(_socketHandle, data, len); if (res > 0) { //TBSYS_LOG(INFO, "д³öÊý¾Ý, fd=%d, addr=%d", _socketHandle, res); TBNET_COUNT_DATA_WRITE(res); } } while (res < 0 && errno == EINTR); return res;}/* * ¶ÁÊý¾Ý */int Socket::read (void *data, int len) { if (_socketHandle == -1) { return -1; } int res; do { res = ::read(_socketHandle, data, len); if (res > 0) { //TBSYS_LOG(INFO, "¶ÁÈëÊý¾Ý, fd=%d, addr=%d", _socketHandle, res); TBNET_COUNT_DATA_READ(res); } } while (res < 0 && errno == EINTR); return res;}
上述代码很简单,基本上对系统函数进行封装,在socket封装类中,其实主要就是需要完成数据包的输入输出,但是这里面又不能让上层应用察觉到,于是在socket类中,增加了一个iocomponent的对象,这个对象就是指明该socket的归属,有了这个对象,那么iocomponet对象在上层的所有的操作将会关联到这个socket上,因此,在iocomponent中就可以直接操作底层的socket函数了,代码如下:
IOComponent *Socket::getIOComponent() { return _iocomponent;}/* * ÉèÖÃIOComponent * * @param IOComponent */void Socket::setIOComponent(IOComponent *ioc) { _iocomponent = ioc;}
在tbnet中,除了上述所说的socket封装类以外,其还有个继承的子类(servesocket),这个类的作用其实就是实现了在socket上另外几个操作:bind,listen以及accept,实现方式跟上面类似,代码如下:
Socket *ServerSocket::accept() { Socket *handleSocket = NULL; struct sockaddr_in addr; int len = sizeof(addr); int fd = ::accept(_socketHandle, (struct sockaddr *) & addr, (socklen_t*) & len); if (fd >= 0) { handleSocket = new Socket(); handleSocket->setUp(fd, (struct sockaddr *)&addr); } else { int error = getLastError(); if (error != EAGAIN) { TBSYS_LOG(ERROR, "%s(%d)", strerror(error), error); } } return handleSocket;}bool ServerSocket::listen() { if (!checkSocketHandle()) { return false; } // µØÖ·¿ÉÖ setSoLinger(false, 0); setReuseAddress(true); setIntOption(SO_KEEPALIVE, 1); setIntOption(SO_SNDBUF, 640000); setIntOption(SO_RCVBUF, 640000); setTcpNoDelay(true); if (::bind(_socketHandle, (struct sockaddr *)&_address, sizeof(_address)) < 0) { return false; } if (::listen(_socketHandle, _backLog) < 0) { return false; } return true;}
其实针对socket的封装就是在系统接口层面再加了一层,这样做的主要目的就是为了保证调用系统接口的安全性,这样的方法也是我们需要学习的地方,一般来讲严格的编码规范中很少直接调用系统接口的,一般都会在系统接口上封装一层并加上足够的条件检查,以提高代码的健壮性,如果按照我的想法其实这部分完全可以放到socket里面,没有必要再搞一个继承类,这个纯属个人见解,总的来讲tbnet对于socket封装来讲,还算不错,尤其是在设置socket变量的时候,很多的开发者习惯使用setsockopt类的函数来直接对socket进行设置,用这种方法很容易出现错误,并且在出现问题时其实是很难定位的,从这里面我们可以得到以下启发:今后在需要使用系统调用的地方,我们最好能够封装下,不要直接就拿来使用,使用经过封装好的接口的好处上面也提到了,养成一种良好的编码习惯其实是很重要的,tbnet里面的socket封装差不多就介绍完了,希望大家能够从这里面学到一些东西,接下来的博文我们就来看看tbnet中的connection吧,谢谢了
如果需要,请注明转载,多谢了