【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.textchar 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.textsprintf(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); //2if (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  Qchar name[32];  //用户名char text[128]; //消息正文
} MSG_t;link_list_t createLink();

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PIP换源的全面指南

##概述 在Python的世界里&#xff0c;pip是不可或缺的包管理工具&#xff0c;它帮助开发者安装和管理Python软件包。然而&#xff0c;由于网络条件或服务器位置等因素&#xff0c;直接使用默认的pip源有时会遇到下载速度慢或者连接不稳定的问题。这时&#xff0c;更换pip源到一…

用Conda配置 Stable Diffusion WebUI 1.9.4

用Conda配置 Stable Diffusion WebUI 1.9.4 本文主要讲解: 如何用Conda搭建Stable Diffusion WebUI 1.9.4环境,用Conda的方式安装,不需要单独去安装Cuda了。 1. 安装miniconda https://docs.anaconda.com/free/miniconda/index.html 2. 搭建虚拟环境 激活python虚拟环境…

PFC电路中MOS管的选取2

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

AI工具,如何通过 GPT-4o 提高工作效率

文章目录 引言一、理解GPT-4o及其功能二、如何利用GPT-4o提高工作效率1. 代码生成与优化2. 自动化测试与调试3. 技术文档撰写与知识管理 三、实际案例与成功应用1. GitHub 协作与问题解决2. 敏捷开发与迭代优化 四、GPT-4o的挑战与应对策略五、未来展望与发展方向六、结论 &…

Django QuerySet对象,filter()方法

filter()方法 用于实现数据过滤功能&#xff0c;相当于sql语句中的where子句。 filter(字段名__exact10) 或 filter(字段名10)类似sql 中的 10 filter(字段名__gt10) 类似SQL中的 >10 filter(price__lt29.99) 类似sql中的 <29.99 filter(字段名__gte10, 字段名__lte20…

开始尝试从0写一个项目--前端(一)

基础项目构建 创建VUE初始工程 确保自己下载了node.js和npm node -v //查看node.js的版本 npm -v //查看npm的版本 npm i vue/cli -g //安装VUE CLI 创建 以管理员身份运行 输入&#xff1a;vue ui 就会进入 点击创建 自定义项目名字&#xff0c;选择npm管理 结…

14-23 剑和远方3 - 深度神经网络的主要架构

神经网络架构 神经网络的架构决定了这些网络如何运行&#xff0c;这是执行各种任务和扩展神经网络应用的关键因素&#xff0c;主要有两种方法&#xff1a;前馈神经网络和反馈神经网络。在本文中&#xff0c;在彻底分析每种方法之后&#xff0c;我们将对这两种架构进行深入比较…

redhat7.x 升级openssh至openssh-9.8p1

1.环境准备&#xff1a; OS系统&#xff1a;redhat 7.4 2.备份配置文件&#xff1a; cp -rf /etc/ssh /etc/ssh.bak cp -rf /usr/bin/openssl /usr/bin/openssl.bak cp -rf /etc/pam.d /etc/pam.d.bak cp -rf /usr/lib/systemd/system /usr/lib/systemd/system.bak 3.安装…

【ai】pycharm添加本地解释器

解释器右键可以重命名 系统的解释器竟然安装了4个 可以先使用python虚拟环境中的解释器 虚拟环境虽然是属于其他的项目的&#xff0c;但是看起来也可以给自己的当前项目用&#xff1a; 添加了 别的项目里虚拟环境的解释器

axios的底层ajax,XMLHttpRequest原理解释及使用方法

定义 ajax全称asychronous JavaScript and XML 意思是异步的 JavaScript和xml&#xff0c; 也就是通过javascript创建XMLHttpRequest &#xff08;xhr&#xff09;对象与服务器进行通信 步骤 创建实例对象&#xff0c;初始请求方法和url&#xff0c;设置监听器监听请求完成…

C# 快速排序算法的详细讲解

目录 一、前言 二、例子 三、快速排序算法图片讲解 四、快速排序算法代码 五、纯净代码 一、前言 用比较好懂的方式讲一下快速排序算法。 二、例子 如果我有一堆钱&#xff0c;想数清楚&#xff0c;最快的方案是什么&#xff1f; 图1 一堆钱 答&#xff1a;先分类&…

Python | Leetcode Python题解之第213题打家劫舍II

题目&#xff1a; 题解&#xff1a; class Solution:def rob(self, nums: List[int]) -> int:def robRange(start: int, end: int) -> int:first nums[start]second max(nums[start], nums[start 1])for i in range(start 2, end 1):first, second second, max(fi…

谷粒商城学习笔记-16-人人开源搭建后台管理系统

文章目录 一&#xff0c;克隆前/后端代码1&#xff0c;克隆前端工程renren-fast-value2&#xff0c;克隆后端工程renren-fast 二&#xff0c;集成后台管理系统的后端代码三&#xff0c;启动后台管理系统四&#xff0c;前端系统的安装和运行1&#xff0c;下载安装VSCode2&#x…

react v18 less使用(craco)

方案一、弹出配置&#xff08;不推荐&#xff09; 安装依赖&#xff1a;yarn add less less-loader 首先 执行 yarn eject 弹出配置项文件&#xff08;注意&#xff1a;弹出配置不可逆&#xff01;&#xff09; 在 config 文件夹中 找到 webpack.config.js&#xff0c;在如图…

18_特征金字塔网络FPN结构详解

1.1 简介 在深度学习领域&#xff0c;尤其是计算机视觉和目标检测任务中&#xff0c;Feature Pyramid Networks (FPN) 是一种革命性的架构设计&#xff0c;它解决了多尺度特征检测和融合的关键问题。FPN最初由何凯明等人在2017年的论文《Feature Pyramid Networks for Object …

Redis官方可视化管理工具

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl RedisInsight是一个Redis可视化工具&#xff0c;提供设计、开发和优化 Redis 应用程序的功能。RedisInsight分为免费的社区版和一个付费的企业版&#xff0c;免费版具有基本…

cs231n 作业3

使用普通RNN进行图像标注 单个RNN神经元行为 前向传播&#xff1a; 反向传播&#xff1a; def rnn_step_backward(dnext_h, cache):dx, dprev_h, dWx, dWh, db None, None, None, None, Nonex, Wx, Wh, prev_h, next_h cachedtanh 1 - next_h**2dx (dnext_h*dtanh).dot(…

lua中判断2个表是否相等

当我们获取 table 长度的时候无论是使用 # 还是 table.getn 其都会在索引中断的地方停止计数&#xff0c;而导致无法正确取得 table 的长度&#xff0c;而且还会出现奇怪的现象。例如&#xff1a;t里面有3个元素&#xff0c;但是因为最后一个下表是5和4&#xff0c;却表现出不一…

大数据信用做贷前风控一般有哪些好处?

随着大数据技术的不断发展&#xff0c;利用大数据信用进行贷前风控已经成为越来越受欢迎的一种方式。大数据信用是指通过分析大量的数据&#xff0c;对个人的信用状况进行评估&#xff0c;从而为金融机构提供更加准确、可靠的风控依据。使用大数据信用做贷前风控有很多好处&…

【密码学】密码学中的四种攻击方式和两种攻击手段

在密码学中&#xff0c;攻击方式通常指的是密码分析者试图破解加密信息或绕过安全机制的各种策略。根据密码分析者对明文、密文以及加密算法的知识程度&#xff0c;攻击可以分为以下四种基本类型&#xff1a; 一、四种攻击的定义 &#xff08;1&#xff09;唯密文攻击(COA, C…