思路:每幾秒發送一條不顯示的信息,客戶端斷開則不再發送信息,超時則表示客戶端斷開連接。(心跳包)
服務器
#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;
}