基于UDP的网络聊天室

项目需求:

  1. 如果有用户登录,其他用户可以收到这个人的登录信息
  2. 如果有人发送信息,其他用户可以收到这个人的群聊信息
  3. 如果有人下线,其他用户可以收到这个人的下线信息
  4. 服务器可以发送系统信息

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;
}

结果图:

相关推荐
宛唐羁客1 小时前
ODBC连接PostgreSQL数据库后,网卡DOWN后,客户端进程阻塞问题解决方法
网络·数据库
Beekeeper&&P...2 小时前
web钩子什么意思
前端·网络
清风.春不晚3 小时前
shell脚本2---清风
网络·网络安全
枫叶丹43 小时前
【在Linux世界中追寻伟大的One Piece】手写序列化与反序列化
linux·运维·网络
C++忠实粉丝4 小时前
计算机网络socket编程(2)_UDP网络编程实现网络字典
linux·网络·c++·网络协议·计算机网络·udp
添砖java_8574 小时前
UDP数据报套接字编程
网络·网络协议·udp
9527华安4 小时前
FPGA实现PCIE3.0视频采集转10G万兆UDP网络输出,基于XDMA+GTH架构,提供工程源码和技术支持
网络·fpga开发·udp·音视频·xdma·pcie3.0·万兆网
风走茶未凉4 小时前
转置卷积与全卷积网络FCN在语义分割中的应用
网络·深度学习·cnn
莫固执,朋友5 小时前
网络抓包工具tcpdump 在海思平台上的编译使用
网络·ffmpeg·音视频·tcpdump
VVVVWeiYee5 小时前
Mesh路由组网
运维·网络·智能路由器·信息与通信