思维导图:


代码:
服务器:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <a.h>
#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<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <a.h>
#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);}
}
运行效果:
应该还是有不少Bug的我记得有个段错误,但是没修改然后就莫名其妙的好了,后面还会在修改的,本代码参考chatgpt的解答