一、基于UDP的网络聊天室
项目需求:
- 如果有用户登录,其他用户可以收到这个人的登录信息
- 如果有人发送信息,其他用户可以收到这个人的群聊信息
- 如果有人下线,其他用户可以收到这个人的下线信息
- 服务器可以发送系统信息。
服务器代码(使用select实现IO多路复用):
#include<myhead.h>
#define SER_IP "192.168.32.132"
#define SER_PORT 9999
typedef struct Node//存储客户端名称和地址的链表
{
char usrName[20];
struct sockaddr_in cin;
struct Node* next;
}clichat;
struct magTyp
{
char type;
char usrName[20];
char msgText[1024];
}clitxt;
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;
}
//定义服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(SER_PORT);
sin.sin_addr.s_addr=inet_addr(SER_IP);
//绑定
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//创建用于存储发来信息客户端地址的结构体数组
struct sockaddr_in cin1;
socklen_t cinlen=sizeof(cin1);
//创建用于存储文件描述符的数组容器,供select使用
fd_set readfds,tempfds;
FD_ZERO(&readfds);
FD_SET(0,&readfds);//标准输入文件描述符放入
FD_SET(sfd,&readfds);//套接字文件描述符放入
int maxfd=sfd;//存放最大的文件描述符
char rbuf[128]="";
clichat *head=NULL;
while(1)
{
tempfds=readfds;
int res=select(maxfd+1,&tempfds,NULL,NULL,NULL);
if(res==-1)
{
perror("select error");
return -1;
}else if(res==0)
{
puts("time out");
return -1;
}
if(FD_ISSET(0,&tempfds))//终端输入发生
{
bzero(rbuf,sizeof(rbuf));
fgets(rbuf,sizeof(rbuf),stdin);
rbuf[strlen(rbuf)-1]=0;
clichat *p=head;
while(p)
{
sendto(sfd,rbuf,strlen(rbuf),0,(struct sockaddr*)&(p->cin),cinlen);
p=p->next;
}
// puts("发送成功");
}
if(FD_ISSET(sfd,&tempfds))//客户端发来消息
{
bzero(rbuf,sizeof(rbuf));
recvfrom(sfd,&clitxt,sizeof(clitxt),0,(struct sockaddr*)&cin1,&cinlen);
if(clitxt.type=='L')
{
bzero(rbuf,sizeof(rbuf));
//创建节点存储新的客户端信息
clichat *s=(clichat*)malloc(sizeof(struct Node));
strcpy(s->usrName,clitxt.usrName);//保存用户名
strcpy(rbuf,clitxt.usrName);//存入字符数组用于发送消息
s->cin=cin1;//保存客户端地址信息
clichat *p=head;
strcat(rbuf,":已上线");
puts(rbuf);
while(p)//通知其他用户
{
sendto(sfd,rbuf,strlen(rbuf),0,(struct sockaddr*)&(p->cin),cinlen);
p=p->next;
}
s->next=head;
head=s;
}else if(clitxt.type=='C')
{
bzero(rbuf,sizeof(rbuf));
strcpy(rbuf,clitxt.usrName);
strcat(rbuf,":");
strcat(rbuf,clitxt.msgText);
printf("%s\n",rbuf);
clichat *p=head;
while(p)//转发给除了发送者的其他在线的客户端
{
if(p->cin.sin_port!=cin1.sin_port)
{
sendto(sfd,rbuf,strlen(rbuf),0,(struct sockaddr*)&(p->cin),cinlen);
}
p=p->next;
}
}else if(clitxt.type=='Q')
{
bzero(rbuf,sizeof(rbuf));
strcpy(rbuf,clitxt.usrName);
strcat(rbuf,":已下线");
puts(rbuf);
clichat *p=head;
if(head->cin.sin_port==cin1.sin_port)//删除链表节点
{
head=head->next;
free(p);p=NULL;
}else
{
while(p->next->cin.sin_port!=cin1.sin_port)
{
p=p->next;
}
clichat *del=p->next;
p->next=del->next;
free(del);del=NULL;
}
p=head;
while(p)//发送给其他用户
{
sendto(sfd,rbuf,strlen(rbuf),0,(struct sockaddr*)&(p->cin),cinlen);
p=p->next;
}
}
}
}
close(sfd);
return 0;
}
客户端代码(使用poll实现IO多路复用):
#include<myhead.h>
#define SER_IP "192.168.32.132"
#define SER_PORT 9999
struct magTyp
{
char type;
char usrName[20];
char msgText[1024];
}clitxt;
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;
}
//定义服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(SER_PORT);
sin.sin_addr.s_addr=inet_addr(SER_IP);
socklen_t sinlen=sizeof(sin);
//第一次连接服务器 发送上线消息
char name[20]="";
puts("请输入用户名>>>:");
fgets(name,sizeof(name),stdin);
name[strlen(name)-1]=0;
clitxt.type='L';
strcpy(clitxt.usrName,name);
sendto(cfd,&clitxt,sizeof(clitxt),0,(struct sockaddr*)&sin,sinlen);
puts("已成功发送上线消息");
//创建用于存储文件描述符的数组容器 供poll使用
struct pollfd pfd[2];
pfd[0].fd=0;
pfd[0].events=POLLIN;
pfd[1].fd=cfd;
pfd[1].events=POLLIN;//检测终端输入和cfd操作
char buf[128]="";//数据收发
while(1)
{
int res=poll(pfd,2,-1);
if(res==-1)
{
perror("poll error");
return -1;
}else if(poll==0)
{
printf("time out\n");
return -1;
}
//发生标准输入事件
if(pfd[0].revents==POLLIN)
{
bzero(buf,sizeof(buf));
//printf("请输入>>>:");
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]=0;
//数据存入结构体发送给客户端
strcpy(clitxt.msgText,buf);
if(strcmp(buf,"quit")==0)
{
clitxt.type='Q';
sendto(cfd,&clitxt,sizeof(clitxt),0,(struct sockaddr*)&sin,sinlen);
puts("发送成功");
break;
}
clitxt.type='C';
sendto(cfd,&clitxt,sizeof(clitxt),0,(struct sockaddr*)&sin,sinlen);
// puts("发送成功");
}
//服务器发来消息触发事件
if(pfd[1].revents==POLLIN)
{
bzero(buf,sizeof(buf));
recvfrom(cfd,buf,sizeof(buf),0,NULL,NULL);
printf("%s\n",buf);
}
}
close(cfd);
return 0;
}
运行效果:
思维导图:
模拟面试: