ITPub博客

首页 > 应用开发 > IT综合 > UDP Socket编程实践:实现轻量级聊天室系统

UDP Socket编程实践:实现轻量级聊天室系统

投稿 IT综合 作者:bhluo 时间:2025-12-25 14:42:38 0 删除 编辑

# UDP Socket编程实践:实现轻量级聊天室系统


UDP协议以其无连接、低延迟的特性,特别适合实现实时聊天应用。本文将深入探讨基于UDP的Socket编程,实现一个完整的聊天室系统,涵盖广播通信、用户管理和消息处理。


## UDP聊天室的独特优势


与TCP相比,UDP实现聊天室具有以下特点:

1. **无连接开销**:无需建立/断开连接

2. **广播支持**:天然支持组播和广播

3. **低延迟**:无拥塞控制和重传机制

4. **资源占用少**:服务端状态维护简单


## 系统架构设计


聊天室采用客户端-服务器架构,支持以下功能:

- 用户注册和登录

- 群聊消息广播

- 私聊消息传递

- 用户状态通知

- 消息历史记录


## 通信协议设计


```c

// chat_protocol.h

#ifndef CHAT_PROTOCOL_H

#define CHAT_PROTOCOL_H


#include <stdint.h>

#include <arpa/inet.h>


// 消息类型

typedef enum {

    MSG_TYPE_REGISTER = 0x01,     // 注册/登录

    MSG_TYPE_BROADCAST = 0x02,    // 广播消息

    MSG_TYPE_PRIVATE = 0x03,      // 私聊消息

    MSG_TYPE_USER_LIST = 0x04,    // 用户列表

    MSG_TYPE_LOGOUT = 0x05,       // 退出登录

    MSG_TYPE_HEARTBEAT = 0x06,    // 心跳包

    MSG_TYPE_NOTIFICATION = 0x07, // 系统通知

    MSG_TYPE_ERROR = 0x08         // 错误消息

} MsgType;


// 消息头(固定12字节)

typedef struct {

    uint32_t magic;        // 魔数 0x43484154 (CHAT)

    uint8_t version;       // 协议版本

    uint8_t type;          // 消息类型

    uint16_t seq;          // 序列号

    uint32_t length;       // 数据部分长度

    uint32_t checksum;     // 校验和(可选)

} ChatHeader;


// 用户信息结构

typedef struct {

    char username[32];     // 用户名

    char nickname[32];     // 昵称

    uint32_t user_id;      // 用户ID

    uint32_t ip_addr;      // IP地址

    uint16_t port;         // 端口号

    uint8_t status;        // 状态:0-离线 1-在线 2-忙碌

    uint32_t last_seen;    // 最后活动时间戳

} UserInfo;


// 注册/登录请求

typedef struct {

    char username[32];

    char password[64];    // 简单实现,实际应加密

    char nickname[32];

} RegisterRequest;


// 聊天消息

typedef struct {

    uint32_t sender_id;

    uint32_t receiver_id; // 0表示广播

    char content[256];

    uint64_t timestamp;

} ChatMessage;


// 响应结构

typedef struct {

    uint8_t success;      // 0-失败 1-成功

    uint32_t code;        // 状态码

    char message[128];    // 附加信息

} Response;


// 计算校验和

static inline uint32_t calculate_checksum(const void *data, size_t len) {

    uint32_t sum = 0;

    const uint8_t *ptr = (const uint8_t *)data;

    for (size_t i = 0; i < len; i++) {

        sum += ptr[i];

    }

    return sum;

}


#endif

```


## 服务器端实现


服务器采用单线程非阻塞模式,使用select处理多个客户端:


```c

// server.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

#include <sys/select.h>

#include <time.h>

#include <pthread.h>

#include "chat_protocol.h"


#define SERVER_PORT 8888

#define MAX_CLIENTS 1000

#define BUFFER_SIZE 1024

#define TIMEOUT_SECONDS 300  // 5分钟超时


// 在线用户管理

typedef struct {

    UserInfo info;

    struct sockaddr_in addr;

    time_t last_activity;

    int authenticated;

} OnlineUser;


typedef struct {

    OnlineUser users[MAX_CLIENTS];

    int count;

    pthread_mutex_t lock;

} UserManager;


// 消息历史记录

typedef struct {

    ChatMessage messages[1000];

    int head;

    int tail;

    pthread_mutex_t lock;

} MessageHistory;


// 全局变量

UserManager user_manager;

MessageHistory msg_history;

int server_fd;


// 初始化用户管理器

void init_user_manager(UserManager *manager) {

    memset(manager, 0, sizeof(UserManager));

    pthread_mutex_init(&manager->lock, NULL);

}


// 初始化消息历史

void init_message_history(MessageHistory *history) {

    memset(history, 0, sizeof(MessageHistory));

    pthread_mutex_init(&history->lock, NULL);

}


// 查找用户

OnlineUser* find_user_by_addr(UserManager *manager, struct sockaddr_in *addr) {

    for (int i = 0; i < manager->count; i++) {

        if (manager->users[i].addr.sin_addr.s_addr == addr->sin_addr.s_addr &&

            manager->users[i].addr.sin_port == addr->sin_port) {

            return &manager->users[i];

        }

    }

    return NULL;

}


// 添加在线用户

int add_online_user(UserManager *manager, UserInfo *info, struct sockaddr_in *addr) {

    pthread_mutex_lock(&manager->lock);

    

    if (manager->count >= MAX_CLIENTS) {

        pthread_mutex_unlock(&manager->lock);

        return -1;

    }

    

    // 检查是否已存在

    for (int i = 0; i < manager->count; i++) {

        if (strcmp(manager->users[i].info.username, info->username) == 0) {

            // 更新地址和状态

            manager->users[i].addr = *addr;

            manager->users[i].last_activity = time(NULL);

            manager->users[i].authenticated = 1;

            pthread_mutex_unlock(&manager->lock);

            return i;

        }

    }

    

    // 添加新用户

    int index = manager->count;

    manager->users[index].info = *info;

    manager->users[index].addr = *addr;

    manager->users[index].last_activity = time(NULL);

    manager->users[index].authenticated = 1;

    manager->count++;

    

    pthread_mutex_unlock(&manager->lock);

    return index;

}


// 移除离线用户

void remove_inactive_users(UserManager *manager) {

    time_t now = time(NULL);

    pthread_mutex_lock(&manager->lock);

    

    for (int i = 0; i < manager->count; i++) {

        if (now - manager->users[i].last_activity > TIMEOUT_SECONDS) {

            // 移动数组元素

            for (int j = i; j < manager->count - 1; j++) {

                manager->users[j] = manager->users[j + 1];

            }

            manager->count--;

            i--;

        }

    }

    

    pthread_mutex_unlock(&manager->lock);

}


// 广播消息给所有在线用户

void broadcast_message(UserManager *manager, ChatMessage *msg, 

                      struct sockaddr_in *exclude_addr) {

    char buffer[BUFFER_SIZE];

    ChatHeader header = {

        .magic = htonl(0x43484154),

        .version = 1,

        .type = MSG_TYPE_BROADCAST,

        .seq = 0,

        .length = htonl(sizeof(ChatMessage))

    };

    

    memcpy(buffer, &header, sizeof(header));

    memcpy(buffer + sizeof(header), msg, sizeof(ChatMessage));

    

    pthread_mutex_lock(&manager->lock);

    

    for (int i = 0; i < manager->count; i++) {

        if (exclude_addr && 

            manager->users[i].addr.sin_port == exclude_addr->sin_port &&

            manager->users[i].addr.sin_addr.s_addr == exclude_addr->sin_addr.s_addr) {

            continue;  // 排除发送者

        }

        

        if (manager->users[i].authenticated) {

            sendto(server_fd, buffer, sizeof(header) + sizeof(ChatMessage),

                   0, (struct sockaddr*)&manager->users[i].addr,

                   sizeof(struct sockaddr_in));

        }

    }

    

    pthread_mutex_unlock(&manager->lock);

}


// 发送私聊消息

int send_private_message(UserManager *manager, ChatMessage *msg) {

    pthread_mutex_lock(&manager->lock);

    

    for (int i = 0; i < manager->count; i++) {

        if (manager->users[i].info.user_id == msg->receiver_id) {

            char buffer[BUFFER_SIZE];

            ChatHeader header = {

                .magic = htonl(0x43484154),

                .version = 1,

                .type = MSG_TYPE_PRIVATE,

                .seq = 0,

                .length = htonl(sizeof(ChatMessage))

            };

            

            memcpy(buffer, &header, sizeof(header));

            memcpy(buffer + sizeof(header), msg, sizeof(ChatMessage));

            

            sendto(server_fd, buffer, sizeof(header) + sizeof(ChatMessage),

                   0, (struct sockaddr*)&manager->users[i].addr,

                   sizeof(struct sockaddr_in));

            

            pthread_mutex_unlock(&manager->lock);

            return 0;

        }

    }

    

    pthread_mutex_unlock(&manager->lock);

    return -1;  // 用户不在线

}


// 处理注册/登录请求

void handle_register(struct sockaddr_in *client_addr, 

                    RegisterRequest *req, int req_len) {

    char buffer[BUFFER_SIZE];

    Response resp = {0};

    

    // 简单的用户验证(实际应使用数据库)

    if (strlen(req->username) < 3 || strlen(req->password) < 6) {

        resp.success = 0;

        resp.code = 400;

        strcpy(resp.message, "用户名或密码太短");

    } else {

        // 创建用户信息

        UserInfo user_info;

        strncpy(user_info.username, req->username, 31);

        strncpy(user_info.nickname, req->nickname, 31);

        user_info.user_id = time(NULL) % 1000000;  // 简单ID生成

        user_info.ip_addr = client_addr->sin_addr.s_addr;

        user_info.port = client_addr->sin_port;

        user_info.status = 1;

        user_info.last_seen = time(NULL);

        

        // 添加到在线用户列表

        int user_index = add_online_user(&user_manager, &user_info, client_addr);

        

        resp.success = (user_index >= 0) ? 1 : 0;

        resp.code = resp.success ? 200 : 500;

        

        if (resp.success) {

            sprintf(resp.message, "欢迎 %s 加入聊天室!", req->nickname);

            

            // 通知其他用户有新用户加入

            ChatMessage notify_msg = {

                .sender_id = 0,  // 系统消息

                .receiver_id = 0,

                .timestamp = time(NULL)

            };

            sprintf(notify_msg.content, "%s 进入了聊天室", req->nickname);

            broadcast_message(&user_manager, &notify_msg, client_addr);

        } else {

            strcpy(resp.message, "服务器已满");

        }

    }

    

    // 发送响应

    ChatHeader header = {

        .magic = htonl(0x43484154),

        .version = 1,

        .type = MSG_TYPE_REGISTER,

        .seq = 0,

        .length = htonl(sizeof(Response))

    };

    

    memcpy(buffer, &header, sizeof(header));

    memcpy(buffer + sizeof(header), &resp, sizeof(Response));

    

    sendto(server_fd, buffer, sizeof(header) + sizeof(Response),

           0, (struct sockaddr*)client_addr, sizeof(struct sockaddr_in));

}


// 处理聊天消息

void handle_chat_message(struct sockaddr_in *client_addr, 

                        ChatMessage *msg, int is_private) {

    OnlineUser *sender = find_user_by_addr(&user_manager, client_addr);

    if (!sender || !sender->authenticated) {

        return;  // 未认证用户

    }

    

    // 更新最后活动时间

    sender->last_activity = time(NULL);

    msg->sender_id = sender->info.user_id;

    msg->timestamp = time(NULL);

    

    // 添加到消息历史

    pthread_mutex_lock(&msg_history.lock);

    msg_history.messages[msg_history.head] = *msg;

    msg_history.head = (msg_history.head + 1) % 1000;

    if (msg_history.head == msg_history.tail) {

        msg_history.tail = (msg_history.tail + 1) % 1000;  // 队列满,丢弃最旧消息

    }

    pthread_mutex_unlock(&msg_history.lock);

    

    // 广播或私发消息

    if (is_private) {

        if (send_private_message(&user_manager, msg) == 0) {

            // 同时发送给发送者(确认消息已发送)

            ChatHeader header = {

                .magic = htonl(0x43484154),

                .version = 1,

                .type = MSG_TYPE_PRIVATE,

                .seq = 0,

                .length = htonl(sizeof(ChatMessage))

            };

            

            char buffer[sizeof(header) + sizeof(ChatMessage)];

            memcpy(buffer, &header, sizeof(header));

            memcpy(buffer + sizeof(header), msg, sizeof(ChatMessage));

            

            sendto(server_fd, buffer, sizeof(buffer),

                   0, (struct sockaddr*)client_addr, sizeof(struct sockaddr_in));

        }

    } else {

        broadcast_message(&user_manager, msg, client_addr);

    }

}


// 发送用户列表

void send_user_list(struct sockaddr_in *client_addr) {

    pthread_mutex_lock(&user_manager.lock);

    

    int user_count = user_manager.count;

    int data_len = sizeof(uint32_t) + user_count * sizeof(UserInfo);

    

    // 构建响应

    char buffer[BUFFER_SIZE];

    ChatHeader header = {

        .magic = htonl(0x43484154),

        .version = 1,

        .type = MSG_TYPE_USER_LIST,

        .seq = 0,

        .length = htonl(data_len)

    };

    

    memcpy(buffer, &header, sizeof(header));

    

    // 写入用户数量

    uint32_t net_count = htonl(user_count);

    memcpy(buffer + sizeof(header), &net_count, sizeof(net_count));

    

    // 写入用户信息

    char *ptr = buffer + sizeof(header) + sizeof(uint32_t);

    for (int i = 0; i < user_count; i++) {

        memcpy(ptr, &user_manager.users[i].info, sizeof(UserInfo));

        ptr += sizeof(UserInfo);

    }

    

    sendto(server_fd, buffer, sizeof(header) + data_len,

           0, (struct sockaddr*)client_addr, sizeof(struct sockaddr_in));

    

    pthread_mutex_unlock(&user_manager.lock);

<"3m.xingjungy.cn"><"7d.xingjungy.cn"><"0h.xingjungy.cn">

}


// 主服务器循环

void run_server() {

    struct sockaddr_in client_addr;

    socklen_t addr_len = sizeof(client_addr);

    char buffer[BUFFER_SIZE];

    

    printf("UDP聊天服务器启动,端口: %d\n", SERVER_PORT);

    

    while (1) {

        // 使用select实现超时和轮询

        fd_set readfds;

        struct timeval tv;

        

        FD_ZERO(&readfds);

        FD_SET(server_fd, &readfds);

        

        tv.tv_sec = 1;  // 1秒超时

        tv.tv_usec = 0;

        

        int ret = select(server_fd + 1, &readfds, NULL, NULL, &tv);

        

        if (ret < 0) {

            perror("select错误");

            continue;

        }

        

        if (ret == 0) {

            // 超时,清理不活跃用户

            remove_inactive_users(&user_manager);

            continue;

        }

        

        // 接收数据

        ssize_t recv_len = recvfrom(server_fd, buffer, BUFFER_SIZE, 0,

                                   (struct sockaddr*)&client_addr, &addr_len);

        

        if (recv_len < (ssize_t)sizeof(ChatHeader)) {

            continue;

        }

        

        // 解析消息头

        ChatHeader *header = (ChatHeader*)buffer;

        header->magic = ntohl(header->magic);

        header->length = ntohl(header->length);

        

        if (header->magic != 0x43484154) {

            continue;  // 无效消息

        }

        

        // 处理不同类型消息

        switch (header->type) {

            case MSG_TYPE_REGISTER: {

                if (recv_len >= sizeof(ChatHeader) + sizeof(RegisterRequest)) {

                    RegisterRequest *req = (RegisterRequest*)(buffer + sizeof(ChatHeader));

                    handle_register(&client_addr, req, header->length);

                }

                break;

            }

                

            case MSG_TYPE_BROADCAST: {

                if (recv_len >= sizeof(ChatHeader) + sizeof(ChatMessage)) {

                    ChatMessage *msg = (ChatMessage*)(buffer + sizeof(ChatHeader));

                    handle_chat_message(&client_addr, msg, 0);

                }

                break;

            }

                

            case MSG_TYPE_PRIVATE: {

                if (recv_len >= sizeof(ChatHeader) + sizeof(ChatMessage)) {

                    ChatMessage *msg = (ChatMessage*)(buffer + sizeof(ChatHeader));

                    handle_chat_message(&client_addr, msg, 1);

                }

                break;

            }

                

            case MSG_TYPE_USER_LIST: {

                send_user_list(&client_addr);

                break;

            }

                

            case MSG_TYPE_HEARTBEAT: {

                OnlineUser *user = find_user_by_addr(&user_manager, &client_addr);

                if (user) {

                    user->last_activity = time(NULL);

                }

                break;

            }

                

            case MSG_TYPE_LOGOUT: {

                // 处理用户退出

                break;

            }

        }

    }

}


int main() {

    // 初始化

    init_user_manager(&user_manager);

    init_message_history(&msg_history);

    

    // 创建UDP socket

    server_fd = socket(AF_INET, SOCK_DGRAM, 0);

    if (server_fd < 0) {

        perror("创建socket失败");

        return 1;

    }

    

    // 设置socket选项

    int broadcast = 1;

    setsockopt(server_fd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));

    

    int reuse = 1;

    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

    

    // 绑定地址

    struct sockaddr_in server_addr;

    memset(&server_addr, 0, sizeof(server_addr));

    server_addr.sin_family = AF_INET;

    server_addr.sin_addr.s_addr = INADDR_ANY;

    server_addr.sin_port = htons(SERVER_PORT);

    

    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {

        perror("绑定地址失败");

        close(server_fd);

        return 1;

    }

    

    // 运行服务器

    run_server();

    

    close(server_fd);

    return 0;

}

```


## 客户端实现


客户端提供用户界面,支持消息发送和接收:


```c

// client.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <arpa/inet.h>

#include <sys/socket.h>

#include <sys/select.h>

#include <pthread.h>

#include <termios.h>

#include "chat_protocol.h"


#define SERVER_IP "127.0.0.1"

#define SERVER_PORT 8888

#define BUFFER_SIZE 1024


// 全局状态

typedef struct {

    int sock_fd;

    struct sockaddr_in server_addr;

    UserInfo my_info;

    int authenticated;

    int running;

} ClientState;


// 原始终端模式设置

void set_terminal_raw_mode(int enable) {

    static struct termios oldt, newt;

    

    if (enable) {

        tcgetattr(STDIN_FILENO, &oldt);

        newt = oldt;

        newt.c_lflag &= ~(ICANON | ECHO);

        tcsetattr(STDIN_FILENO, TCSANOW, &newt);

    } else {

        tcsetattr(STDIN_FILENO, TCSANOW, &oldt);

    }

}


// 发送注册/登录请求

int send_register_request(ClientState *state, 

                         const char *username, 

                         const char *password,

                         const char *nickname) {

    char buffer[BUFFER_SIZE];

    

    // 构建请求

    RegisterRequest req;

    strncpy(req.username, username, 31);

    strncpy(req.password, password, 63);

    strncpy(req.nickname, nickname, 31);

    

    ChatHeader header = {

        .magic = htonl(0x43484154),

        .version = 1,

        .type = MSG_TYPE_REGISTER,

        .seq = 0,

        .length = htonl(sizeof(RegisterRequest))

    };

    

    memcpy(buffer, &header, sizeof(header));

    memcpy(buffer + sizeof(header), &req, sizeof(req));

    

    // 发送请求

    if (sendto(state->sock_fd, buffer, sizeof(header) + sizeof(req), 0,

              (struct sockaddr*)&state->server_addr, 

              sizeof(state->server_addr)) < 0) {

        return -1;

    }

    

    // 等待响应

    fd_set readfds;

    struct timeval tv;

    

    FD_ZERO(&readfds);

    FD_SET(state->sock_fd, &readfds);

    

    tv.tv_sec = 5;  // 5秒超时

    tv.tv_usec = 0;

    

    if (select(state->sock_fd + 1, &readfds, NULL, NULL, &tv) > 0) {

        struct sockaddr_in from_addr;

        socklen_t addr_len = sizeof(from_addr);

        

        ssize_t recv_len = recvfrom(state->sock_fd, buffer, BUFFER_SIZE, 0,

                                   (struct sockaddr*)&from_addr, &addr_len);

        

        if (recv_len >= sizeof(ChatHeader) + sizeof(Response)) {

            ChatHeader *resp_header = (ChatHeader*)buffer;

            resp_header->magic = ntohl(resp_header->magic);

            

            if (resp_header->magic == 0x43484154 && 

                resp_header->type == MSG_TYPE_REGISTER) {

                Response *resp = (Response*)(buffer + sizeof(ChatHeader));

                if (resp->success) {

                    state->authenticated = 1;

                    printf("登录成功: %s\n", resp->message);

                    return 0;

                } else {

                    printf("登录失败: %s\n", resp->message);

                }

            }

        }

    }

    

    return -1;

}


// 发送聊天消息

void send_chat_message(ClientState *state, const char *content, uint32_t receiver_id) {

    char buffer[BUFFER_SIZE];

    

    ChatMessage msg;

    msg.sender_id = state->my_info.user_id;

    msg.receiver_id = receiver_id;

    strncpy(msg.content, content, 255);

    msg.timestamp = time(NULL);

    

    ChatHeader header = {

        .magic = htonl(0x43484154),

        .version = 1,

        .type = receiver_id == 0 ? MSG_TYPE_BROADCAST : MSG_TYPE_PRIVATE,

        .seq = 0,

        .length = htonl(sizeof(ChatMessage))

<"4s.xingjungy.cn"><"6f.xingjungy.cn"><"9j.xingjungy.cn">

    };

    

    memcpy(buffer, &header, sizeof(header));

    memcpy(buffer + sizeof(header), &msg, sizeof(msg));

    

    sendto(state->sock_fd, buffer, sizeof(header) + sizeof(msg), 0,

          (struct sockaddr*)&state->server_addr, sizeof(state->server_addr));

}


// 接收消息线程

void* receive_thread(void *arg) {

    ClientState *state = (ClientState*)arg;

    char buffer[BUFFER_SIZE];

    

    while (state->running) {

        fd_set readfds;

        struct timeval tv;

        

        FD_ZERO(&readfds);

        FD_SET(state->sock_fd, &readfds);

        

        tv.tv_sec = 1;

        tv.tv_usec = 0;

        

        if (select(state->sock_fd + 1, &readfds, NULL, NULL, &tv) > 0) {

            struct sockaddr_in from_addr;

            socklen_t addr_len = sizeof(from_addr);

            

            ssize_t recv_len = recvfrom(state->sock_fd, buffer, BUFFER_SIZE, 0,

                                       (struct sockaddr*)&from_addr, &addr_len);

            

            if (recv_len >= sizeof(ChatHeader)) {

                ChatHeader *header = (ChatHeader*)buffer;

                header->magic = ntohl(header->magic);

                header->length = ntohl(header->length);

                

                if (header->magic != 0x43484154) {

                    continue;

                }

                

                switch (header->type) {

                    case MSG_TYPE_BROADCAST: {

                        if (recv_len >= sizeof(ChatHeader) + sizeof(ChatMessage)) {

                            ChatMessage *msg = (ChatMessage*)(buffer + sizeof(ChatHeader));

                            printf("\n[群聊] %s: %s\n", 

                                   msg->sender_id == 0 ? "系统" : "用户",

                                   msg->content);

                            printf("> ");

                            fflush(stdout);

                        }

                        break;

                    }

                        

                    case MSG_TYPE_PRIVATE: {

                        if (recv_len >= sizeof(ChatHeader) + sizeof(ChatMessage)) {

                            ChatMessage *msg = (ChatMessage*)(buffer + sizeof(ChatHeader));

                            printf("\n[私聊] 来自用户%d: %s\n", 

                                   msg->sender_id, msg->content);

                            printf("> ");

                            fflush(stdout);

                        }

                        break;

                    }

                        

                    case MSG_TYPE_USER_LIST: {

                        // 处理用户列表

                        break;

                    }

                        

                    case MSG_TYPE_NOTIFICATION: {

                        if (recv_len >= sizeof(ChatHeader) + header->length) {

                            char *notification = buffer + sizeof(ChatHeader);

                            notification[header->length] = '\0';

                            printf("\n[通知] %s\n", notification);

                            printf("> ");

                            fflush(stdout);

                        }

                        break;

                    }

                }

            }

        }

    }

    

    return NULL;

}


// 发送心跳包

void* heartbeat_thread(void *arg) {

    ClientState *state = (ClientState*)arg;

    char buffer[sizeof(ChatHeader)];


    

    ChatHeader header = {

        .magic = htonl(0x43484154),

        .version = 1,

        .type = MSG_TYPE_HEARTBEAT,

        .seq = 0,

        .length = 0

    };

    

    memcpy(buffer, &header, sizeof(header));

    

    while (state->running) {

        if (state->authenticated) {

            sendto(state->sock_fd, buffer, sizeof(header), 0,

                  (struct sockaddr*)&state->server_addr, sizeof(state->server_addr));

        }

        sleep(60);  // 每分钟发送一次心跳

    }

    

    return NULL;

}


// 用户输入处理

void handle_user_input(ClientState *state) {

    char input[256];

    

    printf("UDP聊天客户端\n");

    printf("============\n");

    

    // 登录/注册

    while (!state->authenticated) {

        char username[32], password[64], nickname[32];

        

        printf("用户名: ");

        fgets(username, 32, stdin);

        username[strcspn(username, "\n")] = '\0';

        

        printf("密码: ");

        // 简单密码输入(实际应隐藏显示)

        fgets(password, 64, stdin);

        password[strcspn(password, "\n")] = '\0';

        

        printf("昵称: ");

        fgets(nickname, 32, stdin);

        nickname[strcspn(nickname, "\n")] = '\0';

        

        if (send_register_request(state, username, password, nickname) == 0) {

            strcpy(state->my_info.username, username);

            strcpy(state->my_info.nickname, nickname);

            break;

        }

        

        printf("\n");

    }

    

    printf("\n欢迎 %s!\n", state->my_info.nickname);

    printf("命令:\n");

    printf("  /list - 显示在线用户\n");

    printf("  /priv <用户ID> - 私聊用户\n");

    printf("  /exit - 退出聊天室\n");

    printf("  直接输入消息进行群聊\n\n");

    

    // 创建接收线程

    pthread_t recv_tid, heartbeat_tid;

    state->running = 1;

    

    pthread_create(&recv_tid, NULL, receive_thread, state);

    pthread_create(&heartbeat_tid, NULL, heartbeat_thread, state);

    

    // 主输入循环

    while (state->running) {

        printf("> ");

        fflush(stdout);

        

        if (!fgets(input, sizeof(input), stdin)) {

            break;

        }

        

        input[strcspn(input, "\n")] = '\0';

        

        if (strlen(input) == 0) {

            continue;

        }

        

        // 处理命令

        if (input[0] == '/') {

            if (strcmp(input, "/exit") == 0) {

                state->running = 0;

                break;

            } else if (strcmp(input, "/list") == 0) {

                // 请求用户列表

                ChatHeader header = {

                    .magic = htonl(0x43484154),

                    .version = 1,

                    .type = MSG_TYPE_USER_LIST,

                    .seq = 0,

                    .length = 0

                };

                

                sendto(state->sock_fd, &header, sizeof(header), 0,

                      (struct sockaddr*)&state->server_addr, sizeof(state->server_addr));

            } else if (strncmp(input, "/priv ", 6) == 0) {

                uint32_t user_id;

                char message[256];

                

                if (sscanf(input + 6, "%u %[^\n]", &user_id, message) == 2) {

                    send_chat_message(state, message, user_id);

                } else {

                    printf("格式: /priv <用户ID> <消息>\n");

                }

            } else {

                printf("未知命令: %s\n", input);

            }

        } else {

            // 普通聊天消息

            send_chat_message(state, input, 0);

        }

    }

    

    // 清理

    state->running = 0;

    pthread_join(recv_tid, NULL);

    pthread_join(heartbeat_tid, NULL);

}


int main() {

    ClientState state = {0};

    

    // 创建UDP socket

    state.sock_fd = socket(AF_INET, SOCK_DGRAM, 0);

    if (state.sock_fd < 0) {

        perror("创建socket失败");

        return 1;

    }

    

    // 设置服务器地址

    memset(&state.server_addr, 0, sizeof(state.server_addr));

    state.server_addr.sin_family = AF_INET;

    state.server_addr.sin_port = htons(SERVER_PORT);

    inet_pton(AF_INET, SERVER_IP, &state.server_addr.sin_addr);

    

    // 设置socket选项

    int broadcast = 1;

    setsockopt(state.sock_fd, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));

    

    // 处理用户输入

    handle_user_input(&state);

    

    close(state.sock_fd);

    printf("客户端退出\n");

    return 0;

}

```


## 编译和运行


```bash

# 编译服务器

gcc -o chat_server server.c -lpthread


# 编译客户端

gcc -o chat_client client.c -lpthread


# 启动服务器

./chat_server


# 在多个终端启动客户端

./chat_client

```


## 功能扩展建议


1. **加密通信**:添加TLS/DTLS支持

2. **文件传输**:支持发送小文件

3. **聊天室分组**:支持多个聊天室

4. **消息持久化**:保存聊天记录到数据库

5. **表情支持**:添加表情符号

6. **语音消息**:支持短语音消息


## 性能优化


```c

// 使用epoll提高性能(Linux特有)

#include <sys/epoll.h>


void setup_epoll(int server_fd) {

    int epoll_fd = epoll_create1(0);

    struct epoll_event ev;

    

    ev.events = EPOLLIN;

    ev.data.fd = server_fd;

    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &ev);

    

    struct epoll_event events[10];

    while (1) {

        int n = epoll_wait(epoll_fd, events, 10, 1000); // 1秒超时

        for (int i = 0; i < n; i++) {

            if (events[i].data.fd == server_fd) {

                // 处理数据

            }

        }

    }

}

```


## 调试和监控


```bash

# 查看UDP连接状态

netstat -uanp


# 使用tcpdump抓包分析

sudo tcpdump -i lo udp port 8888 -X


# 监控系统资源

top -p $(pidof chat_server)


# 查看socket缓冲区

cat /proc/net/udp

```


## 总结


通过实现UDP聊天室,我们深入掌握了无连接Socket编程的核心技术。UDP的简洁性和高效性使其特别适合实时通信应用,但也带来了可靠性和顺序性的挑战。


本系统通过自定义协议、心跳机制和用户状态管理,在保持UDP优势的同时,提供了一定程度的可靠性保证。多线程设计确保了客户端的响应性,而服务器端的非阻塞处理支持了高并发连接。


实际部署时,还需要考虑NAT穿透、流量控制、安全认证等生产环境需求。这个聊天室系统可以作为许多实时应用的基础框架,如在线游戏、视频会议、物联网设备通信等。掌握这些技术,将为开发高性能的实时通信系统打下坚实基础。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/19545/viewspace-3106262/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2015-11-01

  • 博文量
    895
  • 访问量
    933496