思路:每几秒发送一条不显示的信息,客户端断开则不再发送信息,超时则表示客户端断开连接。(心跳包)
服务器
#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 64int 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;
}