淘宝开源网络框架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吧,谢谢了

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