UPD与TCP对比:

UDP是无连接的协议,也不保证可靠交付,只在IP数据报服务之上增加了很少的功能,主要是复用和分用以及差错检测的功能。这适用于要求源主机以恒定速率发送数据,允许网络拥塞时丢失数据,却不允许数据有太大时延的实时应用。
TCP则是面向连接的传输层协议,提供可靠的交付服务。TCP把连接作为最基本的抽象,连接的端点叫做套接字(socket)。每一条TCP连接唯一地被通信两端的两个端点(即套接字对 socket pair)所确定,即每一条TCP连接只能是点对点的 [2]。应用程序在使用TCP协议之前必须先建立TCP连接,在传送数据完毕后必须释放已经建立的TCP连接。TCP提供全双工通讯,允许通信双方的应用进程在任何时候都能发送数据,TCP连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据。与面向报文的UDP不同,TCP是面向字节流的。这里的“流”(stream)指的是流入到进程或者从进程流出的字节序列。虽然应用程序和TCP的交互是一次一个大小不等的数据块,但TCP把应用程序交下来的数据仅仅看成是一连串的无结构的字节流,并不知道所传送的字节流的含义。TCP适用于需要有序、可靠地传输数据流的应用程序。

UPD的使用限制:

在 IPv4 中,IP 首部包含一个 16 位的总长度字段,因此 IPv4 数据包的最大长度为65536 字节,不过这其中包含了 IP 首部(通常为 20 字节)和 UDP 首部(8 字节),所以实际可用于 UDP 数据的最大长度为 65507 字节
然而,在实际网络环境中,由于不同网络设备(如路由器)可能有不同的最大传输单元(MTU,Maximum Transmission Unit),数据包大小可能会受到进一步限制。以太网的 MTU 通常为 1500 字节,如果 UDP 数据包大小超过 MTU,就需要进行分片处理。分片会增加网络传输的复杂性和出错的可能性,因此在实际应用中,为了避免分片,建议将 UDP 数据包大小控制在 MTU 以下。在 UTF - 8 编码下,1500 字节大约能存储 500 个常见的中文。

总结:

1、500字基本满足文字类聊天所需;

代码部分:

package tangzeqi.com.service;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.ObjectUtils;
import tangzeqi.com.project.MyProject;
import tangzeqi.com.stroge.BaseMessage;
import tangzeqi.com.stroge.BaseUser;
import tangzeqi.com.stroge.TextMessage;
import tangzeqi.com.stroge.UPDMessage;
import tangzeqi.com.utils.Md5Utils;
import tangzeqi.com.utils.MessageUtils; import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong; /**
* 局域网广播通讯,UPD
*/
public class UPDService { private volatile boolean doing = false; private volatile AtomicLong msgIndesx = new AtomicLong(0); private final String project;
/**
* 广播模式
*/
private volatile DatagramSocket socket; private final DatagramPacket packet = new DatagramPacket("".getBytes(), "".getBytes().length, InetAddress.getByName("255.255.255.255"), 0); private volatile ConcurrentHashMap<String, UPDInetSocketAddress> addresses = new ConcurrentHashMap<>(); public UPDService(String project) throws SocketException, UnknownHostException {
this.project = project;
} public void start() {
try {
socket = new DatagramSocket();
socket.setBroadcast(true);
scan();
} catch (SocketException e) {
throw new RuntimeException(e);
}
} public void scan() {
if (doing) {
} else {
doing = true;
//感知
MyProject.cache(project).executor.execute(() -> {
MyProject.cache(project).executor.execute(new Scaner(0, 5000));
MyProject.cache(project).executor.execute(new Scaner(5001, 10000));
MyProject.cache(project).executor.execute(new Scaner(10001, 15000));
MyProject.cache(project).executor.execute(new Scaner(15001, 20000));
MyProject.cache(project).executor.execute(new Scaner(20001, 25000));
MyProject.cache(project).executor.execute(new Scaner(25001, 30000));
MyProject.cache(project).executor.execute(new Scaner(30001, 35000));
MyProject.cache(project).executor.execute(new Scaner(35001, 40000));
MyProject.cache(project).executor.execute(new Scaner(40001, 45000));
MyProject.cache(project).executor.execute(new Scaner(45001, 50000));
MyProject.cache(project).executor.execute(new Scaner(50001, 55000));
MyProject.cache(project).executor.execute(new Scaner(55001, 60000));
MyProject.cache(project).executor.execute(new Scaner(60001, 65535));
});
//感知、信息、保活
MyProject.cache(project).executor.execute(() -> {
byte[] receiveBuffer = new byte[1500];
final DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
while (!socket.isClosed()) {
try {
socket.receive(receivePacket);
String receivedData = new String(receivePacket.getData(), 0, receivePacket.getLength(), StandardCharsets.UTF_8);
BaseMessage message = MessageUtils.resolve(receivedData);
if (message.getMessage() instanceof UPDMessage) {
if (check(((UPDMessage) message.getMessage()).getToken())) {
if (!addresses.containsKey(receivePacket.getAddress() + ":" + receivePacket.getPort())) {
MyProject.cache(project).sysMessage("发现::" + receivePacket.getAddress() + ":" + receivePacket.getPort());
}
addresses.put(receivePacket.getAddress() + ":" + receivePacket.getPort(), new UPDInetSocketAddress(receivePacket.getAddress(), receivePacket.getPort()));
}
} else if (message.getMessage() instanceof TextMessage) {
if (msgIndesx.get() < message.getId() && msgIndesx.compareAndSet(msgIndesx.get(), message.getId())) {
MyProject.cache(project).chatMessage(((TextMessage) message.getMessage()).getMessage(), message.getMessage().getName());
}
}
} catch (IOException e) {
} catch (Exception e) {
}
}
});
}
} public <T extends BaseUser> void send(T o) {
byte[] message = new byte[0];
message = JSON.toJSONString(BaseMessage.builder().id(System.nanoTime()).message(o).build()).getBytes(StandardCharsets.UTF_8);
packet.setData(message);
packet.setLength(message.length);
long time = new Date().getTime();
for (UPDInetSocketAddress address : addresses.values()) {
if (address.outTime >= time) {
packet.setSocketAddress(address);
try {
socket.send(packet);
} catch (IOException e) {
}
}
}
} private boolean check(String token) {
if (ObjectUtils.isEmpty(token)) return false;
return Md5Utils.getMD5(new Date().getTime() / 1000000 + "", StandardCharsets.UTF_8.name()).equalsIgnoreCase(token);
} public void shutDowm() {
socket.close();
doing = false;
addresses.clear();
} private class UPDInetSocketAddress extends InetSocketAddress {
public final long outTime = DateUtils.addMinutes(new Date(), 1).getTime(); public UPDInetSocketAddress(int port) {
super(port);
} public UPDInetSocketAddress(InetAddress addr, int port) {
super(addr, port);
} public UPDInetSocketAddress(String hostname, int port) {
super(hostname, port);
}
} private class Scaner implements Runnable {
private final DatagramPacket scanPacket; {
try {
scanPacket = new DatagramPacket("".getBytes(), "".getBytes().length, InetAddress.getByName("255.255.255.255"), 0);
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
} int start;
int end; public Scaner(int start, int end) {
this.start = start;
this.end = end;
} @Override
public void run() {
while (!socket.isClosed()) {
for (int portD = (start + end) / 2, portU = ((start + end) / 2) + 1; portU <= end && portD >= start && !socket.isClosed(); portD--, portU++) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
try {
BaseMessage<BaseUser> build = BaseMessage.builder().message(UPDMessage.builder().token(Md5Utils.getMD5(new Date().getTime() / 1000000 + "", StandardCharsets.UTF_8.name())).build()).build();
String string = JSON.toJSONString(build);
byte[] bytes = string.getBytes(StandardCharsets.UTF_8);
scanPacket.setData(bytes);
scanPacket.setLength(bytes.length);
scanPacket.setPort(portU);
socket.send(scanPacket);
// MyProject.cache(project).config.updconnectStatus(true,"开始感知端口:"+portU);
// MyProject.cache(project).sysMessage("开始感知端口:"+portU);
scanPacket.setPort(portD);
socket.send(scanPacket);
// MyProject.cache(project).config.updconnectStatus(true,"开始感知端口:"+portD);
// MyProject.cache(project).sysMessage("开始感知端口:"+portD);
} catch (IOException e) {
}
}
}
}
} }

代码介绍:

start(): 构建一个基础的UPD类,并设置为广播模式。

InetAddress.getByName("255.255.255.255"):在 IPv4 网络中,255.255.255.255 是一个特殊的 IP 地址,被称作有限广播地址(Limited Broadcast Address)。当数据包被发送到这个地址时,它会在本地网络(也就是当前子网)内进行广播,意味着本地网络中的所有设备都会接收到这个数据包。

scan():开启线程任务,循环向局域网内广播一段有特殊含义的字符串。

Md5Utils.getMD5(new Date().getTime() / 1000000):这是一个简单的小计算,主要参与check()方法中的校验,当其他UPD服务接收到后也会采用同样的算法并与之匹配,如果匹配成功则加入信息组队列。后期可以扩展成任何算法,也可以利用这个模式给房间分组。

UPDMessage:广播信息体

TextMessage:普通聊天信息体

send(): 当需要发送普通聊天信息时,构建TextMessage,并给感知到的有效信息组成员发送信息。

UPDInetSocketAddress:简单封装过的InetSocketAddress,在原基础上加入了失效机制,目前为1分钟后失效,当感知到成员时会更新失效时间,当发送聊天信息时会验证有效性。理论上目前所有端口的感知频率在一分钟以内,所以省略了保活线程,后期可以加上。其实也可以采用本地超时缓存,不过考虑开销问题,这次代码中就不使用了。

效果图(来自本人上架idea插件商城的Wchat):

基于UPD的快速局域网聊天室的更多相关文章

  1. 基于EPOLL模型的局域网聊天室和Echo服务器

    一.EPOLL的优点 在Linux中,select/poll/epoll是I/O多路复用的三种方式,epoll是Linux系统上独有的高效率I/O多路复用方式,区别于select/poll.先说sel ...

  2. 基于TCP/IP的局域网聊天室---C语言

    具备注册账号,群聊,查看在线人员信息,私发文件和接收文件功能,因为每个客户端只有一个属于自己的socket,所以无论客户端是发聊天消息还是文件都是通过这一个socket发送, 这也意味着服务器收发任何 ...

  3. C# 异步通信 网络聊天程序开发 局域网聊天室开发

    Prepare 本文将使用一个NuGet公开的组件技术来实现一个局域网聊天程序,利用组件提供的高性能异步网络机制实现,免去了手动编写底层的困扰,易于二次开发,扩展自己的功能. 在Visual Stud ...

  4. 基于Qt的P2P局域网聊天及文件传送软件设计

    基于Qt的P2P局域网聊天及文件传送软件设计 zouxy09@qq.com http://blog.csdn.net/zouxy09         这是我的<通信网络>的课程设计作业,之 ...

  5. 基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍。最后我们将会实现一个基于Server-Sent Event和Flask简单的在线聊天室。

    基于Server-Sent Event的简单聊天室 Web 2.0时代,即时通信已经成为必不可少的网站功能,那实现Web即时通信的机制有哪些呢?在这门项目课中我们将一一介绍.最后我们将会实现一个基于S ...

  6. 基于LINUX的多功能聊天室

    原文:基于LINUX的多功能聊天室 基于LINUX的多功能聊天室 其实这个项目在我电脑已经躺了多时,最初写完项目规划后,我就认认真真地去实现了它,后来拿着这个项目区参加了面试,同样面试官也拿这个项目来 ...

  7. 基于 OpenResty 实现一个 WS 聊天室

    基于 OpenResty 实现一个 WS 聊天室 WebSocket WebSocket 协议分析 WebSocket 协议解决了浏览器和服务器之间的全双工通信问题.在WebSocket出现之前,浏览 ...

  8. 基于Linux的TCP网络聊天室

    1.实验项目名称:基于Linux的TCP网络聊天室 2.实验目的:通过TCP完成多用户群聊和私聊功能. 3.实验过程: 通过socket建立用户连接并传送用户输入的信息,分别来写客户端和服务器端,利用 ...

  9. JAVA基础知识之网络编程——-基于TCP通信的简单聊天室

    下面将基于TCP协议用JAVA写一个非常简单的聊天室程序, 聊天室具有以下功能, 在服务器端,可以接受客户端注册(用户名),可以显示注册成功的账户 在客户端,可以注册一个账号,并用这个账号发送信息 发 ...

  10. 《基于Node.js实现简易聊天室系列之详细设计》

    一个完整的项目基本分为三个部分:前端.后台和数据库.依照软件工程的理论知识,应该依次按照以下几个步骤:需求分析.概要设计.详细设计.编码.测试等.由于缺乏相关知识的储备,导致这个Demo系列的文章层次 ...

随机推荐

  1. react 趟坑

    最近一直在做react项目,发现一个bug,困扰了我两天. Can't perform a React state update on an unmounted component. This is ...

  2. linux:用户管理

    用户账号添加.删除.修改以及用户密码的管理 用户组的管理 涉及三个文件: /etc/passwd    :存储用户的关键信息 /etc/group :存储用户组的关键信息 /etc/shadow :存 ...

  3. vim系列-文本操作篇

    基数行与偶数行分组 使用Vim的替换命令,可以轻松地将基数行和偶数行分组: %s/\(^.*$\)\n\(^.*$\)/\1 \2/g 然后,删除所有的基数行: %s/^.*$\n\(^.*$\)/\ ...

  4. oracle使用存储过程返回游标实现报表查询

    最近在oracle中通过存储过程实现一个报表查询,查询涉及到数据计算这里使用了临时表和存储过程实现输出查询,java接受游标变量返回结果集 第一步.创建统计使用的临时表 CREATE GLOBAL T ...

  5. 0511-Properties集合

    package A10_IOStream; import java.io.*; import java.util.Properties; import java.util.Set; /* java.u ...

  6. RocketMQ原理—5.高可用+高并发+高性能架构

    大纲 1.RocketMQ的整体架构与运行流程 2.基于NameServer管理Broker集群的架构 3.Broker集群的主从复制架构 4.基于Topic和Queue实现的数据分片架构 5.Bro ...

  7. 天翼云边缘安全加速平台亮相2023亚太内容分发大会暨CDN峰会

    6月29日,第十二届亚太内容分发大会暨CDN峰会在北京召开.大会聚集了行业领/袖.专家和学者,深度探讨CDN的技术发展.应用与未来发展趋势,会上还公布了2023边缘加速创新企业榜单,中国电信天翼云成功 ...

  8. 【Java基础总结】集合框架

    集合和数组的区别 集合只存储对象,长度是可变的: 数组既可以存储基本数据类型,又可以存储对象,但长度是固定的. 1. Collection接口 代码演示 1 List<String> c1 ...

  9. 如何在啥也不懂的情况下将你的公众号接入DeepSeek或其它大模型

    如何在啥也不懂的情况下将你的公众号接入DeepSeek或其它大模型 前言 最近国产大模型的"顶流"DeepSeek可谓是红得发紫,朋友圈刷屏的AI神回复.公众号爆款推文,都少不了它 ...

  10. Ansible 数百台批量操作前期准备工作

    Ansible 数百台批量操作前期准备工作 背景: 当前有100台服务器在同一个内网,需要统一部署业务程序并且对主机修改主机名,只提供了一个文档host_user.txt,内容 " IP 用 ...