项目基于udp通信的聊天室

发布于:2025-05-15 ⋅ 阅读:(17) ⋅ 点赞:(0)

udp.h

#ifndef _UDP_H_
#define _UDP_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define M 32       // 用户名最大长度
#define N 1024     // 消息内容最大长度
#define ERRLOG(msg) do { perror(msg); exit(-1); } while(0)

// 消息结构体(包含操作码、用户名、消息内容)
typedef struct {
    char code;          // 操作码:'d'登录 'q'群聊 't'退出
    char user[M];       // 用户名
    char text[N];       // 消息内容(群聊内容/系统消息)
} msg_t;

// 链表节点结构体(存储客户端地址信息)
typedef struct node {
    struct sockaddr_in addr;  // 客户端socket地址
    struct node *next;        // 指向下一个节点
} node_t;

#endif

server.c

#include "udp.h"

// 创建链表头节点
int create_node(node_t **phead) {
    *phead = (node_t *)malloc(sizeof(node_t));
    if (*phead == NULL) {
        ERRLOG("内存分配失败");
    }
    (*phead)->next = NULL;
    return 0;
}

// 尾插法插入客户端节点
int insert_node(node_t *phead, struct sockaddr_in addr) {
    node_t *pnew = NULL;
    create_node(&pnew);
    pnew->addr = addr;
    node_t *ptemp = phead;
    while (ptemp->next != NULL) {
        ptemp = ptemp->next;
    }
    ptemp->next = pnew;
    return 0;
}

// 删除指定客户端节点
int delete_node(node_t *phead, struct sockaddr_in addr) {
    node_t *prev = phead;
    node_t *curr = phead->next;
    while (curr != NULL) {
        if (memcmp(&curr->addr, &addr, sizeof(addr)) == 0) {
            prev->next = curr->next;
            free(curr);
            return 0;
        }
        prev = curr;
        curr = curr->next;
    }
    return -1;
}

int main(int argc, const char *argv[]) {
    if (argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        return -1;
    }

    // 创建UDP套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        ERRLOG("socket创建失败");
    }

    // 绑定服务器地址
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    if (bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) {
        ERRLOG("bind失败");
    }
    printf("服务器启动,监听 %s:%d\n", argv[1], atoi(argv[2]));

    // 初始化客户端链表
    node_t *phead;
    create_node(&phead);

    pid_t pid = fork();
    if (pid < 0) {
        ERRLOG("fork失败");
    } else if (pid == 0) {  // 子进程:处理客户端消息接收与广播
        struct sockaddr_in clientaddr;
        socklen_t client_len = sizeof(clientaddr);
        msg_t msg;

        while (1) {
            memset(&msg, 0, sizeof(msg));
            if (recvfrom(sockfd, &msg, sizeof(msg), 0, 
                        (struct sockaddr *)&clientaddr, &client_len) == -1) {
                ERRLOG("recvfrom失败");
            }

            switch (msg.code) {
                case 'd':  // 登录处理
                    printf("[%s] 上线\n", msg.user);
                    insert_node(phead, clientaddr);  // 添加到在线列表
                    // 广播登录通知(排除自己)
                    node_t *p = phead->next;
                    while (p != NULL) {
                        if (memcmp(&p->addr, &clientaddr, sizeof(clientaddr)) != 0) {
                            sendto(sockfd, &msg, sizeof(msg), 0, 
                                  (struct sockaddr *)&p->addr, sizeof(p->addr));
                        }
                        p = p->next;
                    }
                    break;

                case 'q':  // 群聊处理
                    if (strcmp(msg.user, "管理员") != 0) {  // 非管理员消息打印
                        printf("[%s]: %s\n", msg.user, msg.text);
                    }
                    // 广播群聊消息(包括自己,UDP无连接需全播)
                    node_t *q = phead->next;
                    while (q != NULL) {
                        sendto(sockfd, &msg, sizeof(msg), 0, 
                              (struct sockaddr *)&q->addr, sizeof(q->addr));
                        q = q->next;
                    }
                    break;

                case 't':  // 退出处理
                    printf("[%s] 下线\n", msg.user);
                    delete_node(phead, clientaddr);  // 从列表删除
                    // 广播退出通知
                    node_t *r = phead->next;
                    while (r != NULL) {
                        sendto(sockfd, &msg, sizeof(msg), 0, 
                              (struct sockaddr *)&r->addr, sizeof(r->addr));
                        r = r->next;
                    }
                    break;
            }
        }
    } else {  // 父进程:发送系统消息(以"管理员"身份)
        msg_t sys_msg;
        while (1) {
            memset(&sys_msg, 0, sizeof(sys_msg));
            strcpy(sys_msg.user, "管理员");
            fgets(sys_msg.text, N, stdin);
            sys_msg.text[strlen(sys_msg.text) - 1] = '\0';  // 去除换行符
            sys_msg.code = 'q';  // 系统消息以群聊码发送

            // 广播系统消息给所有在线客户端
            node_t *s = phead->next;
            while (s != NULL) {
                sendto(sockfd, &sys_msg, sizeof(sys_msg), 0, 
                      (struct sockaddr *)&s->addr, sizeof(s->addr));
                s = s->next;
            }
        }
    }

    close(sockfd);
    return 0;
}

client.c

#include "udp.h"

int main(int argc, const char *argv[]) {
    if (argc != 3) {
        printf("Usage: %s <服务器IP> <端口>\n", argv[0]);
        return -1;
    }

    // 创建UDP套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        ERRLOG("socket创建失败");
    }

    // 服务器地址
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t server_len = sizeof(serveraddr);

    // 登录:发送用户名
    msg_t msg;
    char username[M] = {0};
    printf("请输入用户名(退出输入quit): ");
    fgets(username, M, stdin);
    username[strlen(username) - 1] = '\0';  // 去除换行符
    if (strcmp(username, "quit") == 0) {
        printf("未登录,退出程序\n");
        close(sockfd);
        return 0;
    }

    msg.code = 'd';
    strcpy(msg.user, username);
    if (sendto(sockfd, &msg, sizeof(msg), 0, 
              (struct sockaddr *)&serveraddr, server_len) == -1) {
        ERRLOG("登录消息发送失败");
    }

    pid_t pid = fork();
    if (pid < 0) {
        ERRLOG("fork失败");
    } else if (pid == 0) {  // 子进程:接收服务器广播消息
        msg_t recv_msg;
        while (1) {
            memset(&recv_msg, 0, sizeof(recv_msg));
            if (recvfrom(sockfd, &recv_msg, sizeof(recv_msg), 0, 
                        (struct sockaddr *)&serveraddr, &server_len) == -1) {
                ERRLOG("recvfrom失败");
            }
            // 过滤自己的登录/退出通知(UDP广播会收到自己的消息)
            if (strcmp(recv_msg.user, username) == 0 && 
                (recv_msg.code == 'd' || recv_msg.code == 't')) {
                continue;
            }
            switch (recv_msg.code) {
                case 'd': printf("[%s] 登录上线\n", recv_msg.user); break;
                case 'q': printf("[%s]: %s\n", recv_msg.user, recv_msg.text); break;
                case 't': printf("[%s] 退出聊天\n", recv_msg.user); break;
            }
        }
    } else {  // 父进程:发送群聊消息或退出
        while (1) {
            memset(msg.text, 0, N);
            fgets(msg.text, N, stdin);
            msg.text[strlen(msg.text) - 1] = '\0';  // 去除换行符

            if (strcmp(msg.text, "quit") == 0) {  // 退出操作
                msg.code = 't';
                sendto(sockfd, &msg, sizeof(msg), 0, 
                      (struct sockaddr *)&serveraddr, server_len);
                break;
            } else {  // 群聊消息
                msg.code = 'q';
                sendto(sockfd, &msg, sizeof(msg), 0, 
                      (struct sockaddr *)&serveraddr, server_len);
            }
        }
    }

    close(sockfd);
    return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到