网站关键词搜索优化是怎么做的,免费网站建设推荐,wordpress主题 有分页,wordpress内存分配不足Socket中的心跳机制(心跳包)
1. 什么是心跳机制#xff1f;(心跳包)
在客户端和服务端长时间没有相互发送数据的情况下#xff0c;我们需要一种机制来判断连接是否依然存在。直接发送任何数据包可以实现这一点#xff0c;但为了效率和简洁#xff0c;通常发送一个空包(心跳包)
在客户端和服务端长时间没有相互发送数据的情况下我们需要一种机制来判断连接是否依然存在。直接发送任何数据包可以实现这一点但为了效率和简洁通常发送一个空包这个就是心跳包。
心跳包类似心跳每隔固定时间发送一次通知服务器客户端依然活着。它是一种保持长连接的机制包的内容没有特别规定通常是很小的包或仅包含包头的空包。
心跳包可以由客户端发到服务器也可以由服务器发到客户端但一般是客户端发到服务器。发送心跳包需要额外的线程不能和正常数据发送的线程混在一起。发送间隔根据具体业务情况决定通常在while循环中加sleep()函数即可。
2. 心跳包的实现技术
心跳包可以通过两种方式实现
2.1 应用层自实现
由应用程序自己发送心跳包来检测连接是否正常服务器每隔一定时间向客户端发送一个短小的数据包然后启动一个线程在线程中不断检测客户端的回应 如果在一定时间内没有收到客户端的回应即认为客户端已经掉线同样如果客户端在一定时间内没有收到服务器的心跳包则认为连接不可用
2.2 使用SO_KEEPALIVE套接字选项
在TCP机制中默认存在一个心跳频率为2小时的机制但无法检测断电、网线拔出、防火墙等断线情况。因此在逻辑层常用空包发送来实现心跳包。服务器定时发送空包给客户端客户端收到后回复一个同样的空包服务器如果在一定时间内未收到回复则认为客户端掉线。在长连接下如果一段时间没有数据往来有可能会被中间节点断开连接因此需要心跳包维持连接。一旦断线服务器逻辑可能需要清理数据、重新连接等处理。通常情况下心跳包的判定时间为30-40秒要求高时可缩短至6-9秒。
1. setsockopt函数介绍
setsockopt函数用于设置与某个套接字关联的选项。选项可能存在于多层协议中但它们总会出现在最上面的套接字层。要操作套接字层的选项应该将层的值指定为SOL_SOCKET。要操作其他层的选项需要提供合适的协议号。函数原型如下
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen)sock: 将要被设置选项的套接字level: 选项所在的协议层optname: 需要访问的选项名optval: 指向包含新选项值的缓冲optlen: 现选项的长度
2. 心跳机制的实现
在TCP客户端代码中加入心跳机制使服务端在断网重连后能自动保持连接。
#include socket_tcp_server.h
#include tcp_keepalive.h
#include socket_wrap.h
#include ctype.h
#include FreeRTOS.h
#include task.hstatic char ReadBuff[BUFF_SIZE];void vTcpKeepaliveTask(void){int cfd, n, i, ret;struct sockaddr_in server_addr;int so_keepalive_val 1;int tcp_keepalive_idle 3;int tcp_keepalive_intvl 3;int tcp_keepalive_cnt 3;int tcp_nodelay 1;again: //创建socketcfd Socket(AF_INET, SOCK_STREAM, 0);//使能socket层的心跳检测setsockopt(cfd, SOL_SOCKET, SO_KEEPALIVE, so_keepalive_val, sizeof(int));server_addr.sin_family AF_INET;server_addr.sin_port htons(SERVER_PORT);server_addr.sin_addr.s_addr inet_addr(SERVER_IP);//连接到服务器ret Connect(cfd, (struct sockaddr*)server_addr, sizeof(server_addr));if(ret 0){//100ms去连接一次服务器vTaskDelay(100);goto again;}//配置心跳检测参数setsockopt(cfd, IPPROTO_TCP, TCP_KEEPIDLE, tcp_keepalive_idle, sizeof(int)); setsockopt(cfd, IPPROTO_TCP, TCP_KEEPINTVL, tcp_keepalive_intvl, sizeof(int)); setsockopt(cfd, IPPROTO_TCP, TCP_KEEPCNT, tcp_keepalive_cnt, sizeof(int)); setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, tcp_nodelay, sizeof(int)); printf(server is connect ok\r\n);while(1){//等待服务器发送数据n Read(cfd, ReadBuff, BUFF_SIZE);if(n 0){ goto again; }//进行大小写转换for(i 0; i n; i){ ReadBuff[i] toupper(ReadBuff[i]); }//写回服务器n Write(cfd, ReadBuff, n);if(n 0){ goto again; } }
}3. 心跳包的自主实现
服务端代码
主要思路
在服务端代码中首先创建一个监听套接字等待客户端连接。一旦有客户端连接成功就将其信息包括客户端的文件描述符、IP地址和心跳计数器存储在一个map中。同时服务端启动一个心跳检测线程定期遍历这个map检查每个客户端的心跳计数器。若某客户端在一定时间内未发送心跳包则认为该客户端掉线关闭相应连接并从map中移除。
#include stdio.h
#include sys/socket.h
#include netinet/in.h
#include stdlib.h
#include arpa/inet.h
#include unistd.h
#include string.h
#include vector
#include map
using namespace std;#define HEART_COUNT 5
#define BUF_SIZE 512
#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while (0)typedef mapint, pairstring, int FDMAPIP;enum Type
{HEART,OTHER
};struct PACKET_HEAD
{Type type;int length;
};//支线程传递的参数结构体
struct myparam
{FDMAPIP *mmap;
};//心跳线程
void *heart_handler(void *arg)
{printf(the heart-beat thread started\n);struct myparam *param (struct myparam *)arg;//Server *s (Server *)arg;while (1){FDMAPIP::iterator it param-mmap-begin();for (; it ! param-mmap-end();){// 3s*5没有收到心跳包判定客户端掉线if (it-second.second HEART_COUNT){printf(The client %s has be offline.\n, it-second.first.c_str());int fd it-first;close(fd); // 关闭该连接param-mmap-erase(it); // 从map中移除该记录}else if (it-second.second HEART_COUNT it-second.second 0){it-second.second 1;it;}else{it;}}sleep(3); // 定时三秒}
}int main()
{//创建套接字int m_sockfd socket(AF_INET, SOCK_STREAM, 0);if (m_sockfd 0){ERR_EXIT(create socket fail);}//初始化socket元素struct sockaddr_in server_addr;int server_len sizeof(server_addr);memset(server_addr, 0, server_len);server_addr.sin_family AF_INET;//server_addr.sin_addr.s_addr inet_addr(0.0.0.0); //用这个写法也可以server_addr.sin_addr.s_addr INADDR_ANY;server_addr.sin_port htons(39002);//绑定文件描述符和服务器的ip和端口号int m_bindfd bind(m_sockfd, (struct sockaddr *)server_addr, server_len);if (m_bindfd 0){ERR_EXIT(bind ip and port fail);}//进入监听状态等待用户发起请求int m_listenfd listen(m_sockfd, 20);if (m_listenfd 0){ERR_EXIT(listen client fail);}//定义客户端的套接字这里返回一个新的套接字后面通信时就用这个m_connfd进行通信struct sockaddr_in client_addr;socklen_t client_len sizeof(client_addr);int m_connfd accept(m_sockfd, (struct sockaddr *)client_addr, client_len);printf(client accept success\n);string ip(inet_ntoa(client_addr.sin_addr)); // 获取客户端IP// 记录连接的客户端fd--ip, count暂时就一个FDMAPIP mmap;mmap.insert(make_pair(m_connfd, make_pair(ip, 0)));struct myparam param;param.mmap mmap;// 创建心跳检测线程pthread_t id;int ret pthread_create(id, NULL, heart_handler, ¶m);if (ret ! 0){printf(cant create heart-beat thread.\n);}//接收客户端数据并相应char buffer[BUF_SIZE];while (1){if (m_connfd 0){m_connfd accept(m_sockfd, (struct sockaddr *)client_addr, client_len);printf(client accept success again\n);}PACKET_HEAD head;int recv_len recv(m_connfd, head, sizeof(head), 0); // 先接受包头if (recv_len 0){close(m_connfd);m_connfd -1;printf(client head lose connection\n);continue;}if (head.type HEART){mmap[m_connfd].second 0;printf(receive heart beat from client.\n);}else{//接收数据部分}}//关闭套接字close(m_connfd);close(m_sockfd);printf(server socket closed!!!\n);return 0;
}客户端代码
主要思路
客户端代码中首先创建一个套接字并连接到服务端。连接建立后客户端启动一个心跳发送线程定期向服务端发送心跳包。发送心跳包的过程不需要携带任何实际数据只需发送一个标识心跳的消息即可。服务端收到心跳包后将对应客户端的心跳计数器重置为0表示该客户端仍然活跃。如果服务端未收到心跳包则认为客户端已经掉线。
#include stdio.h
#include sys/socket.h
#include netinet/in.h
#include stdlib.h
#include arpa/inet.h
#include unistd.h
#include string.h
#include vector
#include map
using namespace std;#define BUF_SIZE 512
#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while (0)enum Type
{HEART,OTHER
};struct PACKET_HEAD
{Type type;int length;
};//线程传递的参数结构体
struct myparam
{ int fd;
};void *send_heart(void *arg)
{printf(the heartbeat sending thread started.\n);struct myparam *param (struct myparam *)arg;int count 0; // 测试while (1){PACKET_HEAD head;//发送心跳包head.type HEART;head.length 0;send(param-fd, head, sizeof(head), 0);// 定时3秒这个可以根据业务需求来设定sleep(3); }
}int main()
{//创建套接字int m_sockfd socket(AF_INET, SOCK_STREAM, 0);if (m_sockfd 0){ERR_EXIT(create socket fail);}//服务器的ip为本地端口号struct sockaddr_in server_addr;memset(server_addr, 0, sizeof(server_addr));server_addr.sin_family AF_INET;server_addr.sin_addr.s_addr inet_addr(81.68.140.74);server_addr.sin_port htons(39002);//向服务器发送连接请求int m_connectfd connect(m_sockfd, (struct sockaddr *)server_addr, sizeof(server_addr));if (m_connectfd 0){ERR_EXIT(connect server fail);}struct myparam param;param.fd m_sockfd;pthread_t id;int ret pthread_create(id, NULL, send_heart, ¶m);if (ret ! 0){printf(create thread fail.\n);}//发送并接收数据char buffer[BUF_SIZE] asdfg;int len strlen(buffer);while (1){// 发送数据部分;}//断开连接close(m_sockfd);printf(client socket closed!!!\n);return 0;
}