一、項目介紹???????
????????本文將分享一個基于 UDP 協議的簡易多人聊天室項目,包含服務器端和客戶端的完整實現。該聊天室支持多客戶端同時連接,能實現消息群發、用戶加入 / 退出通知等核心功能,適合作為網絡編程入門實踐案例。
????????項目采用 C 語言開發,利用 UDP 的無連接特性實現數據傳輸,通過鏈表管理在線客戶端信息,核心功能包括:
- 服務器端:客戶端連接管理、消息轉發、加入 / 退出通知
- 客戶端:消息發送、實時接收、退出控制
二、核心技術點
UDP 協議應用
采用SOCK_DGRAM
類型的套接字實現無連接通信,通過sendto
和recvfrom
函數完成數據收發,無需建立持久連接,適合簡單的即時通訊場景。鏈表數據結構
服務器端使用鏈表存儲在線客戶端的 IP 和端口信息,實現客戶端的動態加入 / 刪除管理,通過遍歷鏈表完成消息群發(排除發送者自身)。多線程編程
客戶端采用主線程發送消息、子線程接收消息的設計,實現消息收發的并行處理,避免阻塞。網絡字節序轉換
使用htons
/ntohs
、inet_addr
/inet_ntoa
等函數處理 IP 地址和端口的字節序轉換,保證跨平臺通信兼容性。
三、代碼解析
1. 服務器端(server.c)
數據結構設計:定義
liu
結構體存儲客戶端地址信息,通過鏈表頭節點管理所有在線客戶端核心功能函數:
qunfa
:遍歷鏈表群發消息(排除發送者)chuangjianlianbiao
:新增客戶端到鏈表并廣播加入通知panduan
:判斷客戶端是否已在線tuichu
:處理客戶端退出,從鏈表移除并廣播退出通知
主邏輯:循環接收客戶端消息,根據消息類型(普通消息 / 退出指令)和客戶端狀態(新用戶 / 老用戶)執行對應操作。
#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" 時退出聊天室并通知服務器。
#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;
}
四、運行效果
- 啟動服務器后,顯示 "UDP 聊天室服務器已啟動,等待客戶端連接..."
- 客戶端啟動后,自動連接服務器并加入聊天室,其他在線用戶會收到 "系統消息:IP: 端口 加入聊天室......"
- 任一客戶端發送消息,其他客戶端會收到 "IP: 端口發來:消息內容"
- 客戶端輸入 "quit",其他用戶會收到退出通知,該客戶端從在線列表中移除
五、總結與擴展方向
本項目實現了 UDP 聊天室的基礎功能,可進一步擴展:
- 增加用戶昵稱功能(替代 IP: 端口顯示)
- 實現私聊功能(指定接收者 IP 和端口)
- 加入消息加密機制,提升安全性
- 增加客戶端心跳檢測,處理異常斷開情況
完整代碼已附在文中,適合網絡編程初學者參考學習,如有問題歡迎留言交流。