项目需求:
1.如果有用户登录,其他用户可以收到这个人的登录信息
2.如果有人发送信息,其他用户可以收到这个人的群聊信息
3.如果有人下线,其他用户可以收到这个人的下线信息
4.服务器可以发送系统信息
客户端代码
#include<myhead.h>
//用户链表结构体
typedef struct Node
{
char usrName[20];//用户名
struct sockaddr_in cin;//客户端地址
struct Node *next;//指针
}Node,*NodePtr;
//消息结构体
typedef struct msgTyp
{
char type;//消息类型
char usrName[20];//用户名
char msgText[1024];//消息内容
}msgTyp;
//创建头结点
NodePtr create_Node(struct sockaddr_in sin)
{
NodePtr L=(NodePtr)malloc(sizeof(Node));
if(NULL==L)
{
printf("创建失败\n");
return NULL;
}
strcpy(L->usrName,"**system**");//服务器名字
L->cin=sin;//服务器地址
L->next=NULL;
return L;
}
//申请结点
NodePtr apply_Node()
{
NodePtr p=(NodePtr)malloc(sizeof(Node));
if(NULL==p)
{
printf("创建失败\n");
return NULL;
}
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
memset(&cin,0,addrlen);
strcpy(p->usrName,"");
p->cin=cin;
p->next=NULL;
return p;
}
//判空
int list_empty(NodePtr L)
{
return L->next==NULL;
}
//尾插
int inser_tail(NodePtr L,struct sockaddr_in cin,msgTyp M)
{
if(NULL==L)
{
printf("链表不合法\n");
return -1;
}
NodePtr p=apply_Node();
strcpy(p->usrName,M.usrName);
p->cin=cin;
NodePtr t=L;
while(t->next!=NULL)
{
t=t->next;
}
t->next=p;
return 0;
}
//查找结点位置
int search_Node(NodePtr L,struct sockaddr_in cin)
{
if(NULL==L||list_empty(L))
{
printf("查找失败\n");
return -1;
}
NodePtr q=L->next;
int i=1;
//判断端口和ip地址
while(q->cin.sin_addr.s_addr!=cin.sin_addr.s_addr||q->cin.sin_port!=cin.sin_port)
{
i++;
q=q->next;
}
return i;//返回位置
}
//按位置删除结点
int delete_Node(NodePtr L,struct sockaddr_in cin)
{
if(NULL==L||list_empty(L))
{
printf("删除失败\n");
return -1;
}
int i=search_Node(L,cin);//要删除的结点位置
NodePtr p=L;
int j=1;
//找到要删除结点
while(j<i)
{
p=p->next;
j++;
}
NodePtr q=p->next;
p->next=q->next;
free(q);
q=NULL;
return 0;
}
//头删
int delete_head(NodePtr L)
{
if(NULL==L||list_empty(L))
{
printf("删除失败\n");
return -1;
}
NodePtr p=L->next;
L->next=p->next;
free(p);
p=NULL;
return 0;
}
//销毁链表
int destroy(NodePtr L)
{
if(NULL==L)
{
printf("销毁失败\n");
return -1;
}
while(!list_empty(L))
{
delete_head(L);
}
free(L);
L=NULL;
return 0;
}
int main(int argc, char const *argv[])
{
//判断输入个数
if(argc!=3)
{
printf("input error\n");
return -1;
}
//创建服务器套接字
int sfd=socket(AF_INET,SOCK_DGRAM,0);
if(sfd==-1)
{
perror("socket error");
return -1;
}
//服务器信息
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))==-1)
{
perror("bind error");
return -1;
}
//创建头结点
NodePtr L=create_Node(sin);
if(NULL==L)
{
return -1;
}
//客户端
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
//创建子进程
pid_t pid=fork();
//子进程工作内容
if(pid==0)
{
msgTyp msg;//定义消息变量
char buf[1044]="";//存放数据
while(1)
{
//初始化
memset(&msg,0,sizeof(msg));
bzero(buf,sizeof(buf));
//接收客户端消息
recvfrom(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&cin,&addrlen);
//判断消息类型
switch(msg.type)
{
//d类型消息登录
case 'd':
{
//服务器输出登录成功
printf("%s [%s:%d]登录成功\n",msg.usrName,inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
//登录成功消息存入buf
sprintf(buf,"-----%s登录成功-----",msg.usrName);
//遍历链表发送
NodePtr p=L->next;
while(p!=NULL)
{
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&p->cin,sizeof(p->cin));
p=p->next;
}
//插入链表
inser_tail(L,cin,msg);
}
break;
//t类型消息发送消息
case 't':
{
//判断端口和地址是否为服务器,服务器输出
if(L->cin.sin_port!=cin.sin_port||L->cin.sin_addr.s_addr!=cin.sin_addr.s_addr)
{
printf("%s [%s:%d]%s\n",msg.usrName,inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),msg.msgText);
}
//消息存入buf
sprintf(buf,"%s:%s",msg.usrName,msg.msgText);
//遍历链表
NodePtr p=L->next;
while(p!=NULL)
{
//判断端口和地址是否和发送过来的客户端一样,不是则发送消息
if(p->cin.sin_port!=cin.sin_port||p->cin.sin_addr.s_addr!=cin.sin_addr.s_addr)
{
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&p->cin,sizeof(p->cin));
}
p=p->next;
}
}
break;
//q类型消息退出
case 'q':
{
//服务器输出
printf("%s [%s:%d]退出成功\n",msg.usrName,inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
//已下线消息存入buf
sprintf(buf,"-----%s已下线-----",msg.usrName);
delete_Node(L,cin);//删除该结点
//遍历链表发送消息
NodePtr p=L->next;
while(p!=NULL)
{
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&p->cin,sizeof(p->cin));
p=p->next;
}
}
break;
//消息类型错误
default:
printf("接收消息类型错误\n");
}
}
}
//父进程工作内容
if(pid>0)
{
msgTyp msg;//定义消息类型变量
while(1)
{
memset(&msg,0,sizeof(msg));//初始化
fgets(msg.msgText,sizeof(msg.msgText),stdin);//服务器输入内容存入消息内容
msg.msgText[strlen(msg.msgText)-1]=0;
//判断是否退出
if(strcmp(msg.msgText,"quit")==0)
{
break;
}
msg.type='t';//消息类型为t
strcpy(msg.usrName,"**system**");//服务器名字
//发送给子进程服务器
sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sizeof(sin));
}
}
destroy(L);//销毁链表
kill(pid,SIGKILL);//杀死子进程
wait(NULL);//回收
close(sfd);//关闭套接字
return 0;
}
客户端代码
#include<myhead.h>
//消息结构体
typedef struct msgTyp
{
char type;
char usrName[20];
char msgText[1024];
}msgTyp;
int main(int argc, char const *argv[])
{
//判断输入个数
if(argc!=3)
{
printf("input error\n");
return -1;
}
//创建套接字
int cfd=socket(AF_INET,SOCK_DGRAM,0);
if(cfd==-1)
{
perror("socket error");
return -1;
}
//服务器地址
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 sin_addrlen=sizeof(sin);
msgTyp msg;//定义消息变量
//存入姓名
printf("请输入姓名:");
scanf("%s",msg.usrName);
getchar();
strcpy(msg.msgText,"");//初始化消息内容
msg.type='d';//d类型消息
//发送给服务器
sendto(cfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sin_addrlen);
//创建子进程
pid_t pid=fork();
//子进程工作内容
if(pid==0)
{
char buf[1024]="";//存消息
while(1)
{
bzero(buf,sizeof(buf));//初始化
//接收消息
recvfrom(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,&sin_addrlen);
printf("%s\n",buf);//输出
}
}
//父进程工作内容
if(pid>0)
{
while(1)
{
bzero(msg.msgText,sizeof(msg.msgText));//初始化消息内容
fgets(msg.msgText,sizeof(msg.msgText),stdin);//输入消息存入
msg.msgText[strlen(msg.msgText)-1]=0;
//判断是否退出
if(strcmp(msg.msgText,"quit")==0)
{
msg.type='q';//q类型消息
//发送消息给服务器
sendto(cfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sizeof(sin));
break;
}
msg.type='t';//t类型消息
sendto(cfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sizeof(sin));
}
}
kill(pid,SIGKILL);//杀死子进程
wait(NULL);//回收
close(cfd);//关闭套接字
return 0;
}
运行结果