思路:每几秒发送一条不显示的信息,客户端断开则不再发送信息,超时则表示客户端断开连接。(心跳包)

服务器
#include <head.h>
#define MAX_CLIENTS 100 // 最大支持100个客户端
#define TIMEOUT 5 // 5秒超时
struct Client {
struct sockaddr_in addr;
time_t last_seen; // 记录最后一次收到该客户端数据的时间
};
struct Client client_list[MAX_CLIENTS];
int client_count = 0;
// **更新客户端心跳时间**
void update_client(struct sockaddr_in *client_addr) {
time_t now = time(NULL);
for (int i = 0; i < client_count; i++) {
if (memcmp(&client_list[i].addr, client_addr, sizeof(struct sockaddr_in)) == 0) {
client_list[i].last_seen = now; // 更新时间
return;
}
}
// **如果客户端不在列表中,则添加**
if (client_count < MAX_CLIENTS) {
client_list[client_count].addr = *client_addr;
client_list[client_count].last_seen = now;
client_count++;
}
}
// **检查超时客户端**
void check_clients() {
time_t now = time(NULL);
for (int i = 0; i < client_count; i++) {
if (now - client_list[i].last_seen > TIMEOUT) {
printf("客户端 %s:%d 断开\n",
inet_ntoa(client_list[i].addr.sin_addr),
ntohs(client_list[i].addr.sin_port));
// **移除客户端**
for (int j = i; j < client_count - 1; j++) {
client_list[j] = client_list[j + 1];
}
client_count--;
i--; // **继续检查下一个**
}
}
}
int main(int argc, const char *argv[]) {
if (argc < 2) {
printf("请输入端口号\n");
return 1;
}
short port = atoi(argv[1]);
// **创建 UDP 套接字**
int receiver = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
if (bind(receiver, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind");
return 1;
}
printf("服务器启动,监听端口 %d\n", port);
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
char buf[64];
fd_set readfds;
struct timeval timeout;
while (1) {
// **使用 select 进行超时检测**
FD_ZERO(&readfds);
FD_SET(receiver, &readfds);
timeout.tv_sec = 1; // 每秒检查一次
timeout.tv_usec = 0;
int activity = select(receiver + 1, &readfds, NULL, NULL, &timeout);
if (activity > 0) {
// **接收数据**
memset(buf, 0, sizeof(buf));
int len = recvfrom(receiver, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&client_addr, &addr_len);
if (len > 0) {
buf[len] = '\0';
update_client(&client_addr); // **更新心跳时间**
// **如果是心跳包 "PING",不打印、不转发**
if (strcmp(buf, "PING") == 0) {
continue;
}
printf("收到消息: %s\n", buf);
// **转发消息给所有在线客户端**
for (int i = 0; i < client_count; i++) {
sendto(receiver, buf, strlen(buf), 0,
(struct sockaddr*)&client_list[i].addr, sizeof(client_list[i].addr));
}
}
}
// **检查超时客户端**
check_clients();
}
return 0;
}
客户端
#include <head.h>
#include <pthread.h>
#define BUF_SIZE 64
int sender; // 套接字
struct sockaddr_in addr;
void *heartbeat(void* arg)
{
while(1)
{
// 发送空的心跳包
sendto(sender, "", 1, 0, (struct sockaddr*)&addr, sizeof(addr));
sleep(2); // 每2秒发送一次心跳包
}
}
int main(int argc, const char *argv[])
{
if (argc < 2) {
printf("请输入端口号\n");
return 1;
}
short port = atoi(argv[1]);
// 创建套接字
sender = socket(AF_INET, SOCK_DGRAM, 0);
if (sender == -1) {
perror("创建套接字失败");
return 1;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr("192.168.128.20"); // 服务器的IP地址
// 启动心跳包线程
pthread_t heart;
pthread_create(&heart, NULL, heartbeat, NULL);
while (1) {
char buf[BUF_SIZE] = "";
printf("输入:");
scanf("%s", buf);
getchar(); // 读取输入并去掉换行符
// 发送普通消息
sendto(sender, buf, strlen(buf), 0, (struct sockaddr*)&addr, sizeof(addr));
// 接收服务器的回复
int len = recvfrom(sender, buf, BUF_SIZE - 1, 0, NULL, NULL);
if (len > 0) {
buf[len] = '\0'; // 确保字符串以 '\0' 结尾
printf("接收到回复的消息: %s\n", buf);
} else {
printf("接收服务器消息失败\n");
}
}
// 关闭套接字
close(sender);
return 0;
}