淘宝开源网络框架tbnet之socket

来源:互联网 发布:mac 触摸板 鼠标方向 编辑:程序博客网 时间:2024/04/30 11:56

在上篇博文中,我们讨论了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] == '\0') {        _address.sin_addr.s_addr = htonl(INADDR_ANY);    } else {        char c;        const char *p = address;        bool isIPAddr = true;        // ÊÇipµØÖ·¸ñʽÂð?        while ((c = (*p++)) != '\0') {            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吧,谢谢了

如果需要,请注明转载,多谢了

1 0
原创粉丝点击