2024/03/24(网络聊天室)

基于UDP的网络聊天室

项目需求

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

头文件

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>

服务器端

#include<myhead.h>

//模式
#define LOGIN 	1
#define CHAT 	2
#define QUIT 	3

#define N 128
#define ERR_LOG(msg) do{\
	printf("%d %s %s \n", __LINE__, __func__, __FILE__);\
	perror(msg);\
}while(0)

//收发信息的结构体
typedef struct
{
	int type;
	char name[20];
	char text[N] ;

}MSG;


//地址结构信息体链表
typedef struct node
{
	struct sockaddr_in cin;
	struct node* next;
}addr_list;

//函数声明
int do_recv(int sfd, addr_list* head);
int do_login(int sfd, addr_list* head, MSG rcv_msg, struct sockaddr_in cin);
int do_quit(int sfd, addr_list* head, MSG rcv_msg, struct sockaddr_in cin);
int do_chat(int sfd, addr_list* head, MSG rcv_msg, struct sockaddr_in cin);
int do_system(int sfd, struct sockaddr_in sin);

/********************主程序***************************/
int main(int argc, const char *argv[])
{
	//判断输入
	if(argc<3)
	{
		printf("请输入IP和端口号\n");
		return -1;
	}

	//1、创建报式套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		perror("socket");
		return -1;
	}

	//2、绑定服务器IP和端口号
	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]);

	if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		perror("bind error");
		return -1;
	}

	//创建进程
	int pid = 0;
	pid = fork();
	if(pid > 0)
	{
		//父进程, 接收消息
		addr_list*  head = (addr_list*)malloc(sizeof(addr_list));
		head->next = NULL;
		do_recv(sfd , head);

	}
	else if(pid==0)
	{
		//子进程,发送系统消息
		do_system(sfd, sin);
	}


	//4.关闭套接字
	close(sfd);
	return 0;
}

//系统提示消息
int do_system(int sfd, struct sockaddr_in sin)
{
	MSG sys_msg = {htonl(CHAT), "**system**"};

	while(1)
	{
		bzero(sys_msg.text, N);
		fgets(sys_msg.text, N, stdin);
		sys_msg.text[strlen(sys_msg.text)-1] = 0;

		//将当前进程当做客户端,父进程当做服务器,发送信息;
		if(sendto(sfd, &sys_msg, sizeof(sys_msg), 0, (void*)&sin, sizeof(sin)) < 0)
		{
			ERR_LOG("sendto");
			return -1;
		}
	}
	printf("系统消息发送成功");
	return 0;
}

//登录上线函数
int do_login(int sfd, addr_list* head, MSG rcv_msg, struct sockaddr_in cin)
{
	//打印登录成功
	printf("%s [%s:%d]登录成功\n", rcv_msg.name,(char*)inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));

	sprintf(rcv_msg.text, "-----%s登录成功-----", rcv_msg.name);


	while(head->next != NULL)
	{
		head = head->next;
		if(sendto(sfd, &rcv_msg, sizeof(rcv_msg), 0, (void*)&(head->cin), sizeof(head->cin))<0)
		{
			ERR_LOG("sendto");
			return -1;
		}
	}

	//将登录成功的客户端信息添加到链表中;
	addr_list *temp = (addr_list*)malloc(sizeof(addr_list));
	temp->cin = cin;
	temp->next= NULL;
	
	head->next = temp;
	return 0;

}

//接收函数
int do_recv(int sfd, addr_list* head)
{
	MSG rcv_msg;
	int recv_len = 0;
	struct sockaddr_in cin;
	socklen_t clen = sizeof(cin);

	while(1)
	{
		//接收消息
		recv_len = recvfrom(sfd, &rcv_msg, sizeof(rcv_msg), 0, (void*)&cin, &clen);
		if(recv_len < 0)
		{
			ERR_LOG("recvfrom");
			return -1;
		}
		//提取出协议
		//将网络字节序转换为主机字节序
		int type = ntohl(rcv_msg.type);

		switch(type)
		{
		case LOGIN:
			do_login(sfd, head, rcv_msg, cin);
			break;
		case CHAT:
			do_chat(sfd, head, rcv_msg, cin);
			break;
		case QUIT:
			do_quit(sfd, head, rcv_msg, cin);
			break;
		}

	}
}

//聊天发送函数
int do_chat(int sfd, addr_list* head, MSG rcv_msg, struct sockaddr_in cin)
{

	printf("%s [%s:%d]chat成功\n", rcv_msg.name,(char*)inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
	//重新拼接群聊消息: 名字+消息
	char buf[258] = "";
	sprintf(buf, "%s:%s", rcv_msg.name, rcv_msg.text);
	strcpy(rcv_msg.text, buf);

	//循环发送,除了自己以外的ip地址
	while(head->next != NULL)
	{
		head = head->next;
		if(memcmp(&cin, &head->cin, sizeof(cin)) != 0)
		{	
			if(sendto(sfd, &rcv_msg, sizeof(rcv_msg),0, (void*)&(head->cin), sizeof(head->cin))<0)
			{
				ERR_LOG("sendto");
				return -1;
			}
		}
	}
	return 0;	
}

//退出函数
int do_quit(int sfd, addr_list* head, MSG rcv_msg, struct sockaddr_in cin)
{
	sprintf(rcv_msg.text, "-----%s 已下线-----\n", rcv_msg.name);
	//循环遍历链表,发送"xxx一下线"的信息
	//发送给当前客户端外的其余客户端
	while(head->next != NULL)
	{
		if(memcmp(&cin, &head->next->cin, sizeof(cin)) == 0)
		{
			//从链表中删除该客户端信息
			//free
			addr_list* temp = head->next;
			head->next = temp->next;
			free(temp);
		}
		else
		{
			head = head->next;
			if(sendto(sfd, &rcv_msg, sizeof(rcv_msg),0, (void*)&head->cin, sizeof(head->cin))<0)
			{
				ERR_LOG("sendto");
				return -1;
			}
		}
	}
	return 0;
}

客户端

#include<myhead.h>

//模式
#define LOGIN 	1
#define CHAT 	2
#define QUIT 	3

#define N 128
#define ERR_LOG(msg) do{\
	printf("%d %s %s \n", __LINE__, __func__, __FILE__);\
	perror(msg);\
}while(0)

//收发信息的结构体
typedef struct
{
	int type;
	char name[20];
	char text[N] ;

}MSG;


typedef void (*sighandler_t)(int);

//声明函数
int do_recv(int sfd);
int do_chat(int sfd, MSG msg, struct sockaddr_in sin);

//信号处理函数
void handler(int sig)
{
	//回收子进程资源并退出
	while(waitpid(-1,NULL, WNOHANG)>0);
	exit(0);
}

/*******************主程序******************************/
int main(int argc, const char *argv[])
{
	if(argc < 3)
	{   
		printf("请输入 ip 和端口号\n");
		return -1; 
	} 

	//注册信号处理函数,让子进程退出后,父进程回收子进程资源并退出
	sighandler_t s = signal(SIGCHLD, handler);
	if(s == SIG_ERR)
	{
		ERR_LOG("signal");
		return -1;
	}

	//1.创建报式套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		perror("socket");
		return -1;
	}

	//2.填充服务器ip和端口号
	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 addrlen = sizeof(sin);


	//登录协议
	MSG msg ;
	msg.type = htonl(LOGIN);

	printf("请输入姓名>>>");
	fgets(msg.name, 20, stdin);
	msg.name[strlen(msg.name)-1] = 0;

	//发送登录信息sendto
	if(sendto(sfd, &msg, sizeof(msg), 0, (void*)&sin, sizeof(sin)) < 0)
	{
		ERR_LOG("sendto");	
		return -1;
	}

	pid_t pid = fork();

	if(pid > 0)
	{
		//父进程获取信息
		do_recv(sfd);
	}
	else if(0 == pid)
	{
		//子进程发送信息
		do_chat(sfd, msg, sin);
	}


	//4.关闭套接字
	close(sfd);
	return 0;
}

//聊天函数
int do_chat(int sfd, MSG msg, struct sockaddr_in sin)
{
	while(1)
	{
		//从终端获取聊天信息
		bzero(msg.text, N);
		fgets(msg.text, N ,stdin);
		msg.text[strlen(msg.text)-1] = 0;

		//如果是聊天信息,则封装上 CHAT协议 
		//如果是退出信息,则封装上 QUIT协议
		if(strncasecmp(msg.text, "quit" , 4) == 0)
		{
			msg.type = htonl(QUIT);
		}
		else
		{
			msg.type = htonl(CHAT);
		}

		//发送 
		if(sendto(sfd, &msg, sizeof(msg), 0, (void*)&sin, sizeof(sin)) < 0)
		{
			ERR_LOG("sendto");
			return -1;
		}

		//如果是退出信息,则客户端要退出
		//子进程:即当前进程,需要退出
		//且父进程也要退出
		if(msg.type == htonl(QUIT))
		{
			exit(0); 		//退出子进程;
		}
	}
}

//接收函数
int do_recv(int sfd)
{
	MSG rcv_msg ;
	while(1)
	{
		if(recvfrom(sfd, &rcv_msg, sizeof(rcv_msg), 0, NULL, NULL) < 0)
		{
			perror("recvfrom");
			return -1;
		}
		printf("%s\n",rcv_msg.text);
	}
}

实现效果

相关推荐

  1. 实现一个网页聊天

    2024-03-26 02:56:04       31 阅读
  2. 基于UDP网络聊天OICQ

    2024-03-26 02:56:04       37 阅读
  3. 2024.3.7 简易网络聊天

    2024-03-26 02:56:04       22 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-26 02:56:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-26 02:56:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-26 02:56:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-26 02:56:04       20 阅读

热门阅读

  1. 数据结构-栈-004

    2024-03-26 02:56:04       17 阅读
  2. 鸿蒙 ohpm 的异常报错

    2024-03-26 02:56:04       18 阅读
  3. webpack的核心概念

    2024-03-26 02:56:04       18 阅读
  4. mysql 截取字符串及解析json

    2024-03-26 02:56:04       20 阅读
  5. 双指针的详细教程

    2024-03-26 02:56:04       18 阅读
  6. vue2中如何实现数据的更新?

    2024-03-26 02:56:04       17 阅读
  7. 【无标题】程序员35岁会失业吗?

    2024-03-26 02:56:04       19 阅读
  8. Linux下常用命令

    2024-03-26 02:56:04       19 阅读
  9. 数据结构中排序算法

    2024-03-26 02:56:04       18 阅读