1.头文件
/*===============================================
* 文件名称:UDP.h
* 创 建 者:crx
* 创建日期:2023年09月3日
* 描 述:
================================================*/
#ifndef _UDP_H
#define _UDP_H
#include
#include /* See NOTES */
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef struct node{ //链表节点保存用户地址结构体信息
struct sockaddr_in caddr;
struct node *next;
}Node,*Pnode;
typedef struct mesg{ //用户状态、姓名、消息
char state;
char name[20];
char text[60];
}Mesg;
enum state{ //状态:登录、转发、下线
Login,
Relay,
Quit,
};
//创建头节点
Pnode create_node();
//插入,登录
int insert_node(Pnode p,struct sockaddr_in caddr,Mesg msg);
//转发
void relay(int sockfd,Pnode p,struct sockaddr_in caddr,Mesg msg);
//删除,下线
int delete_node(Pnode p,struct sockaddr_in caddr,Mesg msg);
#endif
2.服务器
/*===============================================
* 文件名称:UDPs.c
* 创 建 者:crx
* 创建日期:2023年09月3日
* 描 述:
================================================*/
#include "UDP.h"
int main(int argc, char *argv[])
{
//1.创建套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
//2.绑定
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8181);
saddr.sin_addr.s_addr = INADDR_ANY;
int bindfd = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(-1 == bindfd)
{
perror("bind");
return -1;
}
//3.准备保存客户端地址结构体,等待登录
printf("等待登录....\n");
struct sockaddr_in caddr;
socklen_t addrlen = sizeof(caddr);
Mesg msg;
//4.创建头节点
Pnode p = create_node();
Pnode q = p;
Pnode temp = p;
while(1)
{
memset(&caddr,0,sizeof(caddr));
//5.接收用户登录信息
recvfrom(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&caddr,&addrlen);
//6.根据用户状态执行对应操作
//msg.state = Login
//用户登录,链表插入用户信息
//判断链表是否为空,为空则插入用户
//链表不为空,判断是否是链表中已有用户,不是则插入用户信息
//转发登录消息给其他在线用户
if(msg.state == Login)
{
if(NULL == q->next)
{
insert_node(p,caddr,msg);
strcpy(msg.text,"已登录!");
}
else
{
temp = p->next;
while(temp)
{
if(temp->caddr.sin_port == caddr.sin_port && temp->caddr.sin_addr.s_addr == caddr.sin_addr.s_addr)
break;
else
temp = temp->next;
}
if(NULL == temp)
{
insert_node(p,caddr,msg);
strcpy(msg.text,"已登录!");
relay(sockfd,p,caddr,msg);
}
}
}
//msg.state = Relay
//转发用户信息给其他在线用户
if(msg.state == Relay)
{
relay(sockfd,p,caddr,msg);
}
//msg.state = Quit
//用户下线
//链表中删除用户信息
//转发用户下线信息给其他用户
if(msg.state == Quit)
{
delete_node(p,caddr,msg);
strcpy(msg.text,"已下线");
relay(sockfd,p,caddr,msg);
}
}
return 0;
}
//创建头节点
Pnode create_node()
{
Pnode p = (Pnode)malloc(sizeof(Node));
if(NULL == p)
{
perror("malloc");
return NULL;
}
p->next = NULL;
return p;
}
//插入,登录
int insert_node(Pnode p,struct sockaddr_in caddr,Mesg msg)
{
if(NULL == p)
{
return -1;
}
Pnode new = (Pnode)malloc(sizeof(Node));
if(NULL == new)
{
perror("malloc");
return -2;
}
new->next = p->next;
p->next = new;
new->caddr = caddr;
printf("已登录!name:%s,ip:%s,port:%d\n",msg.name,inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
return 1;
}
//转发
void relay(int sockfd,Pnode p,struct sockaddr_in caddr,Mesg msg) {
while(p->next)
{
p = p->next;
if(p->caddr.sin_port == caddr.sin_port && p->caddr.sin_addr.s_addr == caddr.sin_addr.s_addr)
{
continue;
}
else
{
sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&(p->caddr),sizeof(p->caddr));
}
}
}
//删除,下线
int delete_node(Pnode p,struct sockaddr_in caddr,Mesg msg)
{
if(NULL == p)
{
return -1;
}
while(p->next)
{
if(memcmp(&(p->next->caddr),&caddr,sizeof(caddr)) == 0)
{
Pnode q = p->next;
p->next = q->next;
free(q);
break;
}
else
{
p = p->next;
}
}
printf("已下线!name:%s,ip:%s,port:%d\n",msg.name,inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
}
3.客户端
/*===============================================
* 文件名称:UDPc.c
* 创 建 者:crx
* 创建日期:2023年09月3日
* 描 述:
================================================*/
#include "UDP.h"
int main(int argc, char *argv[])
{
//1.创建套接字
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
//2.服务器地址
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8181);
saddr.sin_addr.s_addr = inet_addr("192.168.17.225");
//3.创建用户
Mesg msg;
//4.运行后触发msg.state = Login
//登录填写用户名
//发送给服务器登录信息转发登录消息操作
printf("登录\n");
msg.state = Login;
printf("请输入用户名:\n");
fgets(msg.name,20,stdin);
printf("******************************************\n");
if(msg.name[strlen(msg.name)-1] == '\n')
msg.name[strlen(msg.name) -1] = '\0';
//发送用户登录消息
if(-1 == sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&saddr,sizeof(saddr)))
{
perror("sendto");
return -1;
}
//5.创建子进程
pid_t pid = fork();
//6.子进程循环接收其他用户消息并打印发送人及信息
if(pid == 0)
{
while(1)
{
socklen_t addrlen = sizeof(saddr);
recvfrom(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&saddr,&addrlen);
printf("~%s~ : %s\n",msg.name,msg.text);
}
}
else
{
//7.父进程获取用户终端输入到用户消息中
while(1)
{
fgets(msg.text,sizeof(msg.text),stdin);
if(msg.text[strlen(msg.text)-1] == '\n')
msg.text[strlen(msg.text) -1] = '\0';
//8.处理终端输入
//终端输入Quit则触发msg.state = Quit
//发送给服务器下线信息执行对应操作
//使用SIGKILL强制结束进程
if(strcmp(msg.text,"Quit") == 0)
{
msg.state = Quit;
if(-1 == sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&saddr,sizeof(saddr)))
{
perror("sendto");
return -1;
}
kill(pid,SIGKILL);
wait(NULL);
exit(0);
}
//终端输入不是Quit则触发msg.state = Relay
//发送给服务端执行转发操作
else
{
msg.state = Relay;
}
if(-1 == sendto(sockfd,&msg,sizeof(msg),0,(struct sockaddr *)&saddr,sizeof(saddr)))
{
perror("sendto");
return -1;
}
}
}
close(sockfd);
return 0;
}
4.makefile
all:UDPs UDPc
UDPs:UDPs.c
gcc UDPs.c -o UDPs
UDPc:UDPc.c
gcc UDPc.c -o UDPc
clean:
rm UDPs UDPc
5.结果