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

简介: UDP 是一种无连接的、不可靠的传输层协议,位于IP协议之上。它提供了最基本的数据传输服务,不保证数据包的顺序、可靠到达或无重复。与TCP(传输控制协议)相比,UDP具有较低的传输延迟,因为省去了建立连接和确认接收等过程,适用于对实时性要求较高、但能容忍一定数据丢失的场景,如在线视频、语音通话、DNS查询等。链表链表是一种动态数据结构,用于存储一系列元素(节点),每个节点包含数据字段和指向下一个节点的引用(指针)。链表分为单向链表、双向链表和循环链表等类型。与数组相比,链表在插入和删除操作上更为高效,因为它不需要移动元素,只需修改节点间的指针即可。但访问链表中的元素不如数组直接,通常需要从

@author: bbxwg

@system_version: Ubuntu 22.04

@Time : 2024-07-04

目录

技术简单讲解:

UDP (User Datagram Protocol)

链表

父子进程

信号

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

客户端操作步骤

服务器端操作步骤


系统版本:

image.gif 编辑

实现效果:

image.gif 编辑

技术简单讲解:

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套接字,配置相关参数。
  • 输出用户名,定义请求类型,发送至服务器。
  1. 多线程通信管理
  • 创建acceptfd,用于接收服务器响应。
  • 启动两个线程:一个负责接收服务器消息,另一个负责向服务器发送消息。
  • 接收线程:监听服务器反馈,处理退出或聊天事件。
  • 若收到“quit”命令,构造退出类型消息,通知服务器删除自身链表节点。
  • 若为聊天消息,封装聊天类型,转发给服务器。
  1. 发送线程:持续监听用户输入,将聊天内容打包发送至服务器。

服务器端操作步骤

  1. 网络服务初始化
  • 创建UDP套接字,绑定本地地址和端口。
  • 初始化客户端链表,用于存储在线用户信息。
  1. 请求处理循环
  • 监听并接收客户端请求,解析请求类型(登录、聊天、退出)。
  • 根据请求类型,调用相应处理函数。
  1. 登录处理
  • 向其他客户端广播新用户加入信息。
  • 更新链表,添加当前连接的客户端信息,确保不会自我广播。
  1. 聊天消息转发
  • 循环遍历链表,向除发送者外的所有客户端转发聊天内容。
  • 使用memcmp比较客户端地址,避免重复发送给消息来源。
  1. 退出处理
  • 定位并删除链表中对应的客户端节点。
  • 向其他客户端发送退出通知,更新在线用户列表。

image.gif 编辑

服务器源码如下 :

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

image.gif

客户端源码如下:

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

image.gif

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();

image.gif


相关文章
|
3月前
|
安全 Linux 编译器
探索Linux内核的奥秘:从零构建操作系统####
本文旨在通过深入浅出的方式,带领读者踏上一段从零开始构建简化版Linux操作系统的旅程。我们将避开复杂的技术细节,以通俗易懂的语言,逐步揭开Linux内核的神秘面纱,探讨其工作原理、核心组件及如何通过实践加深理解。这既是一次对操作系统原理的深刻洞察,也是一场激发创新思维与实践能力的冒险。 ####
|
11天前
|
XML JSON 算法
【JavaEE】——自定义协议方案、UDP协议
自定义协议,序列化,xml方案,json方案,protobuffer方案,UDP协议,校验和,比特翻转,CRC算法,md5算法
|
13天前
|
Prometheus 运维 监控
Prometheus+Grafana+NodeExporter:构建出色的Linux监控解决方案,让你的运维更轻松
本文介绍如何使用 Prometheus + Grafana + Node Exporter 搭建 Linux 主机监控系统。Prometheus 负责收集和存储指标数据,Grafana 用于可视化展示,Node Exporter 则采集主机的性能数据。通过 Docker 容器化部署,简化安装配置过程。完成安装后,配置 Prometheus 抓取节点数据,并在 Grafana 中添加数据源及导入仪表盘模板,实现对 Linux 主机的全面监控。整个过程简单易行,帮助运维人员轻松掌握系统状态。
100 3
|
23天前
|
存储 网络协议 安全
用于 syslog 收集的协议:TCP、UDP、RELP
系统日志是从Linux/Unix设备及网络设备生成的日志,可通过syslog服务器集中管理。日志传输支持UDP、TCP和RELP协议。UDP无连接且不可靠,不推荐使用;TCP可靠,常用于rsyslog和syslog-ng;RELP提供可靠传输和反向确认。集中管理日志有助于故障排除和安全审计,EventLog Analyzer等工具可自动收集、解析和分析日志。
|
2月前
|
监控 网络协议 网络性能优化
网络通信的核心选择:TCP与UDP协议深度解析
在网络通信领域,TCP(传输控制协议)和UDP(用户数据报协议)是两种基础且截然不同的传输层协议。它们各自的特点和适用场景对于网络工程师和开发者来说至关重要。本文将深入探讨TCP和UDP的核心区别,并分析它们在实际应用中的选择依据。
63 3
|
2月前
|
网络协议 SEO
TCP连接管理与UDP协议IP协议与ethernet协议
TCP、UDP、IP和Ethernet协议是网络通信的基石,各自负责不同的功能和层次。TCP通过三次握手和四次挥手实现可靠的连接管理,适用于需要数据完整性的场景;UDP提供不可靠的传输服务,适用于低延迟要求的实时通信;IP协议负责数据包的寻址和路由,是网络层的重要协议;Ethernet协议定义了局域网的数据帧传输方式,广泛应用于局域网设备之间的通信。理解这些协议的工作原理和应用场景,有助于设计和维护高效可靠的网络系统。
50 4
|
3月前
|
网络协议 网络性能优化 C#
C# 一分钟浅谈:UDP 与 TCP 协议区别
【10月更文挑战第8天】在网络编程中,传输层协议的选择对应用程序的性能和可靠性至关重要。本文介绍了 TCP 和 UDP 两种常用协议的基础概念、区别及应用场景,并通过 C# 代码示例详细说明了如何处理常见的问题和易错点。TCP 适用于需要可靠传输和顺序保证的场景,而 UDP 适用于对延迟敏感且可以容忍一定数据丢失的实时应用。
60 1
|
3月前
|
网络协议 Linux 网络性能优化
Linux C/C++之TCP / UDP通信
这篇文章详细介绍了Linux下C/C++语言实现TCP和UDP通信的方法,包括网络基础、通信模型、编程示例以及TCP和UDP的优缺点比较。
64 0
Linux C/C++之TCP / UDP通信
|
2月前
|
Linux 网络安全 数据安全/隐私保护
Linux 超级强大的十六进制 dump 工具:XXD 命令,我教你应该如何使用!
在 Linux 系统中,xxd 命令是一个强大的十六进制 dump 工具,可以将文件或数据以十六进制和 ASCII 字符形式显示,帮助用户深入了解和分析数据。本文详细介绍了 xxd 命令的基本用法、高级功能及实际应用案例,包括查看文件内容、指定输出格式、写入文件、数据比较、数据提取、数据转换和数据加密解密等。通过掌握这些技巧,用户可以更高效地处理各种数据问题。
139 8
|
2月前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
556 6

热门文章

最新文章