1.头文件
/*===============================================
* 文件名称:UDP.h
* 创 建 者:crx
* 创建日期:2023年09月3日
* 描 述:
================================================*/
#ifndef _UDP_H
#define _UDP_H#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>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; elsetemp = 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.cgcc UDPs.c -o UDPs
UDPc:UDPc.cgcc UDPc.c -o UDPc
clean:rm UDPs UDPc
5.结果