自定义协议:如何实现keepalive

Posted BBinChina

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义协议:如何实现keepalive相关的知识,希望对你有一定的参考价值。

高可用协议招式:keepalive

什么是keepalive

Keepalive是一种技术,它可以帮助保持网络连接活跃,避免链路失效。它可以通过给双方发送定期的探测报文,来确认对侧主机是否仍然处于可用状态,并采取必要的措施保持链路的连接性、可靠性和可用性。

tcp如何实现keepalive

tcp提供了相关函数

客户端TCP keep-alive:
// 设置keep-alive参数
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof(int));
// 设置keep-alive间隔 
optval = 10; // 10s 
setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, &optval, sizeof(int));
// 设置探测次数 
optval = 3; // 每10s探测3次 
setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &optval, sizeof(int));
// 设置探测间距 
optval = 2; // 每2s探测一次 
setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, &optval, sizeof(int));

http如何实现keepalive

http在1.1版本实现了keepalive
HTTP/1.1实现keep-alive的方法如下:

  1. 客户端向服务端发出请求时附带Connection: keep-alive
request = "GET / HTTP/1.1\\r\\n" + \\ 
"Host: example.com\\r\\n" + \\
"Connection: keep-alive\\r\\n" + \\ # 附带此行
"\\r\\n"
  1. 服务端正常回复,并附带上Connection: keep-alive
response = "HTTP/1.1 200 OK\\r\\n" + \\ 
"Connection: keep-alive\\r\\n" + \\ # 附带此行
"Content-Length: \\r\\n".format(len(data)) + \\
"\\r\\n" + data
  1. 客户端重用连接,后续请求仍使用此TCP连接
request = "GET /other_url HTTP/1.1\\r\\n" + \\ 
"Host: example.com\\r\\n" + \\
"Connection: keep-alive\\r\\n" + \\ # 仍然附带此行
"\\r\\n"

自定义协议时该怎样实现keepalive

1.客户端定时发送心跳包:客户端定时发送心跳包,服务端收到后即回复确认码,客户端收到确认码即表示连接状态正常;
2.服务端检测客户端:服务端定时检测客户端是否响应心跳包,若客户端在指定时间内未收到确认码,则关闭该连接;
C++ demo代码:

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <fcntl.h>
using namespace std;
#define SER_PORT 9999
#define MAX_LEN 1024
#define CLIENT_NUM 10
int main() 
    int sockfd, client_fd[CLIENT_NUM] = 0; // 套接字描述符
    struct sockaddr_in server_addr, client_addr; // 地址信息
    socklen_t addrlen = sizeof(sockaddr); // 地址信息长度
    char buf[MAX_LEN]; // 数据缓冲区
    int opt = 1; // 优化选项
    // 定义epoll句柄
    int epfd;
    struct epoll_event ev, events[CLIENT_NUM];
    memset(&ev, 0, sizeof(ev));
    // 初始化服务器地址
    bzero(&server_addr, sizeof(struct sockaddr_in));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(SER_PORT);
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    // 创建套接字
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
        cout << "Create Socket failed!" << endl;
        return -1;
    
    // 设置优化选项
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    // 绑定地址
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)) < 0) 
        cout << "Bind Failed!" << endl;
        return -1;
    
    // 监听
    if (listen(sockfd, 5) < 0) 
        cout << "Listen Failed!" << endl;
        return -1;
    
    // 创建epoll
    epfd = epoll_create(CLIENT_NUM);
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);
    // 循环处理客户端请求
    while(1)
        int retval;
        retval = epoll_wait(epfd, events, CLIENT_NUM, -1); // -1表示永远等待
        // 遍历retval个就绪事件
        for (int i = 0; i < retval; ++i)
            // 客户端连接请求
            if (events[i].data.fd == sockfd && events[i].events & EPOLLIN)
                // 接收客户端连接
                int conn_fd = accept(sockfd, (struct sockaddr*)&client_addr, &addrlen);
                if (conn_fd < 0)
                    cout << "Accept Failed!" << endl;
                    return -1;
                
                // 设置新的监听事件
                ev.events = EPOLLIN;
                ev.data.fd = conn_fd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev);
                // 设置客户端socket为非阻塞
                int flag = fcntl(conn_fd, F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(conn_fd, F_SETFL, flag);
                cout << "A New Client Connection: " << inet_ntoa(client_addr.sin_addr) << endl;
            
            // 客户端消息
            if (events[i].data.fd > 0 && events[i].events & EPOLLIN)
                // 遍历已连接描述符
                for (int j = 0; j < CLIENT_NUM; ++j)
                    // 接收客户端消息
                    if (events[i].data.fd == client_fd[j])
                        int len = recv(client_fd[j], buf, MAX_LEN, 0);
                        if (len > 0)
                            // 获取消息类型
                            int type = (unsigned int)buf;
                            switch (type)
                            // 处理心跳包
                            case 0x10:
                                // 向客户端发送确认码
                                send(events[i].data.fd, "\\x12\\x00\\x00\\x00", 4, 0);
                                break;
                            // 处理客户端数据
                            case 0x11:
                                // 处理客户端发来的数据
                                cout << "Client Data: " << buf << endl;
								break;
                            default:
                                break;
                            
                        
                    
                
            
        
    
    close(sockfd);
    return 0;

以上是关于自定义协议:如何实现keepalive的主要内容,如果未能解决你的问题,请参考以下文章

keepalive笔记之二:keepalive+nginx(自定义脚本实现,上述例子也可以实现)

Netty——自定义协议通信

不为人知的网络编程:彻底搞懂TCP协议层的KeepAlive保活机制

一文读懂keepalive的工作原理

浅析IM即时通讯开发中TCP协议层KeepAlive保活机制

手把手教你实现自定义的应用层协议