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

一、项目介绍

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

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

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

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

  2. 链表数据结构

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

  3. 多线程编程

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

  4. 网络字节序转换

    使用htons/ntohs、inet_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 和端口)
  • 加入消息加密机制,提升安全性
  • 增加客户端心跳检测,处理异常断开情况

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

相关推荐
贞明电子2 小时前
工业三色报警灯怎么接入项目系统?开关量与RS485控制应用指南
运维·网络·微信公众平台·贞明电子
明天也要努力学习2 小时前
HTTP 协议的基本格式和 fiddler 的用法
网络协议·http·fiddler
HideF3 小时前
Linux
linux·服务器·网络
万联WANFLOW3 小时前
海外网络故障怎么排查?Cloudflare 三季度断网报告解读与 mtr 实战
运维·网络·故障分析
听风说ml3 小时前
socket编程 【C语言】
c语言·udp·tcp·socket编程
sbjdhjd3 小时前
网工进阶笔记:H3CSE 园区网全栈技术与协议汇总
网络·笔记·云计算·ssh·网络工程·vlan·aaa
taktok_ai5 小时前
用 cc-switch 连接 Taktok
网络
小嘉丶学长7 小时前
Day14 · 字典与集合
java·开发语言·网络·python·学习·1024程序员节
pt10439 小时前
Cisco设备网络安全系列-18:SIEM与SOAR安全解决方案
网络·安全
Tel199253080049 小时前
温度湿度信号转WiFi/MQTT/Modbus TCP采集模块 无线传输
网络·网络协议·tcp/ip·工业自动化·工控设备