搭建网络聊天室(2023-3-19)

服务器

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#define ERR_MSG(msg) do{\
	fprintf(stderr,"line:%d",__LINE__);\
	perror(msg);\
}while(0)
#define PORT 8888
#define IP "192.168.31.244" //任意IP,会将本机的所有ip绑定到套接字上
#define N 128
//通讯结构体
typedef struct
{
	char type; //保存协议
	char name[20]; //保存姓名
	char text[N]; //保存信息内容
}MSG;
//链表节点
typedef struct node
{
	struct sockaddr_in cin;
	struct node* next;
}linklist;
int do_recv(int sfd, linklist* head);
int do_login(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head);
int do_quit(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head);
int do_chat(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head);
int do_system(int sfd, struct sockaddr_in cin);
int main(int argc, const char *argv[])
{
	//创建套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	//允许本地端口快速重用
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))<0)
	{
		ERR_MSG("setsockopt");
		return -1;
	}
	//绑定服务器的ip地址和端口号
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	sin.sin_addr.s_addr = inet_addr(IP);
	if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))<0)
	{
		ERR_MSG("bind");
		return -1;
	}
	printf("服务器准备完毕\n");
	//创建父子进程
	pid_t pid = fork();
	if(pid > 0)
	{
		//父进程
		//创建存储客户端信息的链表
		linklist* head = (linklist*)malloc(sizeof(linklist));
		head->next = NULL;
		//接收客户端的信息:协议+名字+文本信息结构体
		do_recv(sfd, head);
	}
	else
	{
		//子进程
		//发送系统消息:从终端获取消息,发送
		do_system(sfd, sin);
	}
	return 0;
}
int do_system(int sfd, struct sockaddr_in sin)
{
	MSG sys_msg = {'C', "**system**"};
	while(1)
	{
		bzero(sys_msg.text, N);
		fgets(sys_msg.text, N , stdin);
		//将当前子进程当做客户端,父进程为服务器,发送信息
		if(sendto(sfd, &sys_msg, sizeof(sys_msg), 0, (void*)&sin, sizeof(sin)) <0)
		{
			ERR_MSG("sendto");
			exit(1);
		}
	}
	printf("发送成功\n");
}
int do_recv(int sfd, linklist* head)
{
	MSG recv_msg;
	struct sockaddr_in cin;
	socklen_t clen = sizeof(cin);
	while(1)
	{
		memset(&recv_msg, 0, sizeof(recv_msg));
		//接收客户端的请求
		if(recvfrom(sfd, &recv_msg, sizeof(recv_msg), 0, (struct sockaddr*)&cin,&clen) < 0)
		{
			ERR_MSG("recvfrom");
			return -1;
		}
		printf("type = %c __%d__\n", recv_msg.type, __LINE__);
		switch(recv_msg.type)
		{
		case 'L':
			//提取客户端的地址信息,并发送给其他客户端,然后将地址信息插入到链表中
			do_login(sfd, recv_msg, cin, head);
			break;
		case 'C':
			//遍历链表,将数据包转发出去,除了自己之外的客户端。
			do_chat(sfd, recv_msg, cin, head);
			break;
		case 'Q':
			//将退出信息发送给出自己以外的客户端,然后将自己从链表中删除
			do_quit(sfd, recv_msg, cin, head);
			break;
		}
	}
	return 0;
}
int do_quit(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head)
{
	//重现拼接文本信息
	printf("%s [%s : %d]下线\n", recv_msg.name, inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
	sprintf(recv_msg.text, "-------%s 已下线-------", recv_msg.name);
	//遍历链表
	while(head->next != NULL)
	{
		if(memcmp(&cin, &head->next->cin, sizeof(cin)) == 0)
		{
			//删除head->next节点
			linklist* temp = head->next;
			head->next = temp->next;
			free(temp);
		}
		else
		{
			head = head->next;
			if(sendto(sfd, &recv_msg, sizeof(recv_msg), 0, (void*)&head->cin,sizeof(head->cin)) < 0)
			{
				ERR_MSG("sendto");
				exit(1);
			}
		}
	}
	return 0;
}
int do_chat(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head)
{
	//重新拼接群聊消息:名字:信息
	char buf[N] = "";
	sprintf(buf, "%s:%s", recv_msg.name, recv_msg.text);
	strcpy(recv_msg.text, buf);
	//循环发送给除了自己以外的客户端
	while(head->next != NULL)
	{
		head = head->next;
		if(memcmp(&head->cin, &cin, sizeof(cin)) != 0)
		{
			if(sendto(sfd, &recv_msg, sizeof(recv_msg), 0, (void*)&head->cin,sizeof(head->cin)) < 0)
			{
				ERR_MSG("sendto");
				exit(1);
			}
		}
	}
	return 0;
}
int do_login(int sfd, MSG recv_msg, struct sockaddr_in cin, linklist* head)
{
	//打印客户端登录成功
	printf("%s [%s : %d]登录成功\n", recv_msg.name, inet_ntoa(cin.sin_addr),
			ntohs(cin.sin_port));
	//拼接登录成功信息
	sprintf(recv_msg.text, "-------%s 登录成功-------", recv_msg.name);

		//遍历链表发送信息
		while(head->next != NULL)
		{
			head = head->next;
			if(sendto(sfd, &recv_msg, sizeof(recv_msg), 0, (struct sockaddr*)&head->cin, sizeof(head->cin)) < 0)
			{
				ERR_MSG("sendto");
				exit(1);
			}
		}
	//将登录成功的客户端地址信息添加到链表中
	linklist* temp = (linklist*)malloc(sizeof(linklist));
	temp->next = NULL;
	temp->cin = cin;
	//上述while循环结束后 head 就处于尾节点处,可以直接指向temp节点
	head->next = temp;
	return 0;
}

 客户端

#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_MSG(msg) do{\
	fprintf(stderr,"line:%d",__LINE__);\
	perror(msg);\
}while(0)
#define PORT 8888
#define IP "192.168.31.244" //任意IP,会将本机的所有ip绑定到套接字上
#define N 128
typedef struct
{
	char type; //保存协议,‘L’登录协议, ‘C’群聊协议 ‘Q’退出协议
	char name[20];
	char text[N];
}MSG;
typedef void (*sighandler_t)(int);
int do_recv(int sfd);
int do_chat(int sfd, struct sockaddr_in sin, MSG msg);
void handler(int sig)
{
	while(waitpid(-1, NULL, WNOHANG) > 0);
	exit(1);
}
int main(int argc, const char *argv[])
{
	sighandler_t s = signal(SIGCHLD, handler);
	if(s == SIG_ERR)
	{
		ERR_MSG("signal");
		return -1;
	}
	//创建套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}
	//绑定客户端的ip和端口,非必须的
	//填充服务器的ip地址和端口号
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	sin.sin_addr.s_addr = inet_addr(IP);
	socklen_t slen = sizeof(sin);
	//做登录请求
	MSG send_msg;
	send_msg.type = 'L';
	printf("请输入登录名>>>");
	fgets(send_msg.name, sizeof(send_msg.name), stdin);
	send_msg.name[strlen(send_msg.name)-1] = 0;
	//发送登录请求
	if(sendto(sfd, &send_msg, sizeof(send_msg), 0, (struct sockaddr*)&sin,sizeof(sin)) < 0)
	{
		ERR_MSG("sendto");
		return -1;
	}
	pid_t pid = fork();
	if(pid > 0)
	{
		//父进程做接收
		do_recv(sfd);
	}
	else if(0 == pid)
	{
		//子进程发送信息;
		do_chat(sfd, sin, send_msg);
	}
	else
	{
		ERR_MSG("fork");
		return -1;
	}
	return 0;
}
int do_chat(int sfd, struct sockaddr_in sin, MSG msg)
{
	while(1)
	{
		//从终端获取数据
		bzero(msg.text, N);
		fgets(msg.text, N, stdin);
		msg.text[strlen(msg.text)-1] = 0;
		//如果是聊天,则协议设置为C,如果是quit,则协议设置为Q,然后发送。
		if(strncasecmp(msg.text, "quit", 4) == 0)
		{
			msg.type = 'Q';
		}
		else
		{
			msg.type = 'C';
		}
		if(sendto(sfd, &msg, sizeof(msg), 0, (void*)&sin, sizeof(sin)) < 0)
		{
			ERR_MSG("sendto");
			exit(1);
		}
		//如果是退出协议,发送结束后,子进程退出
		//父进程需要回收子进程资源
		if(msg.type == 'Q')
		{
			exit(1);
		}
	}
	return 0;
}
int do_recv(int sfd)
{
	MSG recv_msg;
	while(1)
	{
		memset(&recv_msg, 0, sizeof(recv_msg));
		if(recvfrom(sfd, &recv_msg, sizeof(recv_msg), 0, NULL, NULL)<0)
		{
			ERR_MSG("recvfrom");
			return -1;
		}
		printf("%s\n\n", recv_msg.text);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/k_weihgl/article/details/129657129