华清远见作业第三十一天——网络编程(第六天)

思维导图:
华清远见作业第三十一天——网络编程(第六天)_第1张图片

 代码:

服务器:

#include
#include
#include
#include 
#include 
#include 
#define SER_PORT 10000
#define login 1 //登录协议
#define exchange 2 //交流协议
#define quit 3 //退出协议

//定义从客户端发来信息的结构体体内容
typedef struct infor
{
	int type_num; //协议
	char name[50]; //名字
	char text[128]; //发送的内容
}type_s;
//定义结构体接收客户端地址信息结构体
typedef struct cli_infor
{
	struct sockaddr_in cin; //该结构体中包含了客户端通信区域,端口号,ip地址
	struct cli_infor *next; //指向下一个位置
}cli_linklist;
int do_login(int sfd, cli_linklist *head,struct infor rcv_info,struct sockaddr_in cin);
//父进程接收客户端信息函数
int do_recv(int sfd);
int do_exchange(int sfd,cli_linklist *head,struct infor rcv_info,struct sockaddr_in cin);
//退出函数
int do_quit(int sfd,cli_linklist *head,struct infor rcv_info,struct sockaddr_in cin);
int do_send(int sfd, struct sockaddr_in sin);
//创建节点
cli_linklist *create_node()
{
	cli_linklist * p=(cli_linklist *)malloc(sizeof(cli_linklist));
	if(NULL==p)
		return NULL;
	p->next=NULL;
	return p;
}
//尾插
//客户端链表尾插
cli_linklist *insert_rear(cli_linklist *head,struct sockaddr_in cin)
{
	cli_linklist * s=create_node();
	if(NULL==s)
		return head;
	s->cin=cin;

	if(NULL==head)
	{
		head=s;
		return s;
	}else{
	cli_linklist * p=head;
	while(p->next!=NULL)
		p=p->next;
	p->next=s;
	return head;
	}
}

//删除
//链表中删除该地址信息
//段错误删链表问题
cli_linklist *exit_chat(cli_linklist *head,struct sockaddr_in cin)
{
	if(head->next==NULL)//只有一个客户端时
	{
		free(head);
		head=NULL;
		return head;
	}

	cli_linklist *p=head;
	while(p->next!=NULL)  //两个以上客户端
	{
		if(memcmp(&(p->next->cin),&cin,sizeof(cin))==0)//找到p下一个节点地址信息符合的
		{
			cli_linklist *del=p->next;
			p->next=del->next;
			free(del);
			del=NULL;
			break;
		}else{
			p=p->next;
		}
	}
	return head;
}



int main(int argc, const char *argv[])
{
	//
	if(argc<3)
	{
		printf("请输入ip号和端口号\n");
		return -1;
	}
	//创建套接字
	int sfd=socket(AF_INET,SOCK_DGRAM,0);
	//SOCK_DGRAM:表示使用UDP套接字通信
	if(sfd==-1)
	{
		perror("socket error");
		return -1;
	}
	//2绑定
	//2.1填充地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family =AF_INET;
	sin.sin_port=htons(atoi(argv[2])); //端口号(argv[0])
	sin.sin_addr.s_addr=inet_addr(argv[1]); //ip地址(argv[1])
	//绑定工作
	if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))==-1)
	{
		perror("bind error");
		return -1;
	}
	/*//定义变量存储客户端地址信息结构体
	struct sockaddr_in cin;
	socklen_t socklen=sizeof(cin);*/
	//创建进程
	pid_t pid=0; //存放子进程号
	pid=fork(); // 创建子进程
	if(pid>0)
	{
		//父进程:用于接收连接客户端的请求
		//申请空间创建节点
		//cli_linklist *head=(cli_linklist *)malloc(sizeof(cli_linklist)); //申请空间
		//数据区域和指针区域初始化
		//head->cin=NULL;
		//head->next=NULL;
		//父进程接收客户端信息函数
		do_recv(sfd);
	}else if(pid==0) //子进程发送消息
	{
		//子进程发送消息给客户端函数
		do_send(sfd,sin);
	}
	//关闭套接字
	close(sfd);
	return 0;
}

 

//父进程接收客户端信息函数
int do_recv(int sfd)
{
	struct infor rcv_info; //从客户端发送的信息
	int recvlen=0; //定义一个接收客户端发来的信息的返回值e
	//定义从客户端发来信息的结构体 
	struct sockaddr_in cin;
	socklen_t socklen=sizeof(cin);
	cli_linklist *head=NULL;
	//循环接收信息
	while(1)
	{
		 recvlen=recvfrom(sfd,&rcv_info,sizeof(rcv_info),0,(struct sockaddr*)&cin,&socklen);
		 if(recvlen==-1)
		 {
		 	perror("从客户端读取信息失败了:");
			return -1;
		 }
		 printf("读取成功了,开始找程序中的从客户端发来的自己定义的协议\n");
		 //开始找协议
		 //先把网络字节序转换为主机字节序
		 int type=ntohl(rcv_info.type_num);
		printf("type=%d\n",type);
		 switch(type)
		 {
		 case  login://协议1为登录
			 {	
				head=insert_rear(head,cin);
			 	do_login(sfd, head,rcv_info,cin);
				break;
			 }
		 case exchange ://协议2为交流协议
			 {
			 	do_exchange(sfd,head,rcv_info,cin);
				break;
			 }
		 case quit://为退出协议
			 {
			 	do_quit(sfd,head,rcv_info,cin);
				head=exit_chat(head,cin);
				break;
			 }
		 }
		 
	}
}
//登录函数
//套接子,链表,客户端信息
int do_login(int sfd, cli_linklist *head,struct infor rcv_info,struct sockaddr_in cin)
{
	//服务器输出(内容了)
	printf("%s [%s:%d] 登录成功了\n",rcv_info.name,(char *)inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
							
	//循环遍历链表向每一个客户端发登录成功
	
	//重新拼接群聊消息: 名字+消息
	char rbuf[258] = "";
	snprintf(rbuf,sizeof(rbuf),"%s登录\n",rcv_info.name);
	strcpy(rcv_info.text, rbuf);
	//先判断链表是否为空
	cli_linklist *p=head;		
		while(p->next!=NULL)//尾插法所以最后一个不需要发
		{
		
			if(sendto(sfd,&rcv_info,sizeof(rcv_info),0,\
			(struct sockaddr*)&(p->cin),sizeof(p->cin))==-1)
			{
				perror("writer error登录写入错误");
				return -1;
			}
			
			//链表向后移动			
			p=p->next;
		}
	

	return 0;
}
//发送函数
int do_exchange(int sfd,cli_linklist *head,struct infor rcv_info,struct sockaddr_in cin)
{
	//服务器显示发送成功
	printf("%s [%s:%d] 发送成功了\n",rcv_info.name,(char *)inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
	//重新拼接群聊消息: 名字+消息
	char rbuf[258] = "";
	snprintf(rbuf,sizeof(rbuf),"%s:%s",rcv_info.name,rcv_info.text);
	strcpy(rcv_info.text, rbuf);
	//循环遍历除自己以外的客户端,分别发送信息
	cli_linklist *p=head;
	while(p!=NULL)
	{
		if(p->cin.sin_addr.s_addr==cin.sin_addr.s_addr&&p->cin.sin_port==cin.sin_port)
		{	
			p=p->next;
			continue;
		}
		//向除了自己以外的客户端发送消息
		if(sendto(sfd,&rcv_info,sizeof(rcv_info),0,\
		(struct sockaddr*)&(p->cin),sizeof(p->cin))==-1)
		{
			perror("writer error交流写入错误");
			return -1;
		}
		p=p->next;
	}
	
	return 0;
}
//退出函数
int do_quit(int sfd,cli_linklist *head,struct infor rcv_info,struct sockaddr_in cin)
{
	//服务器显示退出成功的信息
	printf("%s [%s:%d] 退出成功了\n",rcv_info.name,\
	(char *)inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
	//循环向各个客户端发送退出成功的信息
	sprintf(rcv_info.text, "-----%s 已下线-----\n", rcv_info.name);
	cli_linklist *p=head;
	while(p!=NULL)
	{
		if(p->cin.sin_addr.s_addr==cin.sin_addr.s_addr&&p->cin.sin_port==cin.sin_port)
		{	
			p=p->next;
			continue;
		}
		//向除了自己以外的客户端发送消息
		if(sendto(sfd,&rcv_info,sizeof(rcv_info),0,\
		(struct sockaddr*)&(p->cin),sizeof(p->cin))==-1)
		{
			perror("writer error交流写入错误");
			return -1;
		}
		p=p->next;
	}
	return 0;
}

/*int do_send(int sfd, struct sockaddr_in sin)
{
	
	//自己封装协议
	struct infor types;
	types.type_num=htonl(exchange);  //2发送协议
	while(1)
	{
		
		
		//清零
		bzero(types.text,128);
		//从终端写入数据
		fgets(types.text,sizeof(types),stdin);
		types.text[strlen(types.text)-1] = 0;
		//将当前进程当做客户端,父进程当做服务器,发送信息;
		if(sendto(sfd, &types, sizeof(types), 0, (void*)&sin, sizeof(sin)) < 0)
		{
			perror("系统消息错误:");
			return -1;
		}
	}
	printf("系统消息发送成功");
	return 0;
}*/
int do_send(int sfd, struct sockaddr_in sin)
{
	type_s sys_msg;
	sys_msg.type_num=htonl(2);
	strcpy(sys_msg.name,"system");
	while(1)
	{
		bzero(sys_msg.text, 128);
		fgets(sys_msg.text, 128, 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;
}

客户端:

#include
#include
#include
#include 
#include 
#include 
#define login 1 //登录协议
#define exchange 2 //交流协议
#define QUIT 3 //退出协议
//定信息的结构体体内容
typedef struct infor
{
	int type_num; //协议
	char name[50]; //名字
	char text[128]; //发送的内容
}type_s;
void handler(int sig)
{
	//回收子进程资源并退出
	while(waitpid(-1,NULL, WNOHANG)>0);
	exit(0);
}
int do_recv(int sfd);
int do_chat(int sfd, type_s climsg, struct sockaddr_in sin);
int main(int argc, const char *argv[])
{
	if(argc < 3)
	{   
		printf("请输入 ip 和端口号\n");
		return -1; 
	}
	//注册信号处理函数
	if(signal(SIGCHLD,handler)==SIG_ERR)
	{
		perror("signal error");
		return -1;
	}
	//创建套接字
	int sfd=socket(AF_INET,SOCK_DGRAM,0);
	//SOCK_DGRAM:表示使用UDP套接字通信
	if(sfd==-1)
	{
		perror("socket error");
		return -1;
	}
	//2.1填充地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family =AF_INET;
	sin.sin_port=htons(atoi(argv[2])); //端口号(argv[0])
	sin.sin_addr.s_addr=inet_addr(argv[1]); //ip地址(argv[1])
	//封装登录协议
	type_s climsg;
	climsg.type_num=htonl(1);
	printf("请输入姓名>>>");
	fgets(climsg.name,128,stdin);
	climsg.name[strlen(climsg.name)-1]=0;
	//向服务器发送登录协议
	if(sendto(sfd, &climsg, sizeof(climsg), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		perror("sendtio error");
		return -1;
	}
	//定义线程
	pid_t pid = fork();
	if(pid > 0)
	{
		//父进程获取信息
		do_recv(sfd);
		
	}
	else if(pid==0)
	{
		//子进程发送信息
		do_chat(sfd, climsg, sin);
	}


	//4.关闭套接字
	close(sfd);		
	return 0;
}
int do_chat(int sfd, type_s climsg, struct sockaddr_in sin)
{
	while(1)
	{		
		//从终端获取聊天信息
		bzero(climsg.text, sizeof(climsg.text));
		fgets(climsg.text, 128 ,stdin);
		climsg.text[strlen(climsg.text)-1] = 0;
		if(strcmp(climsg.text,"quit")==0)
		{
			climsg.type_num=htonl(QUIT);
		}
		else
		{
			climsg.type_num=htonl(exchange);
		}
		//发送给服务器
		if(sendto(sfd, &climsg, sizeof(climsg), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
		{
			perror("发送错误");
			return -1;
		}
		//退出进程
		if(strcmp(climsg.text,"quit")==0)
		{
			exit(0);
		}
	}
}
int do_recv(int sfd)
{
    struct infor rcv_info; // 从服务端发送的信息
    int recvlen = 0; // 定义一个接收服务端发来的信息的返回值
    struct sockaddr_in server_addr; // 服务器地址信息
    socklen_t addrlen = sizeof(server_addr); // 地址信息长度

    while (1) {
        recvlen = recvfrom(sfd, &rcv_info, sizeof(rcv_info), 0, (struct sockaddr*)&server_addr, &addrlen);
        if (recvlen == -1) {
            perror("从客户端读取信息失败了:");
            return -1;
        }
        printf("%s\n", rcv_info.text);
    }
}



运行效果:

华清远见作业第三十一天——网络编程(第六天)_第2张图片

应该还是有不少Bug的我记得有个段错误,但是没修改然后就莫名其妙的好了,后面还会在修改的,本代码参考chatgpt的解答

你可能感兴趣的:(华清远见作业,网络)