C語言 基於UDP的局域網多人聊天室的簡單代碼

[C]基於UDP的局域網多人聊天室的簡單代碼 ..編程小白,代碼可能比較冗長,請各位大佬指出不足 [*・ω・]

運行現象

1.當客戶端(client)打開,輸入暱稱後服務器(server)端以及客戶端的現象

服務器端會打印所登錄客戶端使用的ip信息和端口號,並給其他用戶發送該用戶的登錄信息
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協議的一些基礎知識,代碼寫的很爛,非常渴望各位大佬批評以及指點。
最後,非常感謝各位大佬點開這篇帖子,你們的點擊也一定是我學習的動力,感謝✧ʕ̢̣̣̣̣̩̩̩̩·͡˔·ོɁ̡̣̣̣̣̩̩̩̩✧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章