网络编程项目之UDP聊天室

news2024/12/23 13:20:04

项目要求

利用UDP协议,实现一套聊天室软件。服务器端记录客户端的地址,客户端发送消息后,服务器群发给各个客户端软件。

问题思考

  • 客户端会不会知道其它客户端地址?

UDP客户端不会直接互连,所以不会获知其它客户端地址,所有客户端地址存储在服务器端。

  • 有几种消息类型?

登录:服务器存储新的客户端的地址。把某个客户端登录的消息发给其它客户端。

聊天:服务器只需要把某个客户端的聊天消息转发给所有其它客户端。

退出:服务器删除退出客户端的地址,并把退出消息发送给其它客户端。

  • 服务器如何存储客户端的地址?

链表

链表节点结构体:
struct node{
	struct sockaddr_in addr;
	struct node *next;
};

消息对应的结构体(同一个协议)
typedef struct msg_t
{
    int type;//L  M  Q  
    char name[32];//用户名
    char text[128];//消息正文
}MSG_t;

int memcmp(void *s1,void *s2,int size)
功能:比较两个空间内的值是否完全相同
  • 客户端如何同时处理发送和接收?

客户端不仅需要读取服务器消息,而且需要发送消息。读取需要调用recvfrom,发送需要先调用gets,两个都是阻塞函数。所以必须使用多任务来同时处理,可以使用多进程或者多线程来处理。

程序流程图

客户端

伪代码:

 服务器端:

//1.创建服务器流程

//2.创建空链表

//3.循环接受消息

//根据消息类型调用函数

login:

1.将登录信息发送给所有已经登录的客户端(链表,sockfd,msg)

2.将新登录的客户端插入链表(caddr)

quit:

1.将谁退出的信息发送给所有登录的客户端(遍历链表)

2.将退出的客户端信息删除

chat:

将聊天的内容转发给已经登录的客户端

客户端:

1.客户端创建流程

2.登录(输入名字,发送给服务器)login

3.创建子进程(循环接收服务器的信息并打印)chat

4.父进程

终端输入信息并发送给服务器(注意发送的是否为quit,区分正常消息和退出消息)

代码 

head.h 头文件

#ifndef _SEQSTACK_H_
#define _SEQSTACK_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <dirent.h>
#include <sys/wait.h>
#include <signal.h>
#define N 128
//链表节点结构体,用来存储用户信息
struct node
{
    struct sockaddr_in addr;
    struct node *next;
};

//消息对应的结构体(同一个协议)
typedef struct msg_t
{
    char type;       //L  M  Q
    char name[32];  //用户名
    char text[128]; //消息正文
} MSG_t;
// 消息类型
enum msgtype
{
    L, //登录
    M, //消息
    Q  //退出
};
#endif

UDP聊天室客户端 

int main(int argc, char const *argv[])
{
    MSG_t msg;
    // 1.创建数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("sockfd: %d\n", sockfd);
    // 2.指定网络信息
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    int len = sizeof(saddr);
    msg.type = L;
    fgets(msg.name, sizeof(msg.name), stdin);
    if (msg.name[strlen(msg.name) - 1] == '\n')
    {
        msg.name[strlen(msg.name) - 1] = '\0';
    }
    sendto(sockfd, &msg, N, 0, (struct sockaddr *)&saddr, sizeof(saddr));
    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0)
    {
        //循环接收消息
        while (1)
        {
            //最后两个参数存放:发送消息的人的信息
            int ret = recvfrom(sockfd, &msg, N, 0, (struct sockaddr *)&saddr, &len);
            if (ret < 0)
            {
                perror("recvfrom err");
                return -1;
            }
            else
            {
                if (msg.type == M)
                {
                    printf("%s: %s\n", msg.name, msg.text);
                }
                else if (msg.type == L)
                {
                    printf("%s\n", msg.text);
                }
                else if (msg.type == Q)
                {
                    printf("%s\n", msg.text);
                }
            }
        }
    }
    else
    {
        printf("------你已登录,开始聊天------\n");
        while (1)
        {
            msg.type = M;
            fgets(msg.text, N, stdin);
            if (msg.text[strlen(msg.text) - 1] == '\n')
            {
                msg.text[strlen(msg.text) - 1] = '\0';
            }
            if (strcmp(msg.text, "quit") == 0)
            {
                msg.type = Q;
                sendto(sockfd, &msg, N, 0, (struct sockaddr *)&saddr, sizeof(saddr));
                kill(pid, SIGKILL);
                wait(NULL);
                exit(0);
            }
            sendto(sockfd, &msg, N, 0, (struct sockaddr *)&saddr, sizeof(saddr));
        }
        kill(pid, SIGKILL); 
        wait(NULL);
    }
    //5.关闭套接字
    close(sockfd);
    return 0;
}

 UDP聊天室服务端

#include "head.h"
void login(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd);
void chat(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd);
void quit(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd);
struct node *linklist_create();

int main(int argc, char const *argv[])
{
    MSG_t msg;
    // 1.创建数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket err");
        return -1;
    }
    printf("sockfd: %d\n", sockfd);
    // 2.指定网络信息
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = INADDR_ANY;
    int len = sizeof(caddr);
    //3.绑定套接字
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("bind ok\n");
    //创建空的有头单向链表
    struct node *p = linklist_create();
    //循环接收消息
    while (1)
    {
        int ret = recvfrom(sockfd, &msg, N, 0, (struct sockaddr *)&caddr, &len);
        if (ret < 0)
        {
            perror("recvfrom err");
            return -1;
        }
        else
        {
            if (msg.type == L)
            {
                login(p, msg, caddr, sockfd);
                printf("%s信息保存成功\n", msg.name);
            }
            else if (msg.type == M)
            {
                chat(p, msg, caddr, sockfd);
            }
            else if (msg.type == Q)
            {
                quit(p, msg, caddr, sockfd);
                printf("%s信息已删除\n", msg.name);
            }
        }
    }
    //5.关闭套接字
    close(sockfd);
    return 0;
}
//创建头结点函数
struct node *linklist_create()
{
    struct node *p = (struct node *)malloc(sizeof(struct node));
    if (NULL == p)
    {
        perror("p malloc lost\n");
        return NULL;
    }
    p->next = NULL;
    return p;
}

//登录函数
void login(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd)
{
    给其他用户发送该用户已登录信息
    sprintf(msg.text, "%s login", msg.name);
    while (p->next != NULL)
    {
        p = p->next;
        sendto(sockfd, &msg, N, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));
    }
    //创建新节点用来保存用户信息
    struct node *ptail = NULL;
    ptail = p;
    struct node *pnew = (struct node *)malloc(sizeof(struct node));
    if (NULL == pnew)
    {
        perror("pnew malloc err\n");
        return;
    }
    pnew->addr = caddr;
    pnew->next = NULL;
    ptail->next = pnew;
    ptail = pnew;
    return;
}
聊天函数
void chat(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd)
{
    while (p->next != NULL)
    {
        p = p->next;
        if (memcmp(&p->addr, &caddr, sizeof(caddr)) != 0)
        {
            sendto(sockfd, &msg, N, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));
        }
    }
    return;
}
退出函数
void quit(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd)
{
    sprintf(msg.text, "%s: 退出", msg.name);
    while (p->next != NULL)
    {
        p = p->next;
        if (memcmp(&p->addr, &caddr, sizeof(caddr)) != 0)
        {
            sendto(sockfd, &msg, N, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));
        }
    }
    struct node *q = p->next;
    while (q != NULL)
    {
        if (memcmp(&p->addr, &caddr, sizeof(caddr)) == 0)
        {
            p->next = q->next;
            free(q);
            q = NULL;
            break;
        }
        else
        {
            p = p->next;
            q = q->next;
        }
    }
    return;
}

问题 

一:第一个问题就是在用sendto,recvfrom函数在进行收发时我传的参数是结构体里的成员变量,在输入名字时我传的是sendto(sockfd,&msg.name....),在传消息时我传的参数sendto(sockfd,&msg.text.)

导致服务器接收不到已经改变了的msg.type消息类型了,所以会出现一些问题。我们应该传入&msg整个结构体,里面包含着消息类型,消息正文,名字,我就像一个协议一样在服务器与客户端之间收发。

二:第二个问题就是我在服务器写到判断消息类型的逻辑错误,我把先判断写在前面了,然后再接收,导致出现2次登录。正确的逻辑应该是先接收客户端发来的协议以及数据,然后再判断。

三:第三个问题就是我在客户端判断客户端退出了,然后没有在while循环外加wait(NULL)回收子进程资源,会导致子进程变成僵尸进程,然后我又在子进程while循环外加入了exit(0),然后父进程写了wait(NULL),虽然会回收,但是也把其他客户端也给退出回收了,因为其他客户端在子进程会收到Q类型的消息类型,也会退出while循环然后退出被父进程回收。最后我又被老师指导下只在父进程加了个kill(pid,SIGKILL)和wait(NULL)。只要我父进程收到quit退出消息直接向子进程发起终止信号强制终止进程。

至此呢,在我解决了这三个问题之后呢,我的项目就完成了,也对整个项目有了更深入的认识。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2230120.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【NOIP普及组】 FBI树

【NOIP普及组】 FBI树 C语言版本C 版本Java版本Python版本 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 我们可以把由“0”和“1”组成的字符串分为三类&#xff1a;全“0”串称为B串&#xff0c;全“1”串称为I串&#xff0c;既含“0”又…

Lucene的概述与应用场景(1)

文章目录 第1章 Lucene概述1.1 搜索的实现方案1.1.1 传统实现方案1.1.2 Lucene实现方案 1.2 数据查询方法1.1.1 顺序扫描法1.1.2 倒排索引法 1.3 Lucene相关概念1.3.1 文档对象1.3.2 域对象1&#xff09;分词2&#xff09;索引3&#xff09;存储 1.3.3 常用的Field种类 1.4 分词…

不适合的学习方法

文章目录 不适合的学习方法1. 纯粹死记硬背2. 过度依赖单一资料3. 线性学习4. 被动学习5. 一次性学习6. 忽视实践7. 缺乏目标导向8. 过度依赖技术9. 忽视个人学习风格10. 过于频繁的切换 结论 以下是关于不适合的学习方法的更详细描述&#xff0c;包括额外的内容和相关公式&…

华为OD机试真题(Python/JS/C/C++)- 考点 - 细节

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题 点这里。 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。

Linux中使用NGINX

NGINX简介 Nginx&#xff08;engine x&#xff09;是俄罗斯人编写的十分轻量级的HTTP服务器是一个高性能的HTTP和反向代理服务器&#xff0c;同时也是一个IMAP/POP3/SMTP代理服务器官方网站&#xff1a;http://nginx.org/ NGINX概述 Nginx默认配置文件&#xff1a;/etc/ngin…

scrapy爬取名人名言

爬取名人名言&#xff1a;http://quotes.toscrape.com/ 1 创建爬虫项目&#xff0c;在终端中输入&#xff1a; scrapy startproject quotes2 创建之后&#xff0c;在spiders文件夹下面创建爬虫文件quotes.py&#xff0c;内容如下&#xff1a; import scrapy from scrapy.spi…

mmsegmentation训练自己的数据集

文章目录 前言一、安装MMSegmentation二、数据集转换1.labelme标签数据转化为voc数据 三、训练设置1.建立数据集文件&#xff0c;并存入数据集2.设置训练配置文件 四、使用官方权重1、选择预测的方法2、查看方法支持的预训练数据集和权重权重位置找到对应的数据集下载权重 3、使…

LeetCode994. 腐烂的橘子(2024秋季每日一题 54)

在给定的 m x n 网格 grid 中&#xff0c;每个单元格可以有以下三个值之一&#xff1a; 值 0 代表空单元格&#xff1b;值 1 代表新鲜橘子&#xff1b;值 2 代表腐烂的橘子。 每分钟&#xff0c;腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。 返回 直到单元格中没有…

Pinctrl子系统中Pincontroller构造过程驱动分析:imx_pinctrl_soc_info结构体

往期内容 本专栏往期内容&#xff1a; Pinctrl子系统和其主要结构体引入Pinctrl子系统pinctrl_desc结构体进一步介绍Pinctrl子系统中client端设备树相关数据结构介绍和解析 input子系统专栏&#xff1a; 专栏地址&#xff1a;input子系统input角度&#xff1a;I2C触摸屏驱动分析…

地理信息科学专业想搞GIS开发:学前端还是后端?

地理信息科学专业的同学是学前端开发比较好呢还是学后端开发比较好呢&#xff1f; 部分网友&#xff1a;学前端更好 主修前端更好&#xff0c;因为地信学后端&#xff0c;是卷不赢学计算机的 本科卷前端&#xff0c;硕士阶段可以卷后端 甚至有网友直呼&#xff0c;地信根本没有…

批处理之for语句从入门到精通--呕血整理

文章目录 一、前言二、for语句的基本用法三、文本解析显神威&#xff1a;for /f 用法详解四、翻箱倒柜遍历文件夹&#xff1a;for /r五、仅仅为了匹配第一层目录而存在&#xff1a;for /d六、计数循环&#xff1a;for /l后记 for语句从入门到精通 一、前言 在批处理中&#…

第8章利用CSS制作导航菜单(第八次作业)

效果图如下&#xff1a; Html代码如下&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8"><title>山水之间</title><style type"text/css">import url("../css/work1.css");</style…

Ubuntu22.04 安装图形界面以及XRDP教程

一、准备环境 1.一台服务器安装系统ubuntu&#xff08;这里大部分ubuntu系统可以同用&#xff09; 2.安装的ubuntu系统未安装图形界面 二、操作步骤 1.远程ssh或者直接登录服务器命令行界面 ssh -p 远程端口 rootIP 2.更新系统软件包 sudo apt update # 更新本地的软件包…

深度学习基础知识-编解码结构理论超详细讲解

编解码结构&#xff08;Encoder-Decoder&#xff09;是一种应用广泛且高效的神经网络架构&#xff0c;最早用于序列到序列&#xff08;Seq2Seq&#xff09;任务&#xff0c;如机器翻译、图像生成、文本生成等。随着深度学习的发展&#xff0c;编解码结构不断演变出多种模型变体…

扫描电镜的超低温冷冻制样及传输技术(Cryo-SEM)

扫描电镜的超低温冷冻制样及传输技术(Cryo-SEM) 扫描电镜&#xff08;Scanning Electron Microscope&#xff0c;简称SEM&#xff09;是一种利用聚焦电子束扫描样品表面&#xff0c;通过检测二次电子或反射电子等信号来获取样品表面形貌信息的显微观察技术&#xff1b;然而&…

JS手写:从0开始认识【柯里化】【支持占位符的柯里化】

柯里化 功能介绍 柯里化是拆分函数的一种手段&#xff0c;允许我们以偏函数的方式调用这个函数。 比如说&#xff0c;原来的函数A必须传入三个参数才能运行。经过柯里化处理之后的函数KA传入三个参数&#xff0c;能成功运行&#xff1b;传入两个参数也能&#xff0c;但是会返…

WebSocket 连接频繁断开的问题及解决方案

文章目录 WebSocket 连接频繁断开的问题及解决方案1. 引言2. 什么是 WebSocket&#xff1f;2.1 WebSocket 的优势2.2 WebSocket 的工作原理 3. WebSocket 连接频繁断开的常见原因3.1 服务器端问题3.1.1 服务器负载过高3.1.2 服务器配置不当3.1.3 超时设置 3.2 网络问题3.2.1 网…

openGauss开源数据库实战十二

文章目录 任务十二 openGauss逻辑结构:表管理任务目标实施步骤一、准备工作二、创建表1.新建表默认保存在public模式中2.在一个数据库的不同模式下创建表3.创建表的时候定义约束4.创建表时使用自增数据类型5.使用现有的表创建新表 三、查看表的信息1.在gsql中查看表的定义2.查看…

ADI仿真连接有效性检查方法

1、确认仿真器引脚接插OK. A、检查电脑正常连接 B、确认仿真器引脚定义匹配与上电正确连接 2、打开CCES&#xff0c;打开Debug Configurations 3、连接芯片类型选择 4、点击Configuratior… 5、选择Test…,在点Start&#xff0c;确认状态都OK&#xff0c;即可开始仿真调…

docker部署nginx+nacos+redis+java镜像和容器

nginx镜像制作 Dockerfile内容&#xff1a; # 基础镜像 FROM nginx # author MAINTAINER ruoyi# 挂载目录 VOLUME /home/ruoyi/projects/ruoyi-ui # 创建目录 RUN mkdir -p /home/ruoyi/projects/ruoyi-ui # 指定路径 WORKDIR /home/ruoyi/projects/ruoyi-ui # 复制conf文件到路…