基于UDP的网络聊天室
服务器Ser
#include <myhead.h>
#define PORT 6666 //端口号:1024~49151
#define IP "127.0.0.1" //本地环回IP
struct msg //协议包
{
char type; //存储包的类型
char name[20]; //包的名字
char text[128]; //数据包
};
//回收僵尸进程
void handler(int sig)
{
while(waitpid(-1,NULL,WNOHANG)>0);
}
int main(int argc, const char *argv[])
{
//捕获17)SIGCHID信号,为信号注册新的处理函数
if(signal(17,handler)==SIG_ERR)
{
ERR_MSG("signal");
return -1;
}
//创建报式套接字 socket
int sfd=socket(AF_INET,SOCK_DGRAM,0);
if(sfd<0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success sfd=%d\n",sfd);
//填服务器自身的地址信息结构体,真实的地址信息结构体根据地址族指定
//AF_INET : man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET
sin.sin_port = htons(PORT); //端口号:1024~49151
sin.sin_addr.s_addr = inet_addr(IP); //服务器IP 127.0.0.1
//绑定服务器的地址信息--->必须绑定 bind
if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
struct sockaddr_in ch[1024]={}; //存储客户端地址信息结构体的数组
struct sockaddr_in empty={}; //空变量
struct sockaddr_in cin; //存储客户端s的地址信息变量
socklen_t addrlen=sizeof(cin); //真实的地址信息结构体的大小
ssize_t res=0;
//创建进程
pid_t pid;
pid =fork();
if(0==pid)
{
//子进程
struct msg system;
while(1)
{
//从终端获取数据 封装群聊包
bzero(system.text,sizeof(system.text));
fgets(system.text,sizeof(system.text),stdin);
system.text[strlen(system.text)-1]='\0';
system.type='C';
strcpy(system.name,"system");
//发送数据到服务器的父进程中通过父进程的群聊请求转发给其他客户端
res=sendto(sfd,&system,sizeof(system),0,(struct sockaddr *)&sin,sizeof(sin));
if(res<0)
{
ERR_MSG("sendto");
return -1;
}
printf("system sendto success\n");
}
exit(0);
}else if(pid>0)
{
//父进程
struct msg system;
while(1)
{
//接收信息包
recvfrom(sfd,&system,sizeof(system),0,(struct sockaddr *)&cin,&addrlen);
//判断信息包的类型
if('L'==system.type)
{
//登录请求
//把新登入的客户端的姓名发给其他已经在线的客户端
for(int j=0;j<1024;j++)
{
if(memcmp(&empty,&ch[j],sizeof(empty))!=0)
{
sendto(sfd,&system,sizeof(system),0,(struct sockaddr *)&ch[j],sizeof(ch[j]));
}
}
//吧新登入的客户端的地址信息结构体存到数组中
for(int i=0;i<1024;i++)
{
if(memcmp(&empty,&ch[i],sizeof(empty))==0)
{
ch[i]=cin;
// printf("1\n");
break;
}
}
printf("[%s:%d]: >>>%s<<<登入成功\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),system.name);
//printf("已上线\n");
}
else if('C'==system.type)
{
//群聊请求
for(int i=0;i<1024;i++)
{
if((memcmp(&empty,&ch[i],sizeof(empty))!=0)&&(memcmp(&cin,&ch[i],sizeof(cin))!=0))
{
sendto(sfd,&system,sizeof(system),0,(struct sockaddr *)&ch[i],sizeof(ch[i]));
}
}
printf("[%s:%d]:chat成功\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
}
else if('Q'==system.type)
{
//下线请求
for(int i=0;i<1024;i++)
{
if((memcmp(&empty,&ch[i],sizeof(empty))!=0)&&(memcmp(&cin,&ch[i],sizeof(cin))!=0)) //转发给除了自己的其他在线用户
{
sendto(sfd,&system,sizeof(system),0,(struct sockaddr *)&ch[i],sizeof(ch[i]));
}
if(memcmp(&cin,&ch[i],sizeof(cin))==0)
{
ch[i]=empty; //从数组中删除下线的客户端的地址信息结构体数据
}
}
printf("[%s:%d]:>>>%s<<<下线\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),system.name);
}
}
}
else
{
ERR_MSG("fork");
return -1;
}
//关闭文件描述符
if(close(sfd)<0)
{
ERR_MSG("close");
return -1;
}
return 0;
}
客户端Cli
#include <myhead.h>
#define PORT 6666 //端口号:1024~49151
#define IP "127.0.0.1" //本地环回IP
struct msg //协议包
{
char type; //存储包的类型
char name[20]; //包的名字
char text[128]; //数据包
};
//回收僵尸进程
void handler(int sig)
{
while(waitpid(-1,NULL,WNOHANG)>0);
}
int main(int argc, const char *argv[])
{
/*
//捕获17)SIGCHID信号,为信号注册新的处理函数
if(signal(17,handler)==SIG_ERR)
{
ERR_MSG("signal");
return -1;
}
*/
//创建报式套接字 socket
int cfd=socket(AF_INET,SOCK_DGRAM,0);
if(cfd<0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success cfd=%d\n",cfd);
//填服务器自身的地址信息结构体,真实的地址信息结构体根据地址族指定
//AF_INET : man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET; //必须填AF_INET
sin.sin_port = htons(PORT); //端口号:1024~49151
sin.sin_addr.s_addr = inet_addr(IP); //服务器IP 127.0.0.1
//登入
printf(">>> 请输入您的网名:");
//定义存储自己地址信息结构体的变量
struct sockaddr_in cin;
//定义存储收到的地址信息结构体变量
struct sockaddr_in rev;
socklen_t addrlen=sizeof(rev);
//封装登入协议包
struct msg clitem;
clitem.type='L';
fgets(clitem.name,sizeof(clitem.name),stdin);
clitem.name[strlen(clitem.name)-1]=0;
strcpy(clitem.text,"上线");
//发送登入包给服务器
ssize_t res=0;
res=sendto(cfd,&clitem,sizeof(clitem),0,(struct sockaddr *)&sin,sizeof(sin));
if(res<0)
{
ERR_MSG("sendto");
return -1;
}
printf("您成功登入\n");
//创建进程
pid_t pid;
pid=fork();
if(0==pid)
{
//子线程
//封装协议包
struct msg clisend;
clisend.text[strlen(clisend.text)-1]=0;
strcpy(clisend.name,clitem.name);
while(1)
{
fgets(clisend.text,sizeof(clisend.text),stdin);
clisend.text[strlen(clisend.text)-1]='\0';
if(strcmp(clisend.text,"quit")==0)
{
//发送下线请求给服务器
clisend.type='Q';
res=sendto(cfd,&clisend,sizeof(clisend),0,(struct sockaddr *)&sin,sizeof(sin));
if(res<0)
{
ERR_MSG("sendto");
return -1;
}
kill(getppid(),SIGKILL);
exit(0);
}
else
{
//发送聊天数据包
clisend.type='C';
res=sendto(cfd,&clisend,sizeof(clisend),0,(struct sockaddr *)&sin,sizeof(sin));
if(res<0)
{
ERR_MSG("sendto");
return -1;
}
}
}
}
else if(pid>0)
{
//父进程
//封装协议包
struct msg clirecv;
while(1)
{
res=recvfrom(cfd,&clirecv,sizeof(clirecv),0,(struct sockaddr *)&rev,&addrlen);
if(res<0)
{
ERR_MSG("recvfrom");
return -1;
}
if('C'==clirecv.type)
{
printf("[%s] %s:%s\n",inet_ntoa(rev.sin_addr),clirecv.name,clirecv.text);
}
else if('L'==clirecv.type)
{
printf("[%s:%d] %s上线了\n",inet_ntoa(rev.sin_addr),ntohs(rev.sin_port),clirecv.name);
}
else if('Q'==clirecv.type)
{
printf("[%s:%d] %s下线了\n",inet_ntoa(rev.sin_addr),ntohs(rev.sin_port),clirecv.name);
}
else
{
printf("信息表出错\n");
//break;
}
}
}
else
{
ERR_MSG("fork");
return -1;
}
//关闭文件描述符
if(close(cfd)<0)
{
ERR_MSG("close");
return -1;
}
return 0;
}