美文网首页
IM客户端开发(1)——连接过程

IM客户端开发(1)——连接过程

作者: Magic11 | 来源:发表于2019-12-05 14:31 被阅读0次
1、客户端通过用户名和密码登录业务层服务器,获取到userId,appid,token等参数
2、通过http请求入口服务,拿到消息服务器的IP地址和端口号
public void login(String userId, String password, final GIMCallback callback) {
    //首先获取入口服务
    GIMGetEnterService task = new GIMGetEnterService(mContext);
    task.setOnGetEnterService(new GIMOnGetEnterService() {
        @Override
        public void onSuccess(final EnterServiceInfos enterServiceInfos) {
            //使用新的IP和端口号
            updateNewIpAndPort();
            //设置连接监听
            initNetConnectListener();
            //启动socket通讯层
            GIMNetApi.InitSendQueue();
        }

        @Override
        public void onFailed() {
        }
    });

    task.execute();
}

1)updateNewIpAndPort()函数用来调用InitNetConfig配置服务器IP和端口号

Java_com_vkansee_jnidemo_jniapi_GIMNetApi_InitNetConfig(JNIEnv *_evn, jclass _thiz, jstring _ip, jint _port,
                                                        jstring _sendId, jint _version, 
                                                        jlong _appId, jboolean _model) {
    GSKCNet::sharedInstance()->ChangeIpAndPort(JniHelper::jstring2stringNew(_evn, _ip), (int)_port);

    GSKCNet::sharedInstance()->SetSendIdAndAppVer(JniHelper::jstring2stringNew(_evn, _sendId), (int)_version);

    GSKCNet::sharedInstance()->SetAppId((long long)_appId);
}

2)initNetConnectListener()函数用来设置连接成功或失败的回调
3)InitSendQueue用来初始化网络连接

bool GSKCNet::Init()
{   
    //初始化网络对象
    if (!m_pGSKSocket)
    {
        m_pGSKSocket = new GSKSocket( m_strIp , m_iPort );
    }

    //连接网络,会根据策略最多尝试5次
    //联网线程
    std::thread t = std::thread(std::bind(&GSKCNet::initNetwork, this));
    t.detach();
    return true;
}

initNetwork的流程如下:
1) 停掉所有的线程(发送线程、接收线程、回调线程)
2) 等待所有的线程都已经正常退出,最多等5秒
3) 尝试连接5次,5次连接不成功调用连接失败回调
4) 连接成功启动所有的线程
代码逻辑如下:

void GSKCNet::initNetwork()
{
    SetInitNetwork(true);

    StopAllThread();
    
    //等待线程退出,最多5秒
    int num = 0;
    while (!GetExitSendLoop() || !GetExitRecvLoop() || !GetExitCallbckLoop())
    {
        glodon::sleep(50);
        m_sleepStrategy.notify_all();  //重连需要设置睡眠间隔,重连可能在发送线程中进行
        ++num;
        if (num >100)
        {
            break;
        }
    }

    //失败5次就不重连了
    if (TryConnectServer(10, INIT_CNETWORK))
    {
        //开始线程
        Start();
    }

    SetInitNetwork(false);
}

连接过程的逻辑如下:

bool GSKCNet::TryConnectServer(int num, enum CONNECTNETWORKSRC type)
{
    if (GetTryReconnected())  //如果其他线程已经正在重连,啥都不做
    {
        return false;
    }
    SetTryReconnected(true);

    if (GetIsLogged())    //如果已经登录,提示连接断开
    {
        if (g_netReConnectCallback)
        {
            g_netReConnectCallback(msg);
        }
    }

    SetLastConnectTime(time(NULL));
    int i = 0;
    while (i < num)
    {
        if (m_pGSKSocket)
        {
            m_pGSKSocket->Close();  //关闭之前的socket
        }
        m_recvBuffer.retrieveAll();
        
        if (!SleepTimeStrategy(i))  //设置睡眠间隔,如果是被主动唤醒的,直接退出循环,不需要再继续连接了
        {
            break;
        }

        if (m_pGSKSocket && m_pGSKSocket->Connect(1) > 0)
        {   //连接成功
            if(g_netConnectCallback)
            {
                g_netConnectCallback(true);
            }
            SetTryReconnected(false);
            return true;
        }else{
            //连接失败
            if(g_netConnectCallback)
            {
                g_netConnectCallback(false);
            }
        }
        ++i;
    }
    SetTryReconnected(false);
    return false;
}

SleepTimeStrategy睡眠的策略如下:

bool GSKCNet::SleepTimeStrategy(int num)
{
    int timeOut = 500;
    if (0 == num)
        timeOut = 500;
    else if (1 == num)
        timeOut = 2000;
    else if (2 == num)
        timeOut = 4000;
    else if (3 == num)
        timeOut = 6000;
    else if (4 == num)
        timeOut = 8000;
    else
        timeOut = 10000;

    std::mutex conlock;
    std::unique_lock < std::mutex > lck(conlock);
    //超时解除等待
    if (m_sleepStrategy.wait_for(lck, std::chrono::milliseconds(timeOut)) == std::cv_status::timeout)
    {
        return true;
    }
    else
    {
        //被主动唤醒
        return false;
    }
}

Connect代码的逻辑如下:

int GSKSocket::Connect( int iTimeout) 
{
    if( IsConnected() )
    {
        return m_iSocket;
    }

    //配置想要的地址信息兼容ipv4,ipv6
    struct addrinfo addrCriteria;
    memset(&addrCriteria,0,sizeof(addrCriteria));
    addrCriteria.ai_family=AF_UNSPEC;
    addrCriteria.ai_socktype=SOCK_STREAM;
    addrCriteria.ai_protocol=IPPROTO_TCP;

    struct addrinfo *server_addr;
    //获取地址信息
    char buf[25] = {0};
    sprintf(buf,"%d",m_iPort);
    int retVal=getaddrinfo(m_strIP.c_str(),buf,&addrCriteria,&server_addr);
    if(retVal!=0)
    {
        return -11;
    }
    struct addrinfo *addr=server_addr;

    int iRet = -1;
    while(addr!=NULL)
    {
        //建立socket
        m_iSocket = socket(addr->ai_family,addr->ai_socktype,addr->ai_protocol);
        if(m_iSocket < 0) //Create socket failed
        {
            addr=addr->ai_next;
            if(!addr)
            {
                iRet = -12;
            }
            continue;
        }

        int iNoDelay = 0;
        if (setsockopt(m_iSocket, IPPROTO_TCP, TCP_NODELAY,  //使用setsockopt TCP_NODELAY禁用 Nagle算法
                       reinterpret_cast<const char*>(&iNoDelay), (socklen_t)sizeof(iNoDelay)) != 0)
        {
            //set socket TCP_NODELAY opt failed
            addr=addr->ai_next;
            if(!addr)
            {
                Close();
                iRet = -13;
            }
            continue;
        }

        auto result = glodon::setSocketNBlocked( m_iSocket );
        if ( result != 0 )
        {
            //set socket NBLOCK failed
            addr=addr->ai_next;
            if(!addr)
            {
                Close();
                iRet = -14;
            }
            continue;
        }

        if (!glodon::connect( m_iSocket, addr->ai_addr ,addr->ai_addrlen) )
        {
            //connect to server failed;
            addr=addr->ai_next;
            if(!addr)
            {
                Close();
                iRet = -15;
            }
            continue;
        }

        fd_set stFdSet;
        FD_ZERO(&stFdSet);
        FD_SET(m_iSocket, &stFdSet);
        struct timeval stTimeout;
        stTimeout.tv_sec = iTimeout;
        stTimeout.tv_usec = 0;

        if ((iRet = select(m_iSocket + 1, NULL, &stFdSet, NULL, &stTimeout)) == -1)
        {
            //select check read failed
            addr=addr->ai_next;
            if(!addr)
            {
                Close();
                iRet = -16;
            }
            continue;
        }
        else if (iRet == 0)
        {
            //select check read timeout
            errno = ETIMEDOUT;
            addr=addr->ai_next;
            if(!addr)
            {
                Close();
                iRet = -17;
            }
            continue;
        }

        int iSockErr;
        socklen_t iSockErrLen = sizeof(iSockErr);
        if (getsockopt(m_iSocket, SOL_SOCKET, SO_ERROR, reinterpret_cast<char*>(&iSockErr), &iSockErrLen) == -1)
        {
            //get socket so_error opt failed
            addr=addr->ai_next;
            if(!addr)
            {
                Close();
                iRet = -18;
            }
            continue;
        }
        if (iSockErr)
        {
            LOGI_GSKCNET_INFO("get socket so_error opt socket = %d, error=%d", m_iSocket, iSockErr);
            errno = iSockErr;
            addr=addr->ai_next;
            if(!addr)
            {
                Close();
                iRet = -19;
            }
            continue;
        }
        if(iRet > 0)
        {
            break;
        }
    }
    freeaddrinfo(server_addr);

    if(m_iSocket != INVALID_SOCKET)
    {
        LOGI_GSKCNET_INFO("connect to server succeed, socket=%d", m_iSocket);
        return m_iSocket;
    }
    LOGI_GSKCNET_INFO("connect to server failed, errorCode=%d", iRet);
    return iRet;
}

连接成功后会启动所有的线程,过程如下:

void GSKCNet::Start()
{
    //开启发送线程
    m_workThread = std::thread(std::bind( &GSKCNet::Loop, this));
    m_workThread.detach();
    //开启接收线程
    m_recvThread = std::thread(std::bind(&GSKCNet::recvLoop, this));
    m_recvThread.detach();
    //开启回调处理线程
    m_callbackThread = std::thread(std::bind(&GSKCNet::callbackLoop, this));
    m_callbackThread.detach();
}

停止所有线程的逻辑如下:

void GSKCNet::StopAllThread()
{
    m_waitExitLock.lock();
    
    SetIsLogged(false);

    SetWorking(false);
    m_sendcv.notify_all();

    SetRecvLoop(false);
    m_sleepStrategy.notify_all();

    SetCallbckLoop(false);
    m_cv.notify_all();
    
    m_waitExitLock.unlock();
}

注意,线程可能会阻塞在某些条件变量上,停止线程的时候,一定要把它们唤醒,否则线程无法正常退出 。

相关文章

  • IM客户端开发(1)——连接过程

    1、客户端通过用户名和密码登录业务层服务器,获取到userId,appid,token等参数 2、通过http请求...

  • Android客户端研发专家

    Android客户端研发专家(杭州) 职位描述: 1、带领公司客户端团队,负责公司IM SDK和音视频SDK的开发...

  • IM Spark客户端登录教程及错误处理

    spark 是一款免费的电脑版IM客户端,使用简单,是IM开发不可或缺的工具 今天就出个教程吧 1.下载spark...

  • https

    对称加密非对称加密 如上图,HTTPS连接过程大致可分为八步:1、客户端访问HTTPS连接。客户端会把安全协议版本...

  • 简述HTTP过程

    HTTP连接一个最基本的过程: 1客户端连接一个主机; 2服务器接收连接, 3客户端请求一个文件, 4服务器发送一...

  • 面试遇到的题目

    1、Socket的长连接和短连接 长连接和短连接的概念: 长连接与短连接的概念:前者是整个通讯过程,客户端和服务端...

  • iOS逆向开发知识点储备

    SSH连接过程:建立安全连接->客户端认证->数据传输

  • HTTP协议学习笔记(2)

    客户端与服务端的通信与TCP连接 1. 客户端与服务端的通信过程 当客户端想要跟服务端进行信息交互时,过程如下: ...

  • TCP的3次握手和4次挥手协议及数据传输过程

    TCP建立连接3次握手握手过程如下:客户端A向服务器发送TCP连接请求数据包,报文设置同步标志位SYN=1,客户端...

  • 微信web开发的两种调试方法

    1.使用微信开发者工具客户端; 2.通过数据线物理连接。

网友评论

      本文标题:IM客户端开发(1)——连接过程

      本文链接:https://www.haomeiwen.com/subject/exddgctx.html