C语言 基于UDP的局域网多人聊天室的简单代码

[C]基于UDP的局域网多人聊天室的简单代码 ..编程小白,代码可能比较冗长,请各位大佬指出不足 [*・ω・]

    • 运行现象
    • 思路
    • 程序源码(LINUX)
      • 1.head.h(头文件)
      • 2.datalink.c (存放着各种自定义函数接口)
      • 3.server.c(服务端)
      • 4.client.c(客户端)
      • 5.Makefile()
    • 代码中的不足之处
    • 源码文件链接
    • 废话

运行现象

1.当客户端(client)打开,输入昵称后服务器(server)端以及客户端的现象

C语言 基于UDP的局域网多人聊天室的简单代码_第1张图片
2.当客户端发送消息,以及服务端发送消息,以及客户端退出时现象
C语言 基于UDP的局域网多人聊天室的简单代码_第2张图片
C语言 基于UDP的局域网多人聊天室的简单代码_第3张图片
C语言 基于UDP的局域网多人聊天室的简单代码_第4张图片

思路

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协议的一些基础知识,代码写的很烂,非常渴望各位大佬批评以及指点。
最后,非常感谢各位大佬点开这篇帖子,你们的点击也一定是我学习的动力,感谢✧ʕ̢̣̣̣̣̩̩̩̩·͡˔·ོɁ̡̣̣̣̣̩̩̩̩✧

你可能感兴趣的:(C语言,c语言,udp,局域网)