网络编程项目之UDP聊天室

项目要求

利用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退出消息直接向子进程发起终止信号强制终止进程。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java-String类

字符串这个词我想同学们并不陌生&#xff0c;但Java语言中的字符串和我们之前所学习的C语言字符串&#xff0c;差别可谓是非常之大&#xff0c;因为在C语言当中&#xff0c;想要表示一个字符串&#xff0c;就只能使用字符数组或字符指针&#xff0c;但是这种" 对数据进行细…

大学生网上面试注意事项,试试白瓜面试

在数字化时代&#xff0c;大学生网上面试已成为求职过程中的一个关键环节。为了在这个虚拟舞台上留下深刻印象&#xff0c;以下是一些针对大学生网上面试的注意事项&#xff0c;同时&#xff0c;我们将探讨如何利用“白瓜面试”这一AI面试助手来提升你的面试表现。 大学生网上…

SpringBoot【实用篇】- 热部署

文章目录 目标:1.手动启动热部署2.自动启动热部署4.禁用热部署 目标: 手动启动热部署自动启动热部署热部署范围配置关闭热部署 1.手动启动热部署 当我们没有热部署的时候&#xff0c;我们必须在代码修改完后再重启程序&#xff0c;程序才会同步你修改的信息。如果我们想快速查…

【源码+文档】基于SpringBoot+Vue旅游网站系统【提供源码+答辩PPT+参考文档+项目部署】

作者简介&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流。✌ 主要内容&#xff1a;&#x1f31f;Java项目、Python项目、前端项目、PHP、ASP.NET、人工智能…

ES(2)(仅供自己参考)

Java代码的索引库&#xff1a; package cn.itcast.hotel;import lombok.AccessLevel; import org.apache.http.HttpHost; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.client.RequestOptions; import org.elasticsea…

《ToDesk云电脑vs青椒云性能测试,谁更能实现游戏自由?》

ToDesk云电脑vs青椒云性能测试 【前言】【使用云电脑的意义】【实测软件】【性能参数对比】1. 硬件配置2.游戏兼容性3. 延迟、流畅度与画面清晰度4. 用户体验5. 价格对比6. 附加功能 【游戏性能测试】《游戏一 黑悟空》《游戏二 赛博朋克 2077》《游戏三 CS反恐精英》 【本文小…

HarmonyOS开发5.0 net 启动界面设置

第一步、创建我们界面 第二步&#xff0c; 在EntryAbility中配置启动页面&#xff0c;在entry/src/main/ets/entryability/EntryAbility.ets中配置启动页面 配置如下 至此大功告成

python--函数详解二

一、作用域&#xff1a; 一个标识符的可见范围&#xff0c;这就是标识符的作用域&#xff0c;一般说的是变量的作用域 1.1、全局作用域 运行结果 在整个程序运行环境中可见。可以被多个函数重复多次使用 1.2、局部作用域 运行结果 这里调用a&#xff0c;显示未定义&#xff…

Etsy又被封号了!这次我终于搞懂了原因...

你是否真的了解在Etsy开店有哪些红线不能踩&#xff1f;你是否真的知道Etsy被封号后如何解决&#xff1f;本文我将探讨Etsy账号被封的常见原因&#xff0c;以及卖家可以采取的应对策略&#xff0c;以期减轻对跨境业务的伤害程度&#xff0c;感兴趣的商家速速码住&#xff0c;不…

大舍传媒:海外发稿的卓越选择——老挝新闻网报道及海外媒体发布服务

大舍传媒&#xff1a;海外发稿的卓越选择——老挝新闻网报道及海外媒体发布服务 在当今全球化的时代&#xff0c;信息的传播速度和范围至关重要。对于企业、组织和个人而言&#xff0c;能够在海外媒体上发布稿件&#xff0c;实现有效的宣传和推广&#xff0c;无疑是提升知名度…

如何找到网上爆款内容,快速复制扩大品牌声量

社媒内容爆款复制是现代营销中的一个重要策略&#xff0c;它对于提升品牌声量、曝光度和知名度具有显著效果。 首先什么是爆款&#xff1f; 爆款内容指的是在社交媒体或其他在线平台上迅速获得大量关注、分享和讨论的内容。 准确、及时找到这部分品牌相关的爆款内容&#xf…

在元神操作系统启动时自动执行任务脚本

1. 背景 本文主要介绍让元神操作系统启动时自动执行任务脚本的方法&#xff0c;适用于无人化任务执行目的。将任务脚本及相关的应用程序准备好之后&#xff0c;把装有元神操作系统的U盘插入目标电脑&#xff0c;然后打开电脑电源就会自动完成所设置的任务。 2. 方法 &#x…

Pandas JSON学习

1.JSON简介 JSON&#xff08;JavaScript Object Notation&#xff0c;JavaScript 对象表示法&#xff09;&#xff0c;是存储和交换文本信息的语法&#xff0c;类似 XML。JSON 比 XML 更小、更快&#xff0c;更易解析&#xff0c;Pandas 可以很方便的处理 JSON 数据。 [{"…

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

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

微服务设计模式 - 特性标志(Feature Flags)

微服务设计模式 - 特性标志&#xff08;Feature Flags&#xff09; 定义 特性标志&#xff08;Feature Flags&#xff09;&#xff0c;又称特性开关&#xff08;Feature Toggles&#xff09;&#xff0c;是一种常见的云计算设计模式&#xff0c;允许开发人员通过配置动态地打开…

Mac “屏幕保护程序启动或显示器关闭后需要密码“无效

屏幕保护程序启动或显示器关闭后需要密码只能选择“立即”的解决方法&#xff1a; 在 iPhone mirror中设置&#xff0c;每次询问权限。 参考&#xff1a;https://support.apple.com/en-us/120421

强势文化与弱势文化的交响:赋能认知

强势文化如同历史长河中的巨浪&#xff0c;以磅礴之力推动着社会的进步与变迁&#xff0c;其影响力深远而广泛&#xff0c;不仅塑造了民族的精神风貌&#xff0c;也深刻影响着个体的认知框架与行为模式。而弱势文化&#xff0c;则如细流涓涓&#xff0c;虽不显眼却蕴含着独特的…

虚拟机 Ubuntu 扩容

文章目录 一、Vmware 重新分配 Ubuntu 空间二、Ubuntu 扩容分区 一、Vmware 重新分配 Ubuntu 空间 先打开 Vmware &#xff0c;选择要重新分配空间的虚拟机 点击 编辑虚拟机设置 &#xff0c;再点击 硬盘 &#xff0c;再点击 扩展 选择预计扩展的空间&#xff0c;然后点击 扩展…

再探“构造函数”

文章目录 一. 初始化列表1.1 实现1.2 何时必须使用初始化列表2.3 尽量使用初始化列表 二. 类型转换2.1 内置类型 转换 类类型2.2 explicit&#xff1a;不转换2.3 构造函数多参数2.4 使用隐式转换 2.5 自定义---转换为--->自定义类型 三. 静态成员变量概念在main函数调用私有…

基于java+SpringBoot+Vue的“衣依”服装销售平台设计与实现

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; Springboot mybatis Maven mysql5.7或8.0等等组成&#x…