网络聊天室UDP实现
服务器端:
头文件:
cs
#include <myhead.h>
//定义客户信息结构体
typedef struct magtye
{
char type; //消息类型
char name[100]; //客户姓名
char text[1024]; //客户发送聊天信息
}msg_t;
//定义结构体存储每个客户端的ip地址和端口号
typedef struct IP_PORT
{
struct sockaddr_in cin;//地址信息
struct IP_PORT *next;//
}*addrlist;
void usr_login(int sfd,msg_t msg,addrlist *head,struct sockaddr_in cin);
void usr_chat(int sfd,msg_t msg,addrlist head,struct sockaddr_in cin);
void usr_quit(int sfd,msg_t msg,addrlist *head,struct sockaddr_in cin);
主函数:
cs
#include"head.h"
int main(int argc, const char *argv[])
{
//创建套接字
int sfd=-1;
sfd=socket(AF_INET,SOCK_DGRAM, 0);
if(sfd==-1)
{
perror("socket error");
return -1;
}
//将端口号快速重用
int reuse=1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))==-1)
{
perror("setsockopt error");
return -1;
}
//服务器进行绑定
//(1)、从终端获取端口号和地址
char SER_IP[100];
int SER_PORT;
printf("请输入服务器ip地址和端口号:");
scanf("%s %d",SER_IP,&SER_PORT);
getchar();//吸收垃圾字符
//(2)、填充服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family=AF_INET; //地址族
sin.sin_port=htons(SER_PORT); //端口号
sin.sin_addr.s_addr=inet_addr(SER_IP); //IP地址
socklen_t sin_len=sizeof(sin);
//(3)、绑定
if(bind(sfd,(struct sockaddr*)&sin,sin_len)==-1)
{
perror("bind error");
return -1;
}
//定义客户端网络信息结构体
struct sockaddr_in cin;
socklen_t cin_len=sizeof(cin);
msg_t msg;//定义客户发送消息的机构体变量
//创建父子进程处理不同的操作
int pid=fork();
if(pid==-1)
{
perror("fork error");
return -1;
}
if(pid==0)//字进程实现服务器接收消息类型
{
addrlist head=NULL;//链表头指针;
while(1)
{
memset(&msg,0,sizeof(msg));
memset(&cin,0,sizeof(cin));
//接收客户端发来的信息,并判断属于哪种消息类型
recvfrom(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&cin,&cin_len);
switch(msg.type)
{
case 'L': //该消息类型代表新用户上线
{
//创建新节点保存新用户的ip地址和端口号
//向其他在线的用户发送新用户上线通知
usr_login(sfd,msg,&head,cin);
}break;
case 'C': //该消息类型代表一个用户发送消息给其他用户
{
usr_chat(sfd,msg,head,cin);
}break;
case 'Q': //该消息类型代表一个用户下线
{
usr_quit(sfd,msg,&head,cin);
}break;
}
}
}
else if(pid>0)//父进程实现服务器对客户端发送消息
{
strcpy(msg.name,"服务器消息");
msg.type='C';
while(1)
{
memset(msg.text,0,sizeof(msg.text));
fgets(msg.text,sizeof(msg.text),stdin);
msg.text[strlen(msg.text)-1]='\0';
sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sin_len);
if(strcmp(msg.text,"服务器下线")==0)
{
sleep(1);
break;
}
}
kill(pid,SIGKILL);//服务器下线杀死子进程
}
wait(NULL);
//关闭套接字
close(sfd);
return 0;
}
自定义函数:
cs
#include"head.h"
//用户登录操作函数
void usr_login(int sfd,msg_t msg,addrlist *head,struct sockaddr_in cin)
{
//1、创建新节点
addrlist s=(addrlist)malloc(sizeof(addrlist));
s->next=NULL;
//存储新用户的ip地址和端口号
printf("%s : %d\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port));
s->cin=cin;
s->next=*head;
*head=s;
//2、遍历链表将新用户上线消息发送给其他在线用户
addrlist p=*head;
while (p!=NULL)
{
if(p->cin.sin_port!=cin.sin_port)
{
sendto(sfd, &msg, sizeof(msg),0,(struct sockaddr *)&(p->cin), sizeof(p->cin));
}
p=p->next; //后移
}
printf("%s:%s\n",msg.name, msg.text);
}
//用户聊天操作函数
void usr_chat(int sfd,msg_t msg,addrlist head,struct sockaddr_in cin)
{
addrlist p=head;
while (p!=NULL)
{
if(p->cin.sin_port!=cin.sin_port)
{
sendto(sfd, &msg, sizeof(msg),0,(struct sockaddr *)&(p->cin), sizeof(p->cin));
}
p=p->next; //后移
}
}
//用户退出操作函数
void usr_quit(int sfd,msg_t msg,addrlist *head,struct sockaddr_in cin)
{
printf("%s:%s\n",msg.name, msg.text);
addrlist p=*head;
addrlist del=NULL;
while (p!=NULL)
{
if(p->cin.sin_port!=cin.sin_port) //向其他用户发送某个用户下线消息
{
sendto(sfd, &msg, sizeof(msg),0,(struct sockaddr *)&(p->cin), sizeof(p->cin));
del=p;
p=p->next;
}
else
{
sendto(sfd, &msg, sizeof(msg),0,(struct sockaddr *)&(p->cin), sizeof(p->cin));//向发出下线消息的用户回复消息
if (del==NULL)
{
*head=p->next;
}
else
{
del->next=p->next;
}
free(del);
del=NULL;
break;
}
}
}
客户端:
cs
#include <myhead.h>
//定义客户信息结构体
typedef struct magtye
{
char type; //消息类型
char name[100]; //客户姓名
char text[1024]; //客户发送聊天信息
}msg_t;
int main(int argc, const char *argv[])
{
//创建套接字
int cfd=-1;
cfd=socket(AF_INET, SOCK_DGRAM, 0);
if(cfd==-1)
{
perror("socket error");
return -1;
}
//将端口号快速重用
int reuse=1;
if(setsockopt(cfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))==-1)
{
perror("setsockopt error");
return -1;
}
//(1)、从终端获取端口号和地址
char SER_IP[100];
int SER_PORT;
printf("请输入服务器ip地址和端口号:");
scanf("%s %d",SER_IP,&SER_PORT);
getchar();//吸收垃圾字符
//(2)、填充服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family=AF_INET; //地址族
sin.sin_port=htons(SER_PORT); //端口号
sin.sin_addr.s_addr=inet_addr(SER_IP); //IP地址
socklen_t sin_len=sizeof(sin);
msg_t msg;
//客户端上线发送消息
printf("请输入用户名:");
fgets(msg.name,sizeof(msg.name),stdin);
msg.name[strlen(msg.name)-1] = '\0';
strcpy(msg.text,"已上线");
msg.type='L';
sendto(cfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sin_len);
//创建多进程进行收发消息操作
int pid=fork();
if(pid==-1)
{
perror("fork error");
return -1;
}
if(pid>0)//父进程进行读取消息
{
while(1)
{
recvfrom(cfd,&msg,sizeof(msg),0,NULL,NULL);
if(strcmp(msg.text,"退出群聊")==0) //用户自己下线
{
break;
}
printf("[%s]: %s\n", msg.name, msg.text);
if(strcmp(msg.text,"服务器下线")==0) //服务器让客户端下线
{
kill(pid,SIGKILL);
break;
}
}
}
else if(pid==0)//子进程发送消息
{
while(1)
{
memset(msg.text,0,sizeof(msg.text));
fgets(msg.text,sizeof(msg.text),stdin);//在终端获取聊天信息
msg.text[strlen(msg.text)-1]='\0';
if(strcmp(msg.text, "下线")==0)
{
msg.type='Q';
strcpy(msg.text, "退出群聊");
}
else
{
msg.type='C';
}
sendto(cfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sin_len);
if(strcmp(msg.text,"退出群聊")==0)
{
break;
}
}
exit(EXIT_SUCCESS);
}
//关闭套接字回收子进程资源
wait(NULL); //阻塞回收子进程资源
close(cfd);
return 0;
}
思维导图: