1.思维导图
2.实现简单UDP聊天室
服务器(用通用的客户端无法检测用户是否退出;因为使用链表,所以长了点)
#include <myhead.h>
//服务器地址信息
#define SER_PORT 8888
#define SER_IP "192.168.122.41"
enum{FALSE=-1,SUCCESS};
//创建节点结构体
typedef struct Node
{
//数据域:存储数据元素
struct sockaddr_in cin;
char name[12];
//指针域:下一个节点的地址
struct Node *next;
}*Linklist;
//创建链表函数
Linklist create_node()
{
Linklist p=(Linklist)malloc(sizeof(struct Node));
if(NULL ==p)
return NULL;
//对p的数据域和指针域进行初始化
p->next=NULL;
return p;
}
//创建链表头插函数
Linklist insert_head(Linklist head,struct sockaddr_in cin,const char* name)
{
//创建新节点s
Linklist s=create_node();
if(NULL ==s)
return head;
s->cin=cin;
strcpy(s->name,name);
//s的指针域
s->next=head;
head=s;
return head;
}
//创建链表头删函数
Linklist delete_head(Linklist head)
{
//1.判断链表是否为空格
if(NULL ==head)
return head;
//2,存在节点 >=1
Linklist del=head;
head=head->next;
// free(del);
del=NULL;
return head;
}
//创建计算链表长度函数
int length(Linklist head)
{
Linklist p=head;
int count=0;
while(p!=NULL)
{
p=p->next;
count++;
}
return count;
}
//创建链表按位置删除函数
Linklist delete_pos(Linklist head ,int pos)
{
//1,判断链表是否为空
//2,判断位置是否合法
if(NULL ==head || pos<1 ||pos>length(head))
{
return head;
}
//3,判断第一个位置
if(pos==1)
{
head=delete_head(head);
return head;
}
//4,删除其他位置
//先找到pos-1位置
Linklist p=head;
for(int i=1;i<pos-1;i++)
{
p=p->next;
}
//删除p后继
Linklist del=p->next;
p->next=del->next;
free(del);del=NULL;
return head;
}
//创建链表按元素查找函数
int search_data(Linklist head,struct sockaddr_in cin)
{
//1,判断链表是否为空
if(NULL==head)
return FALSE;
//2,循环查找
Linklist p=head;
int count=0;
while(p!=NULL)
{
count++;
if(p->cin.sin_port==cin.sin_port&&strcmp(inet_ntoa(p->cin.sin_addr),inet_ntoa(cin.sin_addr))==0)
return count;
p=p->next;
}
return FALSE;
}
//创建链表按元素删除函数
Linklist delete_data(Linklist head,struct sockaddr_in cin)
{
//1,根据key查找pos
int pos=search_data(head,cin);
if(pos==FALSE)
return head;
//2,根据pos删除
head=delete_pos(head,pos);
return head;
}
int main(int argc, const char *argv[])
{
//创建链表
Linklist head=NULL;
//创建用于链接的套接字
int sfd=socket(AF_INET,SOCK_DGRAM,0);
if(sfd==-1)
{
perror("socket error");
return -1;
}
//快速重用端口号
int resue=-1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&resue,sizeof(resue))==-1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");
//服务器地址信息结构体
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("绑定成功\n");
//接收客户端地址信息结构体
struct sockaddr_in cin;
socklen_t strlen = sizeof(cin);
//消息容器
char buf[128]="";
char wbuf[128]="";
//第一次链接标识符
int flag=0;
while(1)
{
flag=0;
bzero(buf,sizeof(buf));
int res=recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&cin,&strlen);
Linklist p=head;
//判断是否第一次进入聊天群
while(p!=NULL)
{
if(p->cin.sin_port==cin.sin_port&&strcmp(inet_ntoa(p->cin.sin_addr),inet_ntoa(cin.sin_addr))==0)
{
flag=1;
break;
}
p=p->next;
}
if(flag==0)
{
p=head;
while(p!=NULL)
{
sprintf(wbuf,"[%s:%d]%s%s\n",inet_ntoa(cin.sin_addr,ntohs(cin.sin_port),buf,"加入聊天群");
sendto(sfd,wbuf,sizeof(wbuf),0,(struct sockaddr*)&p->cin,sizeof(p->cin));
p=p->next;
}
head=insert_head(head,cin,buf);
continue;
}
//检索当前所发消息用户信息
Linklist q=head;
while(q!=NULL)
{
if(q->cin.sin_port==cin.sin_port&&strcmp(inet_ntoa(q->cin.sin_addr),inet_ntoa(cin.sin_addr))==0)
{
break;
}
q=q->next;
}
//与客户端配合,判断客户端退出聊天群
if(strcmp(buf,"quit")==0)
{
delete_data(head,cin);
p=head;
while(p!=NULL)
{
sprintf(wbuf,"[%s:%d]%s%s\n",inet_ntoa(p->cin.sin_addr),ntohs(cin.sin_port),q->name,"退出聊天群");
sendto(sfd,wbuf,sizeof(wbuf),0,(struct sockaddr*)&p->cin,sizeof(p->cin));
p=p->next;
}
continue;
}
//服务器转发
p=head;
while(p!=NULL)
{
if(p->cin.sin_port==cin.sin_port&&strcmp(inet_ntoa(p->cin.sin_addr),inet_ntoa(cin.sin_addr))==0)
{
p=p->next;
continue;
}
sprintf(wbuf,"[%s:%d]%s:%s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),q->name,buf);
sendto(sfd,wbuf,sizeof(wbuf),0,(struct sockaddr*)&p->cin,sizeof(p->cin));
p=p->next;
}
}
//关闭套接字
close(sfd);
return 0;
}
客户端(加了quit退出语句)
#include <myhead.h>
#define SER_PORT 8888
#define SER_IP "192.168.122.41"
#define CLI_PORT 6666
#define CLI_IP "192.168.122.41"
int main(int argc, const char *argv[])
{
//创建用于链接的套接字
int cfd=socket(AF_INET,SOCK_DGRAM,0);
if(cfd==-1)
{
perror("socket error");
return -1;
}
struct sockaddr_in cin;
/* cin.sin_family = AF_INET;
cin.sin_port = htons(CLI_PORT);
cin.sin_addr.s_addr = inet_addr(CLI_IP);
if(bind(cfd,(struct sockaddr*)&cin,sizeof(cin))==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");*/
//服务器地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(SER_IP);
//准备文件描述符容器
fd_set readfds,tempfds;
FD_ZERO(&readfds);
//将要检测的文件描述符放入集合
FD_SET(0,&readfds);
FD_SET(cfd,&readfds);
char buf[128]="";
int flag = 0;
printf("%s\n","请输入登录昵称");
while(1)
{
tempfds = readfds;
//使用select函数对容器中的文件描述符进行检测
int res = select(cfd+1,&tempfds,NULL,NULL,NULL);
if(res==-1)
{
perror("select error");
return -1;
}else if(res ==0)
{
printf("timeout");
return -1;
}
//判断0号描述符在集合中
if(FD_ISSET(0,&tempfds))
{
bzero(buf,sizeof(buf));
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf)-1]='\0';
sendto(cfd,buf,sizeof(buf),0,(struct sockaddr*)&sin,sizeof(sin));
if(strcmp(buf,"quit")==0)
{
break;
}
}
if(FD_ISSET(cfd,&tempfds))
{
recvfrom(cfd,buf,sizeof(buf),0,NULL,NULL);
printf("%s\n",buf);
}
}
//关闭套接字
close(cfd);
return 0;
}