如何开发一个移动端信令网络库

1、背景介绍

最近几年一直在做IM相关的开发,对于IM通道侧接收消息的方式一般有三种:

  • 长连接推
  • 短连HTTP轮询拉
  • 推拉结合

IM通道有两个基本的要求:"不丢不重",纯推不丢不重机制复杂,纯拉性能和实时性又不是特别好,所以我们采用的是推拉结合的方案。当有新消息时长连接负责推送指令告知客户端有新消息,客户端再通过HTTP去请求最新消息。

这个通道和机制一直稳定运行了好多年,直到今年ChatGPT的到来,带来了很多业务场景,对长连接的通道的要求有高了起来。

2、建设长连接通道会有哪些问题

2.1 问题一 连接超时问题

面试时经常被问到的TCP三次握手过程,我们都能脱口而出了。我们也知道三次握手分别对应服务端的accept和客户端的connect,下面摘录了一个客户端服务端实现socket通信的demo:

server.c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    int sockfd, newsockfd, portno;
    socklen_t clilen;
    char buffer[256];
    struct sockaddr_in serv_addr, cli_addr;
    int n;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("ERROR opening socket");
        exit(1);
    }

    bzero((char *) &serv_addr, sizeof(serv_addr));
    portno = 5001;

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        perror("ERROR on binding");
        exit(1);
    }

    listen(sockfd, 5);
    clilen = sizeof(cli_addr);
    printf("new client\n");

    newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
    printf("newsocketfd:%d\n", newsockfd);
    if (newsockfd < 0) {
        perror("ERROR on accept");
        exit(1);
    }

    bzero(buffer, 256);
    n = read(newsockfd, buffer, 255);
    if (n < 0) {
        perror("ERROR reading from socket");
        exit(1);
    }
    printf("Here is the message: %s\n", buffer);

    n = write(newsockfd, "I got your message", 18);
    if (n < 0) {
        perror("ERROR writing to socket");
        exit(1);
    }

    close(newsockfd);
    close(sockfd);
    return 0;
}
client.c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

int main() {
    int sockfd, portno, n;
    struct sockaddr_in serv_addr;
    struct hostent *server;

    char buffer[256];
    portno = 5001;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("ERROR opening socket");
        exit(1);
    }

    server = gethostbyname("localhost");
    if (server == NULL) {
        fprintf(stderr, "ERROR, no such host\n");
        exit(0);
    }

    bzero((char *) &serv_addr, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
    serv_addr.sin_port = htons(portno);

    if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        perror("ERROR connecting");
        exit(1);
    }

    printf("Please enter the message: ");
    bzero(buffer, 256);
    fgets(buffer, 255, stdin);

    n = write(sockfd, buffer, strlen(buffer));
    if (n < 0) {
        perror("ERROR writing to socket");
        exit(1);
    }

    bzero(buffer, 256);
    n = read(sockfd, buffer, 255);
    if (n < 0) {
        perror("ERROR reading from socket");
        exit(1);
    }
    printf("%s\n", buffer);
    close(sockfd);
    return 0;
}

运行demo时没有任何问题,因为demo一般都运行在我们相对稳定的环境。到了现实中更复杂的场景,可能客户端去connect时会超时或者失败,但是它并不是立即超时失败,在Linux 实现中,如果主动 connect 方没有收到 SYN 的回应,会在第6秒发送第2个 SYN 进行重试,第3个 SYN 则是与第2个间隔24秒。直到第75秒还没有收到回应,则 connect 调用返回 ETIMEOUT。

再具体点,什么时候回出现这种情况呢?我们列举一下常见场景:

  1. 网络彻底断了:直接返回失败;
  2. 服务端的accet函数所在线程阻塞(如上面例子,已经有一个client连接,并且在线程里被阻塞):客户端还是可以成长connect成功,建立连接时系统侧干的;
  3. 服务端负载达到极限或者中间路由问题:会连接很长时间失败
  4. 网络差,丢包严重:会连接很长时间失败

2.2 断开感知

socket断开靠读写失败来感知。要监听socket连接是否被断开,一般使用以下方法:

  1. 使用心跳包(Keep-Alive):你在一定时间间隔内,服务端和客户端相互发送小型的数据包以确认连接是否仍然有效。如果一方停止收到心跳包,就可以认为连接已经断开。
  2. 使用超时机制:在进行读取或写入操作时,你可以设置一个超时时间。如果在超时时间内没有收到数据,则可以认为连接已经断开。
  3. 通过错误码检测:在进行读取或写入操作后,检查返回的错误码。如果错误码指示连接已经断开,你可以进行相应的处理。
  4. 使用select()或poll()函数:你可以使用select()或poll()函数来检测套接字上的读取事件。如果套接字上有可读事件,你可以尝试读取数据。如果读取失败并且errno被设置为ECONNRESET或者recv()返回0,这表示连接已经断开。

3、如何建立可靠信令网络

3.1 客户端

客户端要健壮,则需要防止大量发送消息被丢失问题。客户端需要建立消息队列缓存消息,做防雪崩处理等。

3.2 服务端

通道建立了,但是服务端推送的事件想不丢失,则需要服务端有缓存队列,服务端发送失败则缓存下来,如果客户端收到并发送了ACK则从队列清除,如何清除还需要一定的机制配合。

相关推荐
蜡台14 小时前
本机 局域网 搭建本地 HTTPS 域名完整方案
网络协议·http·https·openssl
Alkaid207719 小时前
为什么查 IP 有两个结果?这个问题问倒过多少人
网络协议·tcp/ip
严谨的麻辣烫1 天前
数据中心出口与住宅出口的技术差异:从 ASN 到 TCP 行为的一次拆解
网络·网络协议·tcp/ip
2501_915921431 天前
抓包鹰 Traceeagle 解除证书绑定,SSL Pinning 解除
网络·网络协议·网络安全·ios·adb·https·ssl
Lucky_Turtle1 天前
【SSL】letsencrypt域名证书申请,Cloudflare域名
网络·网络协议·ssl
路由侠内网穿透1 天前
本地部署企业级快速开发平台芋道管理后台并实现外部访问
运维·服务器·网络·网络协议
Sylvia33.2 天前
篮球数据API的技术架构与工程实践:基于火星数据WebSocket实时推送体系
java·python·websocket·网络协议·架构
m0_749492222 天前
精密制造检测设备采购:五款超高精度三坐标测量机对比
网络协议
聚美智数2 天前
SSL证书信息查询-SSL域名解析-SSL证书查询服务-SSL证书解析服务API接口介绍
网络·网络协议·ssl
瓦学妹2 天前
美国住宅IP如何选?不同平台配置选择标准与教程指南
网络·网络协议·tcp/ip