zoukankan      html  css  js  c++  java
  • 学习网络编程的一些实用技巧和细节

    https://blog.csdn.net/analogous_love/article/details/60761528

    非阻塞的的connect()函数如何编写

    1. 创建socket时,将socket设置成非阻塞模式

    2. 接着调用connect()进行连接,如果connect()能立即连接成功,则返回0;如果此刻不能立即连接成功,则返回-1(windows上返回SOCKET_ERROR也等于-1),这个时候错误码是WSAEWOULDBLOCK(windows平台),或者是EINPROGRESS(linux平台),表明立即暂时不能完成。

    3. 接着调用select()函数在指定的时间内检测socket是否可写,如果可写表明connect()连接成功。

    需要注意的是:linux平台上connect()暂时不能完成返回-1,错误码可能是EINPROGRESS,也可能是由于被信号给中断了,这个时候错误码是:EINTR。这种情况也要考虑到;而在windows平台上除了用select()函数去检测socket是否可写,也可以使用windows平台自带的函数WSAAsyncSelect或WSAEventSelect来检测。

    /** 
     *@param timeout 连接超时时间,单位为秒
     *@return 连接成功返回true,反之返回false
     **/
    bool CSocket::Connect(int timeout)
    {
        //windows将socket设置成非阻塞的方式
        unsigned long on = 1;
        if (::ioctlsocket(m_hSocket, FIONBIO, &on) < 0)
            return false;
     
        //linux将socket设置成非阻塞的方式
        //将新socket设置为non-blocking
        /*
        int oldflag = ::fcntl(newfd, F_GETFL, 0);
        int newflag = oldflag | O_NONBLOCK;
        if (::fcntl(m_hSocket, F_SETFL, newflag) == -1)      
            return false;
        */
     
        struct sockaddr_in addrSrv = { 0 };
        addrSrv.sin_family = AF_INET;
        addrSrv.sin_addr = htonl(addr);
        addrSrv.sin_port = htons((u_short)m_nPort);
        int ret = ::connect(m_hSocket, (struct sockaddr*)&addrSrv, sizeof(addrSrv));
        if (ret == 0)//返回0 直接ok了
            return true;
     
        //windows下检测WSAEWOULDBLOCK
        if (ret < 0 && WSAGetLastError() != WSAEWOULDBLOCK)
            return false;
     
     
        //linux下需要检测EINPROGRESS和EINTR
        /* //小于0 需要进行错误码的检验   因为已经发起了三次握手  需要时间呢 RTT时间的存在   所以后面需要select再次进行检验
        if (ret < 0 && (errno != EINPROGRESS || errno != EINTR))
            return false;
        */
     
        fd_set writeset;
        FD_ZERO(&writeset);
        FD_SET(m_hSocket, &writeset);
        struct timeval tv;
        tv.tv_sec = timeout;
        //可以利用tv_usec做更小精度的超时设置
        tv.tv_usec = 0;
        if (::select(m_hSocket + 1, NULL, &writeset, NULL, &tv) != 1)
            return false;
     
        return true;
    }

    二、非阻塞socket下如何正确的收发数据

     这里不讨论阻塞模式下,阻塞模式下send和recv函数如果tcp窗口太小或没有数据的话都是阻塞在send和recv调用处的。对于收数据,一般的流程是先用select(windows和linux平台皆可)、WSAAsyncSelect()或WSAEventSelect()(windows平台)、poll或epoll_wait(linux平台)检测socket有数据可读,然后进行收取。对于发数据,;linux平台下epoll模型存在水平模式和边缘模式两种情形,如果是边沿ET模式一定要一次性把socket上的数据收取干净才行,也就是一定要循环到recv函数出错,错误码是EWOULDBLOCK  |  EAGAIN。而linux下的水平模式或者windows平台上可以根据业务一次性收取固定的字节数,或者收完为止。还有个区别上文也说过,就是windows下发数据的代码稍微有点不同的就是不需要检测错误码是EINTR,只需要检测是否是WSAEWOULDBLOCK。代码如下:

    可以参考  linux高性能编程 ET  LT模式它的代码 怎么处理的

    用于windows或linux水平模式下收取数据这种情况下收取的数据可以小于指定大小,总之一次能收到多少是多少:

    bool TcpSession::Recv()
    {
        //每次只收取256个字节
        char buff[256];
        //memset(buff, 0, sizeof(buff));
        int nRecv = ::recv(clientfd_, buff, 256, 0);
        if (nRecv == 0)
            return false;
     
        inputBuffer_.add(buff, (size_t)nRecv);
     
        return true;
    }

    如果是linux epoll边缘模式(ET),则一定要一次性收完:

    bool TcpSession::RecvEtMode()
    {
        //每次只收取256个字节
        char buff[256];
        while (true)
        {
            //memset(buff, 0, sizeof(buff));
            int nRecv = ::recv(clientfd_, buff, 256, 0);
            if (nRecv == -1)
            {
                if (errno == EWOULDBLOCK || errno == EINTR)
                    return true;
     
                return false;
            }
            //对端关闭了socket
            else if (nRecv == 0)
                return false;
            
           inputBuffer_.add(buff, (size_t)nRecv);
        }
     
        return true;
    }

    linux平台发送数据:

    bool TcpSession::Send()
    {
        while (true)
        {
            int n = ::send(clientfd_, buffer_, buffer_.length(), 0);
            if (n == -1)
            {
                //tcp窗口容量不够, 暂且发不出去,下次再发
                if (errno == EWOULDBLOCK)
                    break;
                //被信号中断,继续发送
                else if (errno == EINTR)
                    continue;
     
                return false;
            }
            //对端关闭了连接
            else if (n == 0)
                return false;
     
            buffer_.erase(n);
            //全部发送完毕
            if (buffer_.length() == 0)
                break;
        }
     
        return true;
    }

    五、nagle算法

    nagle算法的是操作系统网络通信层的一种发送数据包机制,如果开启,则一次放入网卡缓冲区中的数据(利用send或write等)较小时,可能不会立即发出去,只要当多次send或者write之后,网卡缓冲区中的数据足够多时,才会一次性被协议栈发送出去,操作系统利用这个算法减少网络通信次数,提高网络利用率。对于实时性要求比较高的应用来说,可以禁用nagle算法。这样send或write的小数据包会立刻发出去。系统默认是开启的,禁用方法如下:

    long noDelay = 1;
    setsockopt(m_hSocket, IPPROTO_TCP, TCP_NODELAY,(LPSTR)&noDelay, sizeof(long));

    noDelay为1禁用nagle算法,为0启用nagle算法。

    十、重连机制

    1. 如果connect连接不上,那么n秒后再重试,如果还是连接不上2n秒之后再重试,以此类推,4n,8n,16n......

    但是上述方案,也存在问题,就是如果当重试间隔时间变的很长,网络突然畅通了,这个时候,需要很长时间才能连接服务器,这个时候,就应该采取方法2。

    2. 在网络状态发生变化时,尝试重连。比如一款通讯软件,由于网络故障现在处于掉线状态,突然网络恢复了,这个时候就应该尝试重连。windows下检测网络状态发生变化的API是IsNetworkAlive。示例代码如下:

    十一、关于错误码EINTR
         这个错误码是linux平台下的。对于很多linux网络函数,如connect、send、recv、epoll_wait等,当这些函数出错时,一定要检测错误是不是EINTR,因为如果是这种错误,其实只是被信号中断了,函数调用并没用出错,这个时候要么重试,如send、recv、epoll_wait,要么利用其他方式检测完成情况,如利用select检测connect是否成功。千万不要草草认定这些调用失败,而做出错误逻辑判断。
    ---------------------

    继续未完成

  • 相关阅读:
    微信支付Native扫码支付模式二之CodeIgniter集成篇
    如何使用硬盘安装debian8.3?
    使用git将代码push到osc上
    树莓派(Raspberry Pi)搭建简单的lamp服务
    win下修改mysql默认的字符集以防止乱码出现
    CodeIgniter2.2.0-在控制器里调用load失败报错的问题
    Ubuntu Server(Ubuntu 14.04 LTS 64位)安装libgdiplus2.10.9出错问题记录
    linux下mono的安装与卸载
    asp.net中ashx生成验证码代码放在Linux(centos)主机上访问时无法显示问题
    使用NPOI将数据导出为word格式里的table
  • 原文地址:https://www.cnblogs.com/zhangkele/p/10466657.html
Copyright © 2011-2022 走看看