基于UDP的网络聊天室

项目需求:

  1. 如果有用户登录,其他用户可以收到这个人的登录信息
  2. 如果有人发送信息,其他用户可以收到这个人的群聊信息
  3. 如果有人下线,其他用户可以收到这个人的下线信息
  4. 服务器可以发送系统信息

服务器:

复制代码
#include <myhead.h>

//定义存储信息结构体
typedef struct _MSG
{
	char code;  //操作码:'L'表示登录   'C表示群聊   'Q'表示退出
	char name[20];  
	char txt[256];
 
}msg_t;
 
//定义保存客户端网络信息的链表
typedef struct _ADDR
{
	struct sockaddr_in cin;
	struct _ADDR* next;
}addrlist_t;
 
//登录操作的函数
void do_login(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
	//先遍历链表 将新用户加入群聊的消息发送给所有客户端
	addrlist_t* tmp = addr;  //记录链表头结点
	while(tmp->next != NULL)
	{
		tmp = tmp->next;
		if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(tmp->cin),sizeof(tmp->cin)) == -1)
		{
			perror("sendto error");
			return;
		}
	}
	//将新用户的网络信息结构体头插入链表
	addrlist_t* pnew = NULL;
	if((pnew = (addrlist_t*)malloc(sizeof(addrlist_t)))==NULL){
		printf("malloc error\n");
		return;
	}
	pnew->cin = cin;
	pnew->next = addr->next;
	addr->next = pnew;
	printf("--%s已上线--\n",msg.name);
	return;
}
 
//群聊操作函数
void do_chat(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
	//遍历链表,将群聊消息发给除自己以外的其他客户端
	addrlist_t* ptmp = addr;
	while(ptmp->next != NULL)
	{
		ptmp = ptmp->next;
		if(memcmp(&cin, &(ptmp->cin), sizeof(cin))){
			//判断哪个客户发的信息
			if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(ptmp->cin),sizeof(ptmp->cin)) == -1)
			{
				perror("sendto error");
				return;
			}
		}
	}
	return;
}
 
//退出操作的函数
void do_quit(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{

	addrlist_t* ptmp = addr;
	addrlist_t* del = NULL;
	while(ptmp->next != NULL)
	{
		if(memcmp(&(ptmp->next->cin), &cin, sizeof(cin)))
		{
			//判断是否是其他客户发送的消息
			ptmp = ptmp->next;
			if((sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(ptmp->cin),sizeof(ptmp->cin))) == -1)
			{
				perror("sendto error");
				return;
			}
		}else
		{
			del = ptmp->next;
			ptmp->next = del->next;
			free(del);
			del=NULL;
		}
	}
	printf("--%s已下线--\n",msg.name);
	return;
}
int main(int argc, const char *argv[])
{
	if(argc != 3){   //输入ip地址及端口号,进行判断
		printf("input error\n");
		printf("usage: %s <IP> <PORT>\n",argv[0]);
		return -1;
	}
 
	//定义用于接收等待套接字
	int sfd;
	if((sfd = socket(AF_INET,SOCK_DGRAM,0)) == -1)
	{
		perror("socket error");
		return -1;
	}
	printf("socket sfd success\n");
 
	//设置端口号快速重用
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1){
		perror("setsockopt error");
		return -1;
	}
	//绑定
	//填充服务器信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port   = htons(atoi(argv[2]));
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	socklen_t seraddr_len = sizeof(sin);
 
	if((bind(sfd, (struct sockaddr*)&sin, seraddr_len)) == -1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");
	//定义客户端网络信息结构体
	struct sockaddr_in cin;
	socklen_t cliaddr_len = sizeof(cin);

	msg_t msg;  //定义接收信息的变量msg
	
	pid_t pid;  //进程号
	pid = fork();  //创建多进程
	if(pid < 0){
		perror("fork error");
		return -1;
	}else if(pid == 0)
	{   
		//子进程,用来收发数据
		//创建保存客户端信息的链表头结点
		addrlist_t* addr;
		if((addr = (addrlist_t*)malloc(sizeof(addrlist_t)))==NULL)
		{
			printf("malloc error\n");
			return -1;
		}
		bzero(addr,  sizeof(addr));
		addr->next = NULL;
 
		while(1)
		{  //循环收发数据
			bzero(&msg,sizeof(msg));  //每次接收新用户数据清空
			bzero(&cin,sizeof(cin)); 
			//接收客户端发送的消息,存放在msg中
			if((recvfrom(sfd, &msg,sizeof(msg), 0,(struct sockaddr*)&cin, &cliaddr_len)) == -1)
			{
				perror("recvfrom error");
				return -1;
			}
			switch(msg.code){  //判断消息中的操作码,根据操作码执行对应操作
				case 'L':   //登录操作
					do_login(sfd,msg,addr,cin);
					break;
				case 'C':   //群聊操作
					do_chat(sfd,msg,addr,cin);
					break;
				case 'Q':   //退出操作
					do_quit(sfd,msg,addr,cin);
					break;
			}
		}
		exit(0);
	}else{
		//父进程,用来发送系统消息
		//向子进程发送群聊消息
		strcpy(msg.name, "系统消息");
		msg.code = 'C';
		while(1)
		{
			bzero(msg.txt,sizeof(msg.txt));
			fgets(msg.txt, 256,stdin);  //终端获取接收消息
			msg.txt[strlen(msg.txt)-1] = '\0';
			if((sendto(sfd,&msg,sizeof(msg),0 ,(struct sockaddr*)&sin,seraddr_len)) == -1)
			{
				perror("sendto error");
				return -1;
			}
		}
	}
	close(sfd);
	return 0;
}

客户端:

复制代码
#include <myhead.h>

//定义存储信息结构体
typedef struct _MSG
{
	char code;  //操作码:'L'表示登录   'C表示群聊   'Q'表示退出
	char name[20];  
	char txt[256];
 
}msg_t;
 
//定义保存客户端网络信息的链表
typedef struct _ADDR
{
	struct sockaddr_in cin;
	struct _ADDR* next;
}addrlist_t;
 
//登录操作的函数
void do_login(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
	//先遍历链表 将新用户加入群聊的消息发送给所有客户端
	addrlist_t* tmp = addr;  //记录链表头结点
	while(tmp->next != NULL)
	{
		tmp = tmp->next;
		if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(tmp->cin),sizeof(tmp->cin)) == -1)
		{
			perror("sendto error");
			return;
		}
	}
	//将新用户的网络信息结构体头插入链表
	addrlist_t* pnew = NULL;
	if((pnew = (addrlist_t*)malloc(sizeof(addrlist_t)))==NULL){
		printf("malloc error\n");
		return;
	}
	pnew->cin = cin;
	pnew->next = addr->next;
	addr->next = pnew;
	printf("--%s已上线--\n",msg.name);
	return;
}
 
//群聊操作函数
void do_chat(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
	//遍历链表,将群聊消息发给除自己以外的其他客户端
	addrlist_t* ptmp = addr;
	while(ptmp->next != NULL)
	{
		ptmp = ptmp->next;
		if(memcmp(&cin, &(ptmp->cin), sizeof(cin))){
			//判断哪个客户发的信息
			if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(ptmp->cin),sizeof(ptmp->cin)) == -1)
			{
				perror("sendto error");
				return;
			}
		}
	}
	return;
}
 
//退出操作的函数
void do_quit(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{

	addrlist_t* ptmp = addr;
	addrlist_t* del = NULL;
	while(ptmp->next != NULL)
	{
		if(memcmp(&(ptmp->next->cin), &cin, sizeof(cin)))
		{
			//判断是否是其他客户发送的消息
			ptmp = ptmp->next;
			if((sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(ptmp->cin),sizeof(ptmp->cin))) == -1)
			{
				perror("sendto error");
				return;
			}
		}else
		{
			del = ptmp->next;
			ptmp->next = del->next;
			free(del);
			del=NULL;
		}
	}
	printf("--%s已下线--\n",msg.name);
	return;
}
int main(int argc, const char *argv[])
{
	if(argc != 3){   //输入ip地址及端口号,进行判断
		printf("input error\n");
		printf("usage: %s <IP> <PORT>\n",argv[0]);
		return -1;
	}
 
	//定义用于接收等待套接字
	int sfd;
	if((sfd = socket(AF_INET,SOCK_DGRAM,0)) == -1)
	{
		perror("socket error");
		return -1;
	}
	printf("socket sfd success\n");
 
	//设置端口号快速重用
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1){
		perror("setsockopt error");
		return -1;
	}
	//绑定
	//填充服务器信息结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port   = htons(atoi(argv[2]));
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	socklen_t seraddr_len = sizeof(sin);
 
	if((bind(sfd, (struct sockaddr*)&sin, seraddr_len)) == -1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");
	//定义客户端网络信息结构体
	struct sockaddr_in cin;
	socklen_t cliaddr_len = sizeof(cin);

	msg_t msg;  //定义接收信息的变量msg
	
	pid_t pid;  //进程号
	pid = fork();  //创建多进程
	if(pid < 0){
		perror("fork error");
		return -1;
	}else if(pid == 0)
	{   
		//子进程,用来收发数据
		//创建保存客户端信息的链表头结点
		addrlist_t* addr;
		if((addr = (addrlist_t*)malloc(sizeof(addrlist_t)))==NULL)
		{
			printf("malloc error\n");
			return -1;
		}
		bzero(addr,  sizeof(addr));
		addr->next = NULL;
 
		while(1)
		{  //循环收发数据
			bzero(&msg,sizeof(msg));  //每次接收新用户数据清空
			bzero(&cin,sizeof(cin)); 
			//接收客户端发送的消息,存放在msg中
			if((recvfrom(sfd, &msg,sizeof(msg), 0,(struct sockaddr*)&cin, &cliaddr_len)) == -1)
			{
				perror("recvfrom error");
				return -1;
			}
			switch(msg.code){  //判断消息中的操作码,根据操作码执行对应操作
				case 'L':   //登录操作
					do_login(sfd,msg,addr,cin);
					break;
				case 'C':   //群聊操作
					do_chat(sfd,msg,addr,cin);
					break;
				case 'Q':   //退出操作
					do_quit(sfd,msg,addr,cin);
					break;
			}
		}
		exit(0);
	}else{
		//父进程,用来发送系统消息
		//向子进程发送群聊消息
		strcpy(msg.name, "系统消息");
		msg.code = 'C';
		while(1)
		{
			bzero(msg.txt,sizeof(msg.txt));
			fgets(msg.txt, 256,stdin);  //终端获取接收消息
			msg.txt[strlen(msg.txt)-1] = '\0';
			if((sendto(sfd,&msg,sizeof(msg),0 ,(struct sockaddr*)&sin,seraddr_len)) == -1)
			{
				perror("sendto error");
				return -1;
			}
		}
	}
	close(sfd);
	return 0;
}
ubuntu@ubuntu:test$ ^C
ubuntu@ubuntu:test$ cat cli.c
#include <myhead.h>

typedef struct _MSG
{
	char code;      //'L'表示登录  'C'表示群聊 'Q'表示退出群聊
	char name[20];
	char txt[256];
}msg_t;   //定义消息结构体类型
 
int main(int argc, const char *argv[])
{
	if(3 != argc)
	{  
		printf("input error!\n");
		printf("usage:%s <IP> <PORT>\n", argv[0]);
		return -1;
	}
 
	//定义通信的套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(-1 == sfd)
	{
		perror("sockfd error");
		return -1;
	}
	
	//定义服务器地址信息结构体
	struct sockaddr_in sin;
	memset(&sin, 0,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_port   = htons(atoi(argv[2]));
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	socklen_t seraddr_len = sizeof(sin);
 
	msg_t msg;
	memset(&msg,0,sizeof(msg));

	//输入用户名
	printf("请输入用户名:");
	fgets(msg.name,45,stdin);
	msg.name[strlen(msg.name)-1] = '\0';
	msg.code = 'L';
	strcpy(msg.txt,"加入群聊");
	//给服务器发送登录信息
	if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,seraddr_len) == -1)
	{
		perror("sendto error");
		return -1;
	}
 
	//定义父子进程并创建
	pid_t pid = 0;
	pid = fork();
	if(pid < 0)
	{
		printf("fork error\n");
		return -1;
	}else if(pid == 0)
	{
		//子进程,循环接收并打印接收的数据
		while(1){
			if(recvfrom(sfd,&msg,sizeof(msg),0,NULL,NULL) == -1)
			{
				perror("sendto error");
				return -1;
			}
			//打印收到的数据
			printf("[%s]:%s\n",msg.name, msg.txt);
		}
	}else{   //父进程循环接收终端数据并发送给客户端
		while(1){
			bzero(msg.txt,sizeof(msg.txt));
			fgets(msg.txt,128,stdin);  //终端获取聊天消息
			msg.txt[strlen(msg.txt)-1] = '\0';
			if(strcmp(msg.txt, "quit") == 0)
			{
				msg.code = 'Q';
				strcpy(msg.txt, "退出群聊");
			}else{
				msg.code = 'C';
			}
			if(sendto(sfd,&msg,sizeof(msg), 0,(struct sockaddr*)&sin,seraddr_len) == -1)
			{
				perror("sendto error");
				return -1;
			}
			if(strcmp(msg.txt, "退出群聊") == 0){
				break;
			}
		}
		kill(pid,SIGKILL); //杀死子进程
		wait(NULL);  //等待回收子进程资源
	}
	close(sfd);
	return 0;
}

头文件:

复制代码
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/wait.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>
#include <sqlite3.h>
相关推荐
九河云11 分钟前
华为云ECS与Flexus云服务器X实例:差异解析与选型指南
大数据·运维·服务器·网络·人工智能·华为云
头发还没掉光光1 小时前
Linux网络初始及网络通信基本原理
linux·运维·开发语言·网络·c++
七夜zippoe2 小时前
Ascend C流与任务管理实战:构建高效的异步计算管道
服务器·网络·算法
一叶飘零_sweeeet2 小时前
手写 RPC 框架
java·网络·网络协议·rpc
fei_sun9 小时前
【复习】计网每日一题1105大题---ARP、NAT、路由器、IP数据报、冲突域、广播域、100BASE-F、10BASE-T
网络
wearegogog12311 小时前
基于混合蛙跳算法和漏桶算法的无线传感器网络拥塞控制与分簇新方法
网络·算法
周杰伦fans11 小时前
.NET Core WebAPI 中 HTTP 请求方法详解:从新手到精通
网络协议·http·.netcore
希赛网12 小时前
倒计时两天!软考网络工程师案例分析模拟题
网络·网络工程师·软考·案例分析题·考前复习
熙xi.12 小时前
以太网帧格式、IP数据报头部、TCP头部、UDP头部
网络·tcp/ip·udp
盛世宏博智慧档案13 小时前
新生产力算力机房内部温湿度之以太网监控系统方案
运维·服务器·网络·算力·温湿度