Ser.c
#include<myhead.h>
#define SER_IP "192.168.159.148"
#define SER_PORT 6666
//因为客户端发送给服务器的消息是不同类型,所以定义结构体比较方便
typedef struct msg_TYPE
{
char type; // L:登录 C:聊天 Q:退出
char usrName[20]; //用户名
char msgText[1024]; //消息正文
}msg_t;
//定义链表结构体:涉及到用户的登录和退出,也就是插入和删除,所以用链表比较方便
typedef struct Node
{
//char usrName[20]; //用户名(数据域)
struct sockaddr_in cin; //IP地址和端口号(数据域)
struct Node *next; //指针域
}node_t;
//函数声明
void create_link(node_t **head);
int do_register(int sfd,msg_t msg,struct sockaddr_in cin,node_t *lhead);
int do_chat(int sfd,msg_t msg,struct sockaddr_in cin,node_t *lhead);
int quit_chat(int sfd,msg_t msg,struct sockaddr_in cin,node_t *lhead);
int main(int argc, const char *argv[])
{
//判断终端输入是否合理
if(argc!=3)
{
printf("input file error\n");
printf("usage:./a.out IP port\n");
return -1;
}
//1.创建用于通信的套接字
int sfd=socket(AF_INET,SOCK_DGRAM,0);
if(sfd==-1)
{
perror("socket error");
return -1;
}
//2.绑定服务器IP地址和端口
//2.1填充服务器地址信息结构体
struct sockaddr_in sin;
memset(&sin,0,sizeof(sin));
sin.sin_family=AF_INET;
sin.sin_port=htons(atoi(argv[2]));
sin.sin_addr.s_addr=inet_addr(argv[1]);
socklen_t socklen1=sizeof(sin);
//2.2绑定
if(bind(sfd,(struct sockaddr*)&sin,socklen1)==-1)
{
perror("bind error");
return -1;
}
//填充客户端地址信息结构体
struct sockaddr_in cin;
socklen_t socklen2=sizeof(cin);
msg_t msg; //定义结构体变量
//创建多进程,父进程用来发送系统消息,子进程用来接收数据并处理
pid_t pid=fork();
if(pid>0)
{
//父进程:发送系统消息
msg.type='C';
strcpy(msg.usrName,"server");
while(1)
{
fgets(msg.msgText,1024,stdin);
msg.msgText[strlen(msg.msgText)-1]=0;
if(sendto(sfd,&msg,sizeof(msg_t),0,(struct sockaddr*)&sin,socklen1)==-1)
{
perror("sendto error");
return -1;
}
}
}
else if(pid==0)
{
//子进程:用来接收数据并处理
//定义链表头节点
node_t *lhead=NULL;
create_link(&lhead);
lhead->next=NULL;
while(1)
{
//清空数据
memset(&msg,0,sizeof(msg));
memset(&cin,0,sizeof(cin));
if((recvfrom(sfd,&msg,sizeof(msg_t),0,(struct sockaddr*)&cin,&socklen2))==-1)
{
perror("recvfrom error");
return -1;
}
printf("%8s:[%s]\n",msg.usrName,msg.msgText);
switch(msg.type)
{
case 'L':
{
do_register(sfd,msg,cin,lhead);
}
break;
case 'C':
{
do_chat(sfd,msg,cin,lhead);
}
break;
case 'Q':
{
quit_chat(sfd,msg,cin,lhead);
}
break;
}
}
}
else
{
perror("fork error");
return -1;
}
//关闭套接字
close(sfd);
return 0;
}
//创建链表头节点
void create_link(node_t **head)
{
*head=(node_t *)malloc(sizeof(node_t));
}
//用户登录
int do_register(int sfd,msg_t msg,struct sockaddr_in cin,node_t *lhead)
{
//遍历链表,将登录信息发送给其他在线用户
node_t *p=lhead;
while(p->next!=NULL)
{
p=p->next;
if(sendto(sfd,&msg,sizeof(msg_t),0,(struct sockaddr*)&(p->cin),sizeof(p->cin))==-1)
{
perror("sendto error");
return -1;
}
}
//将登录信息插入链表(头插)
//定义一个指针保存用户信息
node_t *umsg=NULL;
create_link(&umsg);
umsg->cin=cin;
umsg->next=lhead->next;
lhead->next=umsg;
return 0;
}
//定义聊天函数
int do_chat(int sfd,msg_t msg,struct sockaddr_in cin,node_t *lhead)
{
//遍历链表,将消息发送给除自己外所以用户
node_t *p=lhead;
while(p->next!=NULL)
{
p=p->next; //循环后移
//判断链表中信息是否是自己,是自己就不发送信息
if(memcmp(&(p->cin),&cin,sizeof(cin)));
{
if(sendto(sfd,&msg,sizeof(msg_t),0,(struct sockaddr*)&(p->cin),sizeof(p->cin))==-1)
{
perror("sendto error");
return -1;
}
}
}
return 0;
}
//定义退出函数
int quit_chat(int sfd,msg_t msg,struct sockaddr_in cin,node_t *lhead)
{
node_t *p=lhead;
while(p->next!=NULL)
{
//判断链表客户端信息是否是自己,是自己就不发送,并且将自己的客户端信息在链表内删除
if(memcmp(&(p->next->cin),&cin,sizeof(cin)))
{
p=p->next; //循环后移
if(sendto(sfd,&msg,sizeof(msg_t),0,(struct sockaddr*)(&p->cin),sizeof(p->cin))==-1)
{
perror("sendto error");
return -1;
}
}
else
{
node_t *s;
s=p->next;
p->next=s->next;
s->next=NULL;
free(s);
s=NULL;
}
}
return 0;
}
Cli.c
#include<myhead.h>
#define SER_IP "192.168.159.148"
#define SER_PORT 6666
//因为客户端发送给服务器的消息是不同类型,所以定义结构体比较方便
typedef struct msg_TYPE
{
char type; // L:登录 C:聊天 Q:退出
char usrName[20]; //用户名
char msgText[1024]; //消息正文
}msg_t;
int main(int argc, const char *argv[])
{
//判断终端输入是否合理
if(argc!=3)
{
printf("input file error\n");
printf("usage:./a.out IP port\n");
return -1;
}
//1.创建用于通信的套接字
int sfd=socket(AF_INET,SOCK_DGRAM,0);
if(sfd==-1)
{
perror("socket error");
return -1;
}
//2.绑定服务器IP地址和端口
//2.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 socklen1=sizeof(sin);
//给服务器发送登录信息数据包
msg_t msg;
//清空数据
memset(&msg,0,sizeof(msg_t));
printf("请输入用户名:");
fgets(msg.usrName,20,stdin);
msg.usrName[strlen(msg.usrName)-1]=0;
strcpy(msg.msgText,"加入群聊");
if(sendto(sfd,&msg,sizeof(msg_t),0,(struct sockaddr*)&sin,socklen1)==-1)
{
perror("sendto error");
return -1;
}
//创建多进程,父进程用来发送系统消息,子进程用来接收数据并处理
pid_t pid=fork();
if(pid>0)
{
//父进程:发送系统消息
while(1)
{
msg.type='C';
fgets(msg.msgText,128,stdin);
msg.msgText[strlen(msg.msgText)-1]=0;
if(strcmp(msg.msgText,"quit")==0)
{
msg.type='Q';
strcpy(msg.msgText,"退出群聊");
}
if(sendto(sfd,&msg,sizeof(msg_t),0,(struct sockaddr*)&sin,socklen1)==-1)
{
perror("sendto error");
return -1;
}
if(strcmp(msg.msgText,"退出群聊")==0)
{
break;
}
}
}
else if(pid==0)
{
//子进程:用来接收数据并处理
while(1)
{
//清空数据
memset(&msg,0,sizeof(msg));
if((recvfrom(sfd,&msg,sizeof(msg_t),0,NULL,NULL))==-1)
{
perror("recvfrom error");
return -1;
}
printf("%s:[%s]\n",msg.usrName,msg.msgText);
}
}
else
{
perror("fork error");
return -1;
}
//给子进程发送一个退出信号
kill(pid,SIGKILL);
wait(NULL);
//关闭套接字
close(sfd);
return 0;
}