思维导图
基于UDP的简易网络聊天室
服务器:
#include <myhead.h>
#define SER_PORT 8888
struct msgTyp //存储消息的结构体
{
char type; //消息类型
char name[30]; //用户姓名
char text[1024]; //消息正文
};
//创建链表存储客户端信息
typedef struct node
{
//数据域
struct sockaddr_in cin;
//指针域,存储下一个节点的地址
struct node *next;
}*LinkList, Node;
//创建链表节点
LinkList Create()
{
LinkList s=(LinkList)malloc(sizeof(Node));
if(s==NULL)
return NULL;
//创建成功
memset(&s->cin,0,sizeof(s->cin));
s->next=NULL;//表示创建新节点指针域为空
return s;
}
int do_login(struct msgTyp msg, int sfd, struct sockaddr_in cin, LinkList head)
{
//转发登录信息
LinkList p = head;
while(p != NULL)
{
sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(p->cin), sizeof(p->cin));
p = p->next;
}
//将当前登录的用户的地址信息结构体存储起来
LinkList s = Create();
//新节点数据域即为它的地址信息结构体
s->cin = cin;
s->next=head->next;
head->next=s;
return 0;
}
int do_chat(LinkList head, int sfd, struct msgTyp msg, struct sockaddr_in cin)
{
//转发群聊信息
LinkList 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;
}
return 0;
}
int do_quit(LinkList head, int sfd, struct msgTyp msg, struct sockaddr_in cin)
{
//转发离线信息
LinkList p = head;
while(p->next != NULL)
{
if(p->cin.sin_port != cin.sin_port)
{
p = p->next;
sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(p->cin), sizeof(p->cin));
}
else //将离线的客户端的地址信息结构体从链表中删除
{
LinkList del = p->next;
p->next = del->next;
free(del);
del = NULL;
}
}
return 0;
}
int main(int argc, const char *argv[])
{
//判断是否外部传参
if(argc != 2){
printf("请输入IP地址!!!\n");
return -1;
}
//创建套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
perror("socket");
return -1;
}
printf("socket create success\n");
//填充服务器自身的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT); //服务器绑定的端口号
sin.sin_addr.s_addr = inet_addr(argv[1]); //服务器绑定的IP号
//绑定
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) ==-1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
char buf[128] = "";
int res = 0;
struct sockaddr_in cin; //存储对端地址信息结构体
socklen_t addrlen = sizeof(cin);
LinkList head = Create(); //创建链表头结点,存储客户端信息
struct msgTyp msg; //接收数据的结构体存储位置
pid_t pid = fork(); //创建子进程
if(pid > 0) //父进程
{
//接收数据
while(1)
{
//接收客户端的消息
res = recvfrom(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&cin, &addrlen);
printf("[%s] %s\n",msg.name,msg.text);
if(res < 0)
{
perror("recvfrom error");
return -1;
}
//根据消息的类型实行不同功能
switch(msg.type)
{
case 'L':
do_login(msg, sfd, cin, head);
break;
case 'C':
do_chat(head, sfd, msg, cin);
break;
case 'Q':
do_quit(head, sfd, msg, cin);
break;
default:
printf("error\n");break;
}
}
}
else if(0 == pid) //子进程
{
//服务器可以发送系统消息
msg.type = 'C';
strcpy(msg.name, "系统消息");
//发送数据
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, sizeof(sin));
}
}
else
{
perror("fork error");
return -1;
}
//关闭所有文件描述符
close(sfd);
return 0;
}
客户端:
#include <myhead.h>
#define SER_PORT 8888
struct msgTyp //消息结构体
{
char type; //消息类型
char name[30]; //用户姓名
char text[1024]; //消息正文
};
int main(int argc, const char *argv[])
{
//判断是否外部传参
if(argc!=2){
printf("请输入IP地址\n");
return -1;
}
//创建套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd ==-1)
{
perror("socket");
return -1;
}
printf("socket create success\n");
//填充服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(SER_PORT);
sin.sin_addr.s_addr = inet_addr(argv[1]);
//输入上线的用户名称
printf("请输入名字>>>");
char name[20] = "";
scanf("%s", name);
getchar();
//定义登录时的类型以及提示信息
struct msgTyp msg_snd;
msg_snd.type = 'L';
strcpy(msg_snd.name, name);
strcpy(msg_snd.text, "加入群聊");
//发送登录信息
if(sendto(sfd, &msg_snd, sizeof(msg_snd), 0, (struct sockaddr*)&sin, sizeof(sin)) ==-1)
{
perror("sendto error");
return -1;
}
char buf[128] = "";
int res = 0;
//存储对端地址信息结构体
struct sockaddr_in cin;
socklen_t addrlen = sizeof(cin);
//定义接收消息的结构体
struct msgTyp msg_rcv;
//创建子进程
pid_t pid = fork();
if(pid > 0) //父进程
{
//发送数据,发送聊天信息
while(1)
{
//客户端从终端获取消息并发送出去
bzero(buf, sizeof(buf)); //清空数组
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf)-1] = 0;
msg_snd.type = 'C';
strcpy(msg_snd.text, buf);
if(strcmp(msg_snd.text, "quit") == 0)
{
msg_snd.type = 'Q';
strcpy(msg_snd.text, "已下线");
}
sendto(sfd, &msg_snd, sizeof(msg_snd), 0, (struct sockaddr*)&sin, sizeof(sin));
if(strcmp(msg_snd.text, "已下线") == 0)
{
break;
}
}
kill(pid,SIGKILL);
wait(NULL);//等待回收子进程资源
}
else if(0 == pid) //子进程
{
//接收数据
while(1)
{
printf("\n");
recvfrom(sfd, &msg_rcv, sizeof(msg_rcv), 0, NULL, NULL);
printf("[%s] : %s", msg_rcv.name, msg_rcv.text);
}
}
else
{
perror("fork error");
return -1;
}
//关闭文件描述符
close(sfd);
return 0;
}
效果图: