【Linux开发实战指南】基于UDP协议的即时聊天室:快速构建登陆、聊天与退出功能

@author: bbxwg

@system_version: Ubuntu 22.04 

@Time : 2024-07-04

目录

技术简单讲解:

UDP (User Datagram Protocol)

链表

父子进程

信号

基于UDP的即时聊天室系统:客户端与服务器端实现

客户端操作步骤

服务器端操作步骤


系统版本:

实现效果:

技术简单讲解:

UDP (User Datagram Protocol)

UDP 是一种无连接的、不可靠的传输层协议,位于IP协议之上。它提供了最基本的数据传输服务,不保证数据包的顺序、可靠到达或无重复。与TCP(传输控制协议)相比,UDP具有较低的传输延迟,因为省去了建立连接和确认接收等过程,适用于对实时性要求较高、但能容忍一定数据丢失的场景,如在线视频、语音通话、DNS查询等。

链表

链表是一种动态数据结构,用于存储一系列元素(节点),每个节点包含数据字段和指向下一个节点的引用(指针)。链表分为单向链表、双向链表和循环链表等类型。与数组相比,链表在插入和删除操作上更为高效,因为它不需要移动元素,只需修改节点间的指针即可。但访问链表中的元素不如数组直接,通常需要从头节点开始遍历。

父子进程

在操作系统中,当一个进程创建新进程时,原始进程称为父进程,新创建的进程称为子进程。这一过程通常通过fork()系统调用来实现。子进程继承了父进程的大部分属性,如环境变量、打开的文件描述符等,但拥有独立的内存空间、PID(进程ID)和调度优先级。父子进程可以并发执行,互不影响,是多任务操作系统中实现并发的一种方式。当父进程结束时,如果未明确处理,操作系统会负责托管孤儿进程(即父进程已经终止的子进程)。

信号

信号是Unix/Linux系统中用于进程间通信和进程控制的一种机制。信号是一种异步通知事件,可以由硬件异常(如断电)、操作系统(如杀死进程的SIGKILL)或一个进程向另一个进程发送(如使用kill命令)。常见的信号有SIGINT(Ctrl+C中断),SIGHUP(挂起),SIGTERM(终止进程)等。进程可以通过安装信号处理器(signal handler)来响应特定信号,决定忽略、默认处理或自定义处理信号事件。信号机制允许程序对突发事件做出响应,增强了程序的灵活性和健壮性。

基于UDP的即时聊天室系统:客户端与服务器端实现

客户端操作步骤
  1. 初始化网络连接

    • 创建UDP套接字,配置相关参数。
    • 输出用户名,定义请求类型,发送至服务器。
  2. 多线程通信管理

    • 创建acceptfd,用于接收服务器响应。
    • 启动两个线程:一个负责接收服务器消息,另一个负责向服务器发送消息。
    • 接收线程:监听服务器反馈,处理退出或聊天事件。
      • 若收到“quit”命令,构造退出类型消息,通知服务器删除自身链表节点。
      • 若为聊天消息,封装聊天类型,转发给服务器。
  3. 发送线程:持续监听用户输入,将聊天内容打包发送至服务器。

服务器端操作步骤
  1. 网络服务初始化

    • 创建UDP套接字,绑定本地地址和端口。
    • 初始化客户端链表,用于存储在线用户信息。
  2. 请求处理循环

    • 监听并接收客户端请求,解析请求类型(登录、聊天、退出)。
    • 根据请求类型,调用相应处理函数。
  3. 登录处理

    • 向其他客户端广播新用户加入信息。
    • 更新链表,添加当前连接的客户端信息,确保不会自我广播。
  4. 聊天消息转发

    • 循环遍历链表,向除发送者外的所有客户端转发聊天内容。
    • 使用memcmp比较客户端地址,避免重复发送给消息来源。
  5. 退出处理

    • 定位并删除链表中对应的客户端节点。
    • 向其他客户端发送退出通知,更新在线用户列表。

服务器源码如下 :

#include <stdio.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/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include "chatroom.h"
struct sockaddr_in saddr, caddr;
int len = sizeof(caddr);
MSG_t msg;
int flag = 0;

link_list_t createLink()
{
    link_list_t p = (link_list_t)malloc(sizeof(link_node_t));
    if (NULL == p)
    {
        perror("linklis create error\n");
        return NULL;
    }
    p->next = NULL;

    return p;
}

void Sever_relogin(int sockfd, link_list_t p)
{
    // 可以将msg.name提放到%s的地方,然后赋值给msg.text
    char a[32] = "";

    if (flag == 0)
    {
        sprintf(msg.text, "恭喜%s你是第一位用户, 将被打印在服务器上!", msg.name);
        printf("%s\n", msg.text);
    }
    flag++;
    sprintf(msg.text, "%s我已经上线啦,快来聊天吧!", a);

    // 循环去发送
    while (p->next != NULL)
    {
        p = p->next;
        sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->saddr), sizeof(p->saddr));
    }

    // 发送完给别人后再去创建新的链表, 这样就不会发送给自己了
    link_list_t pnew = (link_list_t)malloc(sizeof(link_node_t));

    //每次都将新的客户端赋值给新的函数
    pnew->saddr = caddr;
    // 令他的指针域赋值为空
    pnew->next = NULL;

    //令指针一直指向最后一位
    p->next = pnew;
}

void chat(int sockfd, link_list_t p)
{
    // 循环去发送
    while (p->next != NULL)
    {
        p = p->next;
        // 比较当前的客户端的信息和外面的信息是否相同,如果相同则继续不相同则发送
        if (memcmp(&(p->saddr), &caddr, sizeof(caddr)) == 0)
        {
            continue;
        }
        else
        {
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->saddr), sizeof(p->saddr));// 接受放是当前检测的链表
        }
    }
}

void delete (int sockfd, link_list_t p)
{
    //拼接字符并复制给msg.text
    sprintf(msg.text, "%s已经退出,天色不早,趁早休息吧!", msg.name);
    //循环
    while (p->next != NULL)
    {
        p = p->next;

        //如果找到下一个客户端和外面的客户端相同则实现链表删除并且发送给其他客户端否则继续往下
        if (memcmp(&(p->next->saddr), &caddr, sizeof(caddr)) == 0)
        {
            link_list_t pdel = p->next;
            p->next = pdel->next;
            free(pdel);
            pdel = NULL;
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->saddr), sizeof(p->saddr));
        }
    }
}

int main(int argc, char const *argv[])
{
    // 创建链表
    link_list_t p = createLink();

    //1.创建数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("sockfd err");
        return -1;
    }
    printf("sockfd is %d\n", sockfd);

    //2.填充结构体
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(8888);
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    // 绑定
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind error\n");
        return -1;
    }

    // 循环去接受
    while (1)
    {

        int recvfd = recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&caddr, &len); //2
        if (recvfd < 0)
        {
            perror("recvbyte err");
            return -1;
        }

        // 判断msg.type的值 执行相应函数
        switch (msg.type)
        {
        case 'r':
            Sever_relogin(sockfd, p);
            break;
        case 'c':
            chat(sockfd, p);
            break;
        case 'd':
            delete (sockfd, p);
            break;
        default:
            break;
        }
    }
    close(sockfd);
    return 0;
}

 客户端源码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.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/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include "chatroom.h"
// 创建结构体变量
struct sockaddr_in saddr, caddr;
//创建外部变量的长度变量
int len = sizeof(caddr);
// 创建结构体名
MSG_t msg;

//循环去发送
void *Mysend(void *arg)
{
    
    int sockfd = *((int *)arg);
    while (1)
    {
        fgets(msg.text, sizeof(msg.text), stdin);
        if (msg.text[strlen(msg.text) - 1] == '\n')
            msg.text[strlen(msg.text) - 1] = '\0';
        //判断是否输入了quit,如果输入了则让type = 'd’,并且发给服务器,如果不是就让类型等于c然后将c发给服务器
        if (!strcmp(msg.text, "quit"))
        {
            msg.type = 'd';
            sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, sizeof(saddr));
            break;
        }
        msg.type = 'c';
        sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, sizeof(saddr));
    }
    pthread_exit(NULL);
    close(sockfd);
}

void *Myrecv(void *arg)
{
    int sockfd = *((int *)arg);
    int recvfromfd;
    // 循环去接受
    while (1)
    {
        // 循环去接受结构体
        recvfromfd = recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&caddr, &len);
        if (recvfromfd < 0)
        {
            perror("recv is err:");
            return NULL;
        }
        else
        {
            printf("%s: %s\n",msg.name, msg.text);
        }
    }
    pthread_exit(NULL);
    close(sockfd);
}

int main(int argc, char const *argv[])
{

    //1.创建数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("sockfd err");
        return -1;
    }
    //2.填充结构体

    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(8888);
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    // 输入名字,并接将状态变为r然后将一整个结构体发给服务器
    printf("Please input your name: ");
    fgets(msg.name, sizeof(msg.name), stdin);
    if (msg.name[strlen(msg.name) - 1] == '\n')
        msg.name[strlen(msg.name) - 1] = '\0';
    msg.type = 'r';
    sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, sizeof(saddr));

    // 创建两个线程
    pthread_t tid, tid1;

    pthread_create(&tid, NULL, Myrecv, &sockfd);
    pthread_create(&tid1, NULL, Mysend, &sockfd);

    // 回收
    pthread_join(tid, NULL);
    pthread_join(tid1, NULL);

    close(sockfd);
    return 0;
}

chatroom.h源码如下:

//链表节点结构体:
typedef struct node
{
    struct sockaddr_in saddr;
    struct node *next;
} link_node_t, *link_list_t;
//消息对应的结构体(同一个协议)
typedef struct msg_t
{
    char type;      //L  M  Q
    char name[32];  //用户名
    char text[128]; //消息正文
} MSG_t;

link_list_t createLink();

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

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

相关文章

怎么将视频翻译免费?这篇文章告诉你5个视频翻译的方法

在探索不同文化的经典影视剧时&#xff0c;我们常常被那些精彩绝伦的台词深深吸引。 然而&#xff0c;难以理解的外语符号让我们难以完全领略其魅力。不过&#xff0c;如果你认识免费视频翻译软件的话&#xff0c;那将这些经典台词从陌生的外语符号变成直观的母语表达&#xf…

藏汉翻译通工具推荐使用《藏文翻译词典》App:藏族文化的掌上宝典,帮助你了解学习藏语反义词近义词和藏文作文!

如果你正在学习藏语&#xff0c;遇到不同地区的发音不同时&#xff0c;卫藏语、安多语和康巴语&#xff0c;那么你需要一款好用的翻译和语音朗读工具&#xff0c;帮助你掌握藏语。 如果你正在用藏文写作文&#xff0c;发现一些词汇不会时&#xff0c;需要使用藏文词典&#xf…

从0到1构建渠道运营体系:实战案例与策略指南

引言 在当今竞争激烈的市场环境中&#xff0c;有效的渠道运营是企业实现产品或服务快速触达目标用户、提升市场份额的关键。从零开始构建一个高效的渠道运营体系&#xff0c;不仅需要深思熟虑的策略规划&#xff0c;还需要灵活应变的实战操作。本文将结合实战案例&#xff0c;…

C++ STL 多线程库用法介绍

目录 一&#xff1a;Atomic&#xff1a; 二&#xff1a;Thread 1. 创建线程 2. 小心移动(std::move)线程 3. 如何创建带参数的线程 4. 线程参数是引用类型时&#xff0c;要小心谨慎。 5. 获取线程ID 6. jthread 7. 如何在线程中使用中断 stop_token 三&#xff1a;如何…

大前端热点技术

前言 2018年后&#xff0c;整个大前端发展趋于稳定&#xff0c;各大主流框架的特性变少&#xff0c;各种新轮子也在逐渐变少&#xff0c;但在多端融合、上下游提效以及一些细分领域&#xff0c;还是有很多值得期待的。 本文将基于过去一年大前端方向在Web、Node、多端、IoT、…

vue学习笔记之组件传值

说起组件传值&#xff0c;首先要介绍再vue中什么是组件。 组件&#xff08;Component&#xff09;&#xff0c;是vue中很强大的一个功能&#xff0c;可以将一些可重用的代码进行重用。所有的vue组件同时也是vue实例&#xff0c;可以接受使用相同的选项对象和提供相同的生命周期…

[Unity入门01] Unity基本操作

参考的傅老师的教程学了一下Unity的基础操作&#xff1a; [傅老師/Unity教學] Unity3D基礎入門 [華梵大學] 遊戲引擎應用基礎(Unity版本) Class#01 移动&#xff1a;鼠标中键旋转&#xff1a;鼠标右键放大&#xff1a;鼠标滚轮飞行模式&#xff1a;右键WASDQEFocus模式&…

【机器学习】属性降维:揭示数据的简化之美

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 属性降维&#xff1a;揭示数据的简化之美引言什么是属性降维&#xff1f;为何降…

linux学习week2+3

linux学习 九.linux磁盘分区、挂载 3.磁盘情况查询 命令&#xff1a;df -h 注意&#xff1a;使用率到80%以上就要清理了 查询指定目录的磁盘占用情况&#xff1a;du -d 目录 其它参数&#xff1a; -s&#xff1a;指定目录占用大小汇总 -h&#xff1a;带计量单位 -a&#xff…

lora/lycoris

Stable Diffusion 训练指南 (LyCORIS) | Coding HuskyStable Diffusion 文字生成图片的教程已经很多了。这篇文章是讲解如何用 Kohya Trainer 在 Google Colab 上训练一个 LyCORIS 模型。在读之前希望你已经至少玩过 Stable Diffusion。https://ericfu.me/stable-diffusion-fin…

QFileSystemModel绑定到 QTreeView、 QListView、QTableView

QFileSystemModel绑定到 QTreeView、 QListView、QTableView&#xff0c;实现文件的查看 .h文件 #ifndef FILESYSEXAMPLE_H #define FILESYSEXAMPLE_H#include <QMainWindow> #include <QFileSystemModel>namespace Ui { class FileSysExample; }class FileSysExam…

Vuex的模块化编程

1.之前我们使用store引入的时候不够简介&#xff0c;store为我们封装了方法 mapState:从state中获取数据,以数组的方式返回 mapGetters:从getters中获取方法,以数组的方式返回 mapMutations:从mutations中获取操作,以数组的方式返回 mapActions:从actions中获取动作,以数组的方…

Hi3861 OpenHarmony嵌入式应用入门--MQTT

MQTT 是机器对机器(M2M)/物联网(IoT)连接协议。它被设计为一个极其轻量级的发布/订阅消息传输 协议。对于需要较小代码占用空间和/或网络带宽非常宝贵的远程连接非常有用&#xff0c;是专为受限设备和低带宽、 高延迟或不可靠的网络而设计。这些原则也使该协议成为新兴的“机器…

PFC电路中MOS管的选取2

上面这种驱动方式叫推挽驱动&#xff0c;或者图腾柱驱动 当芯片驱动脚 DRV为高电平时&#xff0c;此时回路中的源是芯片的 DRV引脚&#xff0c;芯片驱动电流从左往右流动&#xff0c;通过 R1&#xff0c;通过Q1的be脚&#xff0c;通过R3、R4给MOS管Q4的Cgs结电容充电 不过值得…

Mybatis-Plus一文详解BaseMapper和Service 使用

Mybatis-Plus简介 MyBatis-Plus (opens new window)&#xff08;简称 MP&#xff09;是一个MyBatis (opens new window)的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 特性&#xff1a; 无侵入&#xff1a;只做增强不做…

idea 默认路径修改

1.查看 idea 的安装路径&#xff08;右键点击 idea 图标&#xff0c;查看路径 &#xff09; “C:\Program Files\JetBrains\IntelliJ IDEA 2021.3.1\bin\idea64.exe” 在 bin 目录查看 idea.properties 文件&#xff0c;修改以下四个路径文件 # idea.config.path${user.home}/…

Linux的前世今生

Unix的起源和发展 1969年&#xff0c;AT&T贝尔实验室的Ken Thompson和Dennis Ritchie等人开发了Unix操作系统。Unix的设计理念强调小而简洁的工具&#xff0c;文本流和系统模块化&#xff0c;这些理念后来成为Linux开发的重要基础。1973年&#xff0c;Unix用C语言重新编写…

昇思25天学习打卡营第17天 | K近邻算法实现红酒聚类

内容介绍&#xff1a; K近邻算法&#xff08;K-Nearest-Neighbor, KNN&#xff09;是一种用于分类和回归的非参数统计方法&#xff0c;是机器学习最基础的算法之一。它正是基于以上思想&#xff1a;要确定一个样本的类别&#xff0c;可以计算它与所有训练样本的距离&#xff0…

LVGL移植与VS模拟器使用

一、移植文件介绍 二、移植部分 第一步&#xff1a;创建LVGL文件夹 第二步&#xff1a; 构造LVGL文件夹&#xff1a;LVGL - GUI - lvgl - 第三步&#xff1a;添加文件 3.1 从examples中添加2个.c文件 3.2 从src中添加文件 draw文件 extra文件 第四步&#xff1a; 三、Ke…

分享2个iPhone恢复照片的方法,赶紧码住收藏!

手机已经成为我们生活中不可或缺的一部分&#xff0c;它不仅仅是通讯工具&#xff0c;更是我们记录生活点滴的重要工具之一。然而&#xff0c;正如其他任何设备一样&#xff0c;iPhone上存储的照片有时也会不小心被删除或丢失。 别担心&#xff0c;即使你误删了重要的照片&…