淘宝开源网络框架tbnet之socket
在上篇博文中,我们讨论了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吧,谢谢了
如果需要,请注明转载,多谢了