[C]基于UDP的局域網多人聊天室的簡單代碼 ..編程小白,代碼可能比較冗長,請各位大佬指出不足 [*・ω・]
- 運行現象
- 思路
- 程式原始碼(LINUX)
- 1.head.h(頭檔案)
- 2.datalink.c (存放著各種自定義函式介面)
- 3.server.c(服務端)
- 4.client.c(客戶端)
- 5.Makefile()
- 代碼中的不足之處
- 原始碼檔案鏈接
- 廢話
運行現象
1.當客戶端(client)打開,輸入昵稱后服務器(server)端以及客戶端的現象

2.當客戶端發送訊息,以及服務端發送訊息,以及客戶端退出時現象



思路
1.因為服務端和客戶端都需要具備接收資料和發送資料的能力,因此需要使用多個執行緒分別完成接收和發送的操作(相關函式pthread_create())
2.因為需要通過服務端給每個用戶群發訊息,所以用戶的資訊需要儲存,用陣列和鏈表都可以,這里本人用的是鏈表
3.因為UDP協議發送資訊時用的是sedto()函式,每次發送資料都需要提供發送目標的ip和埠號,因此我們需要定義一個結構體來存放每個登錄用戶的埠號和ip號等資訊.
程式原始碼(LINUX)
1.head.h(頭檔案)
#ifndef _HEAD_H_
#define _HEAD_H_
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <linux/in.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
//發送資訊的標志變數
#define LOGIN 0
#define QUIT 1
#define SESSION 2
#define SERVER 3
#define RENAME 4
//存放客戶端發送資訊
typedef struct msg{
int flag;
char name[16];
char info[64];
}msg;
//用來保存單個用戶的ip,埠號等資訊
typedef struct usermsg{
char user_ip[32];
unsigned short user_port;
char user_name[16];
}usermsg;
//存放所有用戶資訊的鏈
typedef struct userlink{
usermsg user;
struct userlink *next;
}userlink;
int link_creat(userlink **); //創建鏈表
int link_add(userlink *,usermsg *,int); //添加用戶
int myperror(int,char*); //報錯資訊(寫到后面忘了這個函式了,后面寫的函式幾乎都沒有做判斷...)
int link_delete(userlink*,usermsg*,int);//用戶退出,洗掉退出用戶的資訊
int mass(userlink*,usermsg*,msg*,int); //服務器給各個客戶端群發資訊的函式
void* client_thread(void*); //客戶端thread_create()創建執行緒函式的最后一個引數,該執行緒用于接收來自服務器的訊息
userlink* link_select(userlink*,usermsg*);//查找指定用戶的函式
void* clientmsg_thread(void *); //服務端thread_create()創建執行緒函式的最后一個引數,該執行緒用于給客戶端發送服務端的訊息
void* servermsg_thread(void *); //服務端thread_create()創建執行緒函式的最后一個引數,該執行緒用于給客戶端發送其他客戶端的訊息
#endif
2.datalink.c (存放著各種自定義函式介面)
#include"head.h"
//自定義的報錯函式
int myperror(int flag,char *name){
if(flag < 0){
printf(">>server : %s is error",name);
perror(":");
exit(-1);
return 0;
}else{
printf(">>server : %s is succeed\n",name);
return 0;
}
}
//創建鏈表
int link_creat(userlink **p){
if((*p) == NULL){
(*p) = (userlink*)malloc(sizeof(userlink));
(*p) -> next = NULL;
(*p) -> user.user_port = 0;
return 0;
}
puts("==SERVER== :link is exit");
return 0;
}
//添加用戶,并給所有人發送登錄資訊
int link_add(userlink *p,usermsg *umsg,int sockfd){
if(p){
userlink *new = (userlink*)malloc(sizeof(userlink));
new -> next = p -> next;
p -> next = new;
p -> user.user_port ++;
strcpy(new -> user.user_ip,umsg -> user_ip);
new -> user.user_port = umsg -> user_port;
strcpy(new -> user.user_name,umsg->user_name);
//在服務端列印登入用戶的名字,ip,以及埠號
printf(">>server : %s is login,--[ip : %s][port : %u]--\n",umsg -> user_name,
umsg -> user_ip,umsg -> user_port);
new = NULL;
//定義一個新的資料包
msg smsg;
//給資料包定義一個flag標志位,然后往其中填充一條"xxx"用戶登錄的訊息
smsg.flag = SERVER;
memset(smsg.info,'\0',sizeof(smsg.info));
sprintf(smsg.info,">> %s << is login",umsg -> user_name);
//使用mass()群發函式,發給所有用戶(該函式在下方定義)
mass(p,NULL,&smsg,sockfd);
return 0;
}else{
puts("==SERVER== : link not exist");
return 0;
}
}
//尋找用戶的前一個用戶
userlink* link_select(userlink *p,usermsg *umsg){
while(p -> next){
if(!strcmp(p->next->user.user_ip,umsg->user_ip)&&p->next->user.user_port == umsg->user_port ){
return p;
break;
}
p = p -> next;
}
return NULL;
}
//用戶退出
int link_delete(userlink *head,usermsg *umsg,int sockfd){
userlink *temp,*p = NULL;
if(head){
if((p = link_select(head,umsg))){
temp = p -> next;
p -> next = temp -> next;
//在服務端列印一下退出用戶的名字
printf(">>>server : user[ %s ] will be quit\n",temp ->user.user_name);
//定義一個新的資料包,操作和上文中的登錄操作類似,將資料包填充后呼叫mass函式群發
msg bmsg;
bmsg.flag = QUIT;
strcpy(bmsg.name,temp -> user.user_name);
mass(head,umsg,&bmsg,sockfd);
free(temp);
temp = NULL;
head -> user.user_port--;
}else{
puts("==SERVER== : user not exist");
return 0;
}
}else{
puts("==SERVER== : link not exist");
return 0;
}
}
//給所有用戶轉發訊息
int mass(userlink *p,usermsg *umsg,msg *bmsg,int sockfd){
struct sockaddr_in clientaddr;
socklen_t clientlen = sizeof(clientaddr);
//當該資訊來自客戶端時
if(bmsg->flag == SESSION){
userlink *namep = NULL;
if((namep = link_select(p,umsg))){
strcpy(bmsg->name,namep->next->user.user_name);
}else{
puts("==SERVER== : error,the user is exit???");
}
//當該資訊來自服務端時
}else if(bmsg -> flag == SERVER){
strcpy(bmsg->name,"server");
//當該資訊是客戶端的退出指令時
}else if(bmsg -> flag == QUIT){
memset(bmsg->info,'\0',strlen(bmsg->info));
sprintf(bmsg->info,"[%s is quit]",bmsg->name);
strcpy(bmsg->name,"server");
}
//在服務端列印一下這條資訊的內容
printf(">>> [%s] : %s\n",bmsg->name,bmsg->info);
p = p->next;
//遍歷整個鏈表,將打包好的資訊發送給各個客戶端
while(p){
clientaddr.sin_family = AF_INET;
clientaddr.sin_port = htons(p->user.user_port);
clientaddr.sin_addr.s_addr = inet_addr(p->user.user_ip);
sendto(sockfd,bmsg,sizeof(msg),0,(struct sockaddr*)&clientaddr,clientlen);
p = p-> next;
}
}
3.server.c(服務端)
#include"head.h"
//執行緒信號量
sem_t sem_server;
//執行緒從該結構體中拿資料
struct thread_pass{
int sockfd;
userlink *head;
msg *bmsg;
usermsg *umsg;
}pass;
//同上,也是用來給執行緒拿資料的
struct buf{
int sockfd;
userlink *head;
}bufpack;
int main(int argc, const char *argv[])
{
//創建鏈表
userlink *head = NULL;
link_creat(&head);
//生成套接字檔案描述符
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
myperror(sockfd,"socket");
struct sockaddr_in serveraddr;
//服務端的ip,協議族,埠號等資訊
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
myperror(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)),"bind");
//用來接收客戶端的ip,埠號資訊的結構體
struct sockaddr_in clientaddr;
socklen_t clientlen = sizeof(clientaddr);
//自定義的存放客戶端資訊的結構體,便于往鏈表中填充
usermsg umsg;
//存放正文資訊以及資訊標志符的結構體資料包
msg bmsg,smsg;
pthread_t tid_send_clientmsg,tid_send_servermsg;
bufpack.head = head;
bufpack.sockfd = sockfd;
//創建兩個執行緒
pthread_create(&tid_send_servermsg,NULL,servermsg_thread,NULL);
pthread_create(&tid_send_clientmsg,NULL,clientmsg_thread,NULL);
//執行緒分離
pthread_detach(tid_send_clientmsg);
pthread_detach(tid_send_clientmsg);
//初始化執行緒信號量
sem_init(&sem_server,0,0);
//回圈接收資料
while(1){
recvfrom(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&clientaddr,&clientlen);
//將存放在clientaddr中的客戶端ip和埠號存放在我們自定義的umsg結構體里
strcpy(umsg.user_ip,(char*)inet_ntoa(clientaddr.sin_addr.s_addr));
umsg.user_port = ntohs(clientaddr.sin_port);
switch(bmsg.flag){
//當信號量為LOGIN時,為用戶登錄,添加鏈表
case LOGIN:
strcpy(umsg.user_name,bmsg.info);
link_add(head,&umsg,sockfd);
break;
//為QUIT為用戶退出,洗掉鏈表中該用戶的資訊
case QUIT:
link_delete(head,&umsg,sockfd);
break;
//SESSION為用戶發送過來的正文訊息,釋放一個信號量,交給執行緒處理
case SESSION:
pass.head = head;
pass.bmsg = &bmsg;
pass.umsg = &umsg;
pass.sockfd = sockfd;
sem_post(&sem_server);
}
}
return 0;
}
//用來群發服務端訊息的執行緒
void* servermsg_thread(void *p){
msg smsg;
smsg.flag = SERVER;
while(1){
//fgets阻塞,從終端的輸入快取區獲取資訊,然后發送,沒有資訊則阻塞
fgets(smsg.info,sizeof(smsg.info),stdin);
(smsg.info)[strlen(smsg.info) - 1] = '\0';
mass(bufpack.head,NULL,&smsg,bufpack.sockfd);
}
return 0;
}
//用啦群發客戶端訊息的執行緒
void* clientmsg_thread(void *p){
while(1){
//當只有主執行緒中信號量釋放時才會向下運行,否則阻塞
sem_wait(&sem_server);
mass(pass.head,pass.umsg,pass.bmsg,pass.sockfd);
}
}
4.client.c(客戶端)
#include"head.h"
int main(int argc, const char *argv[])
{
//創建套接字檔案描述符
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
myperror(sockfd,"socket");
struct sockaddr_in serveraddr;
pthread_t tid;
msg bmsg;
//服務端的各種資訊,用于后面sendto()函式
serveraddr.sin_port = htons(atoi(argv[2]));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
//輸入名字的提示陳述句
printf(">>>name (Less than 15 character) : ");
fgets(bmsg.info,16,stdin);
(bmsg.info)[strlen(bmsg.info) - 1] = '\0';
bmsg.flag = LOGIN;
sendto(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
//創建執行緒
pthread_create(&tid,NULL,client_thread,&sockfd);
pthread_detach(tid);
while(1){
//從終端輸入快取區獲取資訊,沒有則阻塞
fgets(bmsg.info,sizeof(bmsg.info),stdin);
(bmsg.info)[strlen(bmsg.info) - 1] = '\0';
//當輸入#quit ---這一退出指令時執行的陳述句,將標志位賦值為QUIT
if(!strcmp(bmsg.info,"#quit")){
bmsg.flag = QUIT;
sendto(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
sleep(1);
pthread_cancel(tid);
break;
}else{
bmsg.flag = SESSION;
}
sendto(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
}
return 0;
}
//用于接收服務端發來的訊息,并列印在終端上
void* client_thread(void *p){
msg recvmsg;
while(1){
recvfrom(*(int *)p,&recvmsg,sizeof(recvmsg),0,NULL,NULL);
printf(">>> [%s] : %s\n",recvmsg.name,recvmsg.info);
}
}
5.Makefile()
.PHONY:all
all:client server
OBJS1=client.o datalink.o
OBJS2=server.o datalink.o
CC=gcc
CFLAGS=-g
client:$(OBJS1)
$(CC) $(CFLAGS) $^ -o $@ -lpthread
server:$(OBJS2)
$(CC) $(CFLAGS) $^ -o $@ -lpthread
.PHONY:clean
clean:
rm $(OBJS)
代碼中的不足之處
1.沒有輸入字符溢位時的優化陳述句
2.用戶重名無法判斷
3.服務端沒有正常退出的陳述句
4.代碼冗長,定義的變數以及結構體太多了,不方便閱讀
原始碼檔案鏈接
源檔案下載地址
廢話
本人為剛學嵌入式不久的編程小白,目前只學習了一些C語言,檔案IO和TCP、UDP協議的一些基礎知識,代碼寫的很爛,非常渴望各位大佬批評以及指點,
最后,非常感謝各位大佬點開這篇帖子,你們的點擊也一定是我學習的動力,感謝???????????·??·????????????
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/251819.html
標籤:其他
上一篇:Python爬蟲自學系列(四)
