TCP socket heartbeat packet sample program

When developing games, you often need to implement your own heartbeat mechanism at the application layer, that is, regularly send a custom structure (heartbeat packet) to let the other party know that you are still alive to ensure the validity of the connection.

In the TCP socket heartbeat mechanism, heartbeat packets can be sent from the server to the client, or from the client to the server, but in comparison, the former may be more expensive. — What is implemented here is that the client sends a heartbeat packet to the server. The basic idea is:

1) The server saves the IP and counter count for each client, that is, map>. The main thread of the server uses select to implement multi-channel IO multiplexing, monitor new connections and accept data packets (heartbeat packets), and the sub-thread is used to detect heartbeats:

If the main thread receives a heartbeat packet, clear the counter count corresponding to the client;
In the child thread, the counter count of all clients is traversed every 3 seconds:
If count is less than 5, add 1 to the count counter;
If count is equal to 5, it means that the user’s heartbeat packet has not been received for 15 seconds, and the user is judged to have been offline;
2) The client just opens up a sub-thread and sends heartbeat packets to the server regularly (in this example, the timing time is 3 seconds).

The following is a simple implementation of a socket heartbeat packet under Linux:

/********************************************** ***************************
    > File Name: Server.cpp
    > Author: SongLee
    > E-mail: [email protected]
    > Created Time: Thursday, May 5, 2016 22:50:23
    > Personal Blog: http://songlee24.github.io/
 *************************************************** ************************/
#include<netinet/in.h> // sockaddr_in
#include<sys/types.h> // socket
#include<sys/socket.h> // socket
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/select.h> // select
#include<sys/ioctl.h>
#include<sys/time.h>
#include<iostream>
#include<vector>
#include<map>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<cstring>
using namespace std;
#defineBUFFER_SIZE 1024

enum Type {HEART, OTHER};

struct PACKET_HEAD
{
    Type type;
    int length;
};

void* heart_handler(void* arg);

class Server
{
private:
    struct sockaddr_in server_addr;
    socklen_t server_addr_len;
    int listen_fd; // Listening fd
    int max_fd; // maximum fd
    fd_set master_set; // All fd sets, including listening fd and client fd
    fd_set working_set; // working set
    struct timeval timeout;
    map<int, pair<string, int> > mmap; // Record the connected client fd--><ip, count>
public:
    Server(int port);
    ~Server();
    void Bind();
    void Listen(int queue_len = 20);
    void Accept();
    void Run();
    void Recv(int nums);
    friend void* heart_handler(void* arg);
};

Server::Server(int port)
{
    bzero( & amp;server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);
    server_addr.sin_port = htons(port);
    // create socket to listen
    listen_fd = socket(PF_INET, SOCK_STREAM, 0);
    if(listen_fd < 0)
    {
        cout << "Create Socket Failed!";
        exit(1);
    }
    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, & amp;opt, sizeof(opt));
}

Server::~Server()
{
    for(int fd=0; fd<=max_fd; + + fd)
    {
        if(FD_ISSET(fd, & amp;master_set))
        {
            close(fd);
        }
    }
}

void Server::Bind()
{
    if(-1 == (bind(listen_fd, (struct sockaddr*) & amp;server_addr, sizeof(server_addr))))
    {
        cout << "Server Bind Failed!";
        exit(1);
    }
    cout << "Bind Successfully.\\
";
}

void Server::Listen(int queue_len)
{
    if(-1 == listen(listen_fd, queue_len))
    {
        cout << "Server Listen Failed!";
        exit(1);
    }
    cout << "Listen Successfully.\\
";
}

void Server::Accept()
{
    struct sockaddr_in client_addr;
    socklen_t client_addr_len = sizeof(client_addr);

    int new_fd = accept(listen_fd, (struct sockaddr*) & amp;client_addr, & amp;client_addr_len);
    if(new_fd < 0)
    {
        cout << "Server Accept Failed!";
        exit(1);
    }

    string ip(inet_ntoa(client_addr.sin_addr)); // Get the client IP

    cout << ip << " new connection was accepted.\\
";

    mmap.insert(make_pair(new_fd, make_pair(ip, 0)));

    // Add the fd of the newly established connection to master_set
    FD_SET(new_fd, & amp;master_set);
    if(new_fd > max_fd)
    {
        max_fd = new_fd;
    }
}

void Server::Recv(int nums)
{
    for(int fd=0; fd<=max_fd; + + fd)
    {
        if(FD_ISSET(fd, & amp;working_set))
        {
            bool close_conn = false; // Mark whether the current connection is disconnected

            PACKET_HEAD head;
            recv(fd, & amp;head, sizeof(head), 0); // Accept the header first

            if(head.type == HEART)
            {
                mmap[fd].second = 0; // Each time a heartbeat packet is received, count is set to 0
                cout << "Received heart-beat from client.\\
";
            }
            else
            {
                //Data packet, confirm the length of the data packet through head.length
            }

            if(close_conn) // There is a problem with the current connection, close it
            {
                close(fd);
                FD_CLR(fd, & amp;master_set);
                if(fd == max_fd) // max_fd needs to be updated;
                {
                    while(FD_ISSET(max_fd, & amp;master_set) == false)
                        --max_fd;
                }
            }
        }
    }
}

void Server::Run()
{
    pthread_t id; // Create heartbeat detection thread
    int ret = pthread_create( & id, NULL, heart_handler, (void*)this);
    if(ret != 0)
    {
        cout << "Can not create heart-beat checking thread.\\
";
    }

    max_fd = listen_fd; // Initialize max_fd
    FD_ZERO( & amp;master_set);
    FD_SET(listen_fd, & amp;master_set); //Add listening fd

    while(1)
    {
        FD_ZERO( & amp;working_set);
        memcpy( & amp;working_set, & amp;master_set, sizeof(master_set));

        timeout.tv_sec = 30;
        timeout.tv_usec = 0;

        int nums = select(max_fd + 1, & working_set, NULL, NULL, & timeout);
        if(nums < 0)
        {
            cout << "select() error!";
            exit(1);
        }

        if(nums == 0)
        {
            //cout << "select() is timeout!";
            continue;
        }

        if(FD_ISSET(listen_fd, & amp;working_set))
            Accept(); // There is a new client request
        else
            Recv(nums); //Receive messages from the client
    }
}


// thread function
void* heart_handler(void* arg)
{
    cout << "The heartbeat checking thread started.\\
";
    Server* s = (Server*)arg;
    while(1)
    {
        map<int, pair<string, int> >::iterator it = s->mmap.begin();
        for( ; it!=s->mmap.end(); )
        {
            if(it->second.second == 5) // If no heartbeat packet is received in 3s*5, it is determined that the client is offline
            {
                cout << "The client " << it->second.first << " has be offline.\\
";

                int fd = it->first;
                close(fd); // Close the connection
                FD_CLR(fd, & amp;s->master_set);
                if(fd == s->max_fd) //max_fd needs to be updated;
                {
                    while(FD_ISSET(s->max_fd, & amp;s->master_set) == false)
                        s->max_fd--;
                }

                s->mmap.erase(it + + ); // Remove the record from the map
            }
            else if(it->second.second < 5 & amp; & amp; it->second.second >= 0)
            {
                it->second.second + = 1;
                 + + it;
            }
            else
            {
                 + + it;
            }
        }
        sleep(3); // Timer for three seconds
    }
}

int main()
{
    Server server(15000);
    server.Bind();
    server.Listen();
    server.Run();
    return 0;
}
/********************************************** ***************************
    > File Name: Client.cpp
    > Author: SongLee
    > E-mail: [email protected]
    > Created Time: Thursday, May 5, 2016 23:41:56
    > Personal Blog: http://songlee24.github.io/
 *************************************************** ************************/
#include<netinet/in.h> // sockaddr_in
#include<sys/types.h> // socket
#include<sys/socket.h> // socket
#include<arpa/inet.h>
#include<sys/ioctl.h>
#include<unistd.h>
#include<iostream>
#include<string>
#include<cstdlib>
#include<cstdio>
#include<cstring>
using namespace std;
#defineBUFFER_SIZE 1024

enum Type {HEART, OTHER};

struct PACKET_HEAD
{
    Type type;
    int length;
};

void* send_heart(void* arg);

class Client
{
private:
    struct sockaddr_in server_addr;
    socklen_t server_addr_len;
    int fd;
public:
    Client(string ip, int port);
    ~Client();
    void Connect();
    void Run();
    friend void* send_heart(void* arg);
};

Client::Client(string ip, int port)
{
    bzero( & amp;server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    if(inet_pton(AF_INET, ip.c_str(), & amp;server_addr.sin_addr) == 0)
    {
        cout << "Server IP Address Error!";
        exit(1);
    }
    server_addr.sin_port = htons(port);
    server_addr_len = sizeof(server_addr);
    // create socket
    fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd < 0)
    {
        cout << "Create Socket Failed!";
        exit(1);
    }
}

Client::~Client()
{
    close(fd);
}

void Client::Connect()
{
    cout << "Connecting......" << endl;
    if(connect(fd, (struct sockaddr*) & amp;server_addr, server_addr_len) < 0)
    {
        cout << "Can not Connect to Server IP!";
        exit(1);
    }
    cout << "Connect to Server successfully." << endl;
}

void Client::Run()
{
    pthread_t id;
    int ret = pthread_create( & id, NULL, send_heart, (void*)this);
    if(ret != 0)
    {
        cout << "Can not create thread!";
        exit(1);
    }
}

// thread function
void* send_heart(void* arg)
{
    cout << "The heartbeat sending thread started.\\
";
    Client* c = (Client*)arg;
    int count = 0; // test
    while(1)
    {
        PACKET_HEAD head;
        head.type = HEART;
        head.length = 0;
        send(c->fd, & amp;head, sizeof(head), 0);
        sleep(3); // Timer for 3 seconds

         + + count; // Test: Stop sending heartbeat packets after sending 15 times
        if(count > 15)
            break;
    }
}

int main()
{
    Client client("127.0.0.1", 15000);
    client.Connect();
    client.Run();
    while(1)
    {
        string msg;
        getline(cin, msg);
        if(msg == "exit")
            break;
        cout << "msg\\
";
    }
    return 0;
}

It can be seen that the client sent heartbeat packets 15 times after starting, and then stopped sending heartbeat packets. After a period of time (3s*5), the server determines that the client is offline and disconnects.

—————-
Copyright statement: This article is an original article by CSDN blogger “Shenyi” and follows the CC 4.0 BY-SA copyright agreement. Please attach the original source link and this statement when reprinting.
Original link: https://blog.csdn.net/lisonglisonglisong/article/details/51327695