手把手教你搭建 UDP 多人聊天室(附完整源码)

一、项目介绍

本文将分享一个基于 UDP 协议的简易多人聊天室项目,包含服务器端和客户端的完整实现。该聊天室支持多客户端同时连接,能实现消息群发、用户加入 / 退出通知等核心功能,适合作为网络编程入门实践案例。

项目采用 C 语言开发,利用 UDP 的无连接特性实现数据传输,通过链表管理在线客户端信息,核心功能包括:

  • 服务器端:客户端连接管理、消息转发、加入 / 退出通知
  • 客户端:消息发送、实时接收、退出控制
二、核心技术点
  1. UDP 协议应用

    采用SOCK_DGRAM类型的套接字实现无连接通信,通过sendtorecvfrom函数完成数据收发,无需建立持久连接,适合简单的即时通讯场景。

  2. 链表数据结构

    服务器端使用链表存储在线客户端的 IP 和端口信息,实现客户端的动态加入 / 删除管理,通过遍历链表完成消息群发(排除发送者自身)。

  3. 多线程编程

    客户端采用主线程发送消息、子线程接收消息的设计,实现消息收发的并行处理,避免阻塞。

  4. 网络字节序转换

    使用htons/ntohsinet_addr/inet_ntoa等函数处理 IP 地址和端口的字节序转换,保证跨平台通信兼容性。

三、代码解析
1. 服务器端(server.c)
  • 数据结构设计 :定义liu结构体存储客户端地址信息,通过链表头节点管理所有在线客户端

  • 核心功能函数

    • qunfa:遍历链表群发消息(排除发送者)
    • chuangjianlianbiao:新增客户端到链表并广播加入通知
    • panduan:判断客户端是否已在线
    • tuichu:处理客户端退出,从链表移除并广播退出通知
  • 主逻辑:循环接收客户端消息,根据消息类型(普通消息 / 退出指令)和客户端状态(新用户 / 老用户)执行对应操作。

cpp 复制代码
#include<myhead.h>
#define IP "192.168.175.46"
#define PORT 1111
typedef struct liu 
{
    struct sockaddr_in addr;
    struct liu *next;
} xue, *pxue;
pxue toujiedian() 
{
    pxue p = malloc(sizeof(xue));
    p->next = NULL;
    return p;
}
void qunfa(int oldfd,pxue L,char buff[],int n,struct sockaddr_in sender)//群发消息(不发给自己)
{
    pxue t = L->next;
    while(t) 
    {
        if(!(t->addr.sin_addr.s_addr==sender.sin_addr.s_addr&&t->addr.sin_port==sender.sin_port))//不发给自己
        {
            sendto(oldfd,buff,n,0,(struct sockaddr*)&(t->addr),sizeof(struct sockaddr_in));
        }
        t = t->next;
    }
}
void chuangjianlianbiao(int oldfd,pxue L,struct sockaddr_in client) // 添加客户端到链表并广播加入消息
{
    pxue p = malloc(sizeof(xue));
    p->addr = client;
    p->next = L->next;
    L->next = p;

    char str[1024];
    sprintf(str,"系统消息:%s:%d 加入聊天室......",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
    int n = strlen(str);
    qunfa(oldfd,L,str,n,client);//调用群发函数发送新用户加入聊天室的信息
}
int panduan(pxue L, struct sockaddr_in client)// 判断客户端是否已存在
{
    pxue t=L->next;
    while(t)//t只要不是NULL就运行
    {
        if(t->addr.sin_addr.s_addr==client.sin_addr.s_addr&&t->addr.sin_port==client.sin_port) 
        {
            return 0;// 已存在
        }
        t=t->next;
    }
    return 1;// 不存在
}
void tuichu(int oldfd,pxue L,struct sockaddr_in client)// 客户端退出处理 
{
    pxue t = L->next;
    pxue Q = L;
    while(t) 
    {
        if(t->addr.sin_addr.s_addr==client.sin_addr.s_addr&&t->addr.sin_port==client.sin_port) 
        {         
          
            char str[1024];
            sprintf(str,"系统消息:%s:%d 退出了聊天室......",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
            int n = strlen(str);
            qunfa(oldfd,L,str,n,client);//群发退出消息  
            Q->next = t->next;
            free(t);//从链表删除
            return;
        }
        Q = t;
        t = t->next;
    }
}
int main(int argc,const char*argv[]) 
{
    int oldfd=socket(AF_INET, SOCK_DGRAM, 0);
    if(oldfd==-1) 
    {
        perror("socket");
        return -1;
    }
    
    struct sockaddr_in server={
        .sin_family=AF_INET,
        .sin_port=htons(PORT),
        .sin_addr.s_addr=inet_addr(IP)
    };
    if(bind(oldfd,(struct sockaddr*)&server,sizeof(server))==-1) 
    {
        perror("bind");
        return -1;
    }

    pxue L=toujiedian();
    char buff[1024];
    struct sockaddr_in client;
    int len = sizeof(client);
    printf("UDP聊天室服务器已启动,等待客户端连接...\n");

    while(1) 
    {
        bzero(buff,sizeof(buff));
        int n=recvfrom(oldfd,buff,sizeof(buff),0,(struct sockaddr*)&client, &len);//有新客户端连接或者有旧客户端发消息会触发
        if(n<0) 
        {
            perror("recvfrom");
            continue;
        }
        buff[n] = '\0';
        if(strcmp(buff,"quit")==0)//先判断消息是否为退出指令,不进入新老客户判断
        {
            tuichu(oldfd,L,client);//如果是退出指令,直接进入退出处理函数
            continue;
        }
        if(panduan(L,client))//确定不是退出指令后,判断发信息的客户端是否存储在链表里
        {
            chuangjianlianbiao(oldfd, L, client);//不存在就创建并存储客户端信息,同时广播加入信息
        }
        char str[1024];
        bzero(str,sizeof(str));
        sprintf(str,"%s:%d发来:",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
        strcat(str,buff);
        qunfa(oldfd,L,str,n,client);//存在就群发客户端发来的消息
    }
    close(oldfd);
    return 0;
}
2. 客户端(客户端 1.c)
  • 多线程设计 :子线程jieshou循环接收服务器转发的消息并打印,主线程负责读取用户输入并发送。
  • 通信流程:绑定本地地址后连接服务器,输入消息自动发送,输入 "quit" 时退出聊天室并通知服务器。
cpp 复制代码
#include<myhead.h>
#define IP "192.168.175.46"// 服务端IP
#define PORT 1111// 服务端端口
int oldfd;
void *jieshou()// 接收线程
{
    char buff[1024];
    struct sockaddr_in from;
    int len = sizeof(from);
    while(1) 
    {
        bzero(buff, sizeof(buff));
        int n =recvfrom(oldfd,buff,sizeof(buff),0,(struct sockaddr*)&from,&len);
        if(n>0) 
        {
            buff[n]='\0';
            printf("%s\n",buff);
        }
    }
}
int main(int argc,const char*argv[]) 
{
    oldfd= socket(AF_INET,SOCK_DGRAM, 0);
    if(oldfd == -1) 
    {
        perror("socket");
        return -1;
    }
    //绑定客户端自己的地址
    struct sockaddr_in client={
    .sin_family=AF_INET,
    .sin_port=htons(2222),
    .sin_addr.s_addr=inet_addr("192.168.175.46")
    };
    if(bind(oldfd,(struct sockaddr*)&client,sizeof(client))==-1) 
    {
        perror("bind");
        return -1;
    }
    //要连接的服务器地址
    struct sockaddr_in server={
    .sin_family = AF_INET,
    .sin_port = htons(PORT),
    .sin_addr.s_addr = inet_addr(IP)
    };
    printf("\t\t连接到聊天室服务器 %s:%d\n",IP,PORT);
    printf("\t\t输入消息并回车即可发送,输入 quit 退出聊天室\n");
    // 创建接收线程
    pthread_t tid;
    pthread_create(&tid,NULL,jieshou,NULL);
    pthread_detach(tid);//告诉系统:这个线程结束后,帮我自动回收
    // 主线程负责发送
    char buff[1024];
    while(1) 
    {
        bzero(buff, sizeof(buff));
        fgets(buff, sizeof(buff),stdin);
        buff[strlen(buff)-1] = '\0';
        sendto(oldfd,buff,sizeof(buff),0,(struct sockaddr*)&server,sizeof(server));
        if(strcmp(buff, "quit")==0) 
        {
            printf("您已退出聊天室\n");
            break;
        }
    }
    close(oldfd);
    return 0;
}
四、运行效果
  1. 启动服务器后,显示 "UDP 聊天室服务器已启动,等待客户端连接..."
  2. 客户端启动后,自动连接服务器并加入聊天室,其他在线用户会收到 "系统消息:IP: 端口 加入聊天室......"
  3. 任一客户端发送消息,其他客户端会收到 "IP: 端口发来:消息内容"
  4. 客户端输入 "quit",其他用户会收到退出通知,该客户端从在线列表中移除
五、总结与扩展方向

本项目实现了 UDP 聊天室的基础功能,可进一步扩展:

  • 增加用户昵称功能(替代 IP: 端口显示)
  • 实现私聊功能(指定接收者 IP 和端口)
  • 加入消息加密机制,提升安全性
  • 增加客户端心跳检测,处理异常断开情况

完整代码已附在文中,适合网络编程初学者参考学习,如有问题欢迎留言交流。

相关推荐
静谧之心8 小时前
从“叠加”到“重叠”:Overlay 与 Overlap 双引擎驱动技术性能优化
linux·网络·设计模式·性能优化·golang·overlay·overlap
三十_A8 小时前
【NestJS】HTTP 接口传参的 5 种方式(含前端调用与后端接收)
前端·网络协议·http
源代码•宸8 小时前
网络流量分析——使用Wireshark进行分析
网络·经验分享·测试工具·wireshark
mit6.8248 小时前
[re_2] rpc|http|nginx|protobuf|
网络·c++
程序员老舅9 小时前
‌NAT穿透技术原理:P2P通信中的打洞机制解析‌
服务器·c++·网络协议·网络编程·p2p·nat·网络穿透
now_cn10 小时前
构建线上门户的核心三要素:域名、DNS与IP 全面解析
网络·网络协议·tcp/ip
无敌的牛10 小时前
网络通信IP细节
服务器·网络·tcp/ip
徐子元竟然被占了!!11 小时前
针对 “TCP 会话维持与身份验证” 的攻击
网络·网络协议·tcp/ip
007tg11 小时前
跨境电商账号风控核心:IP纯净度与浏览器指纹的防护策略
网络·网络协议·tcp/ip