项目需求:
- 如果有用户登录,其他用户可以收到这个人的登录信息
- 如果有人发送信息,其他用户可以收到这个人的群聊信息
- 如果有人下线,其他用户可以收到这个人的下线信息
- 服务器可以发送系统信息
zy.h
#ifndef UDP_H
#define UDP_H
#include<myhead.h>
#define N 128
#define M 32
typedef struct Node{
struct sockaddr_in sin;
struct Node *next;
}Node,*Nodeptr;
typedef struct Msg{
char code;
char user[M];
char text[N];
}msg_t;
#endif
服务端:ser.c
#include "zy.h"
void create(Nodeptr *q)
{
// 分配内存以创建新的链表节点
*q = (Nodeptr )malloc(sizeof(Node));
// 如果内存分配失败,则打印错误信息并退出函数
if(*q ==NULL)
{
printf("error\n");
return ;
}
// 将新节点的next指针设置为NULL,表示该节点目前是链表的最后一个节点
(*q)->next = NULL;
}
int insert_tail(Nodeptr q, struct sockaddr_in sin)
{
// 检查链表头指针是否为空,如果为空则无法插入节点
if(q == NULL)
{
printf("插入失败\n");
return -1;
}
// 定义并初始化
Nodeptr p = NULL;
create(&p);
p->sin = sin;
// 定义一个临时指针L,用于遍历链表
Nodeptr L = q;
// 遍历链表,直到找到最后一个节点
while (L->next != NULL)
{
L = L->next;
}
// 将新节点p链接到链表的尾部
L->next = p;
return 0;
}
int main(int argc, char const *argv[])
{
// 检查命令行参数数量,确保提供了IP和端口号
if(argc != 3)
{
printf("请输入IP和端口号\n");
return -1;
}
// 创建套接字
int sfd = socket(AF_INET,SOCK_DGRAM,0);
if(sfd == -1)
{
perror("error");
return -1;
}
// 初始化服务器地址结构
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
sin.sin_addr.s_addr = inet_addr(argv[1]);
// 绑定套接字
if(bind(sfd,(struct sockaddr *)&sin,sizeof(sin)) == -1)
{
perror("error bind");
return -1;
}
// 初始化客户端地址结构
struct sockaddr_in cin;
socklen_t len = sizeof(cin);
char name[32] = {0};
pid_t pid= 0;
// 客户端消息结构体
msg_t msg;
// 服务器消息结构体
msg_t msg_ser;
// 客户端节点指针,用于管理客户端列表
Nodeptr q = NULL;
// 初始化客户端列表
create(&q);
// 设置客户端列表的服务器地址
q->sin =cin;
// 创建子进程
pid = fork();
if(pid < 0)
{
perror("fork error");
return -1;
}else if (pid ==0)
{
// 子进程:接收并处理客户端消息
while (1)
{
bzero(&msg,sizeof(msg));
recvfrom(sfd,(void*)&msg,sizeof(msg),0,(struct sockaddr *)&cin,&len);
switch (msg.code)
{
case 'L':
{
printf("[%s]:玩家已上线\n",msg.user);
// 将新上线的客户端插入到列表中
insert_tail(q,cin);
// 遍历客户端列表,通知所有客户端有人上线
Nodeptr p1 =q->next;
while (p1 !=NULL)
{
msg.code = 'd';
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr *)&p1->sin,sizeof(p1->sin)) == -1)
{
perror("error send");
return -1;
}
p1=p1->next;
}
}
break;
case 'C':
{
// 遍历客户端列表,向所有客户端发送消息
Nodeptr p2 = q->next;
while(p2 != NULL){
msg.code='q';
if(sendto(sfd,(void *)&msg,sizeof(msg),0,(struct sockaddr *)&p2->sin,sizeof(p2->sin)) == -1){
perror("send error");
}
p2=p2->next;
}
}
break;
case 'Q':
// 接收客户端退出消息,并从列表中移除该客户端
printf("[%s]:退出了...\n", msg.user);
Nodeptr p3 = q;
Nodeptr h = NULL;
while(p3->next != NULL){
msg.code='t';
if(memcmp(&(p3->next->sin), &cin,sizeof(cin)) == 0){
h = p3->next;
p3->next = h->next;
free(h);
}else{
p3 = p3->next;
if(-1 == sendto(sfd, &msg,sizeof(msg),0,(struct sockaddr *)&p3->sin,sizeof(p3->sin)))){
perror("send error");
}
}
}
break;
}
}
}else if (pid > 0)
{
// 父进程:发送管理员消息给所有客户端
while (1)
{
strcpy(msg_ser.user,"管理员");
memset(msg_ser.text, 0, N);
fgets(msg_ser.text,N,stdin);
msg_ser.text[strlen(msg_ser.text)-1] = '\0';
msg_ser.code ='q';
if(sendto(sfd,&msg_ser,sizeof(msg_ser),0,(struct sockaddr *)&sin,sizeof(sin)) == -1)
{
perror("send error");
return -1;
}
}
}
// 结束子进程
kill(pid,SIGKILL);
// 等待子进程结束
wait(NULL);
close(sfd);
return 0;
}
客户端:cli.c
#include "zy.h"
int main(int argc, char const *argv[])
{
// 检查命令行参数数量是否正确
if(argc != 3)
{
printf("请在后面输入IP和端口号\n");
return -1;
}
// 创建套接字
int sfd = socket(AF_INET,SOCK_DGRAM,0);
if(sfd == -1)
{
perror("socket error");
return -1;
}
// 设置服务器地址结构
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
sin.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t lent =sizeof(sin);
// 初始化变量
int res = 0;
char name[32]={0};
msg_t msg;
pid_t pid;
// 获取用户名称
printf("请输入你的名字:");
msg.code = 'L';
fgets(name,M,stdin);
strcpy(msg.user,name);
msg.user[strlen(msg.user) - 1] = '\0';
// 向服务器发送登录请求
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr *)&sin,lent) == -1)
{
perror("send error");
return -1;
}
// 创建进程,子进程接收数据,父进程发送数据
pid = fork();
if(pid == -1){
perror("fork error");
}else if(pid == 0){
// 子进程:接收服务器消息
while (1){
bzero(&msg,sizeof(msg));
// 接收服务器的应答
if ( (res=recvfrom(sfd, &msg, sizeof(msg), 0,(struct sockaddr *)&sin,&lent)) == -1){
perror("recv error");
}
// 处理接收到的消息
if(strcmp(msg.user,name) == -10){
continue;
}else{
// 根据消息类型打印信息
switch(msg.code){
case 'L':
printf("[%s]登录上线了\n", msg.user);
break;
case 'C':
printf("[%s]:%s\n",msg.user,msg.text);
break;
case 'Q':
printf("[%s]退出了\n", msg.user);
break;
}
}
}
}else if(pid>0){
// 父进程:发送消息
while(1){
// 获取用户输入的消息
memset(msg.text, 0, N);
fgets(msg.text, N, stdin);
msg.text[strlen(msg.text) - 1] = '\0'; // 清除末尾换行符
// 处理退出命令
if( strcmp(msg.text, "quit") == 0){
msg.code = 'Q';
if (sendto(sfd, &msg, sizeof(msg), 0,(struct sockaddr *)&sin,lent) == -1){
perror("send error");
}
break;
}else{
msg.code = 'C';
}
// 发送消息给服务器
if (sendto(sfd, &msg, sizeof(msg), 0,(struct sockaddr *)&sin,lent) == -1){
perror("send error");
}
}
}
close(sfd);
return 0;
}