#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的解答