当前位置: 首页 > news >正文

C#实现本地服务器客户端私聊通信

(一)需求

       在游戏中我们经常能够看到玩家与玩家之间可以进行私聊,在QQ或微信中最基本的功能就是用户与用户之间的通信。抽象成计算机网络,就是两个客户端通过服务器进行私聊通信,两个客户端可以互相看到对方发送过来的信息。这种两个客户端的私聊通信是如何实现的呢?在本篇文章我们就来探讨一下。

(二)解决思路

       这个需求的重点部分在于网络通信,需要我们掌握基本的计算机网络通信知识,具体到每种编程语言又有对应的API。如果把这个需求抽象到计算机网络中,我们就可以理解成两个客户端向服务器发送信息,服务器接收信息后又把信息发送给另一个客户端。这样,一个客户端就可以接收到另一个客户端发送的信息了。

(三)设计思路

       服务器基于本地服务器开发,通过一个单独的C#控制台项目模拟,编程语言使用C#,客户端通过Unity3D构建GUI并编写客户端脚本。两个客户端则通过打开两个Unity3D项目的可执行文件进行模拟,客户端的GUI需要有调试面板、客户端名称下拉菜单、连接和断开连接按钮、消息显示面板、消息输入框和消息发送按钮等。

(四)代码实现

        由于代码中引用了自定义的网络通信共享库NetShare,关于NetShare请阅读这篇文章。


       客户端

using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
using UnityEngine.UI;
/*
自定义网络通信共享库NetShare,包括通用数据包DataPacket、私聊频道服务器数据包PSDataPacket、
服务器数据包ServerDataPacket和私聊频道客户端数据包PCDataPacket等。
*/
using NetShare;
using System.Threading;
using System.Linq;//私聊频道客户端
public class PersonalChannelClient : MonoBehaviour
{public Text BaseInfo;//显示Socket连接基本信息的文本public Text EchoContents;//Socket回显信息的文本public Text ChatContents;//聊天信息的文本public Dropdown Friends;//目标客户端名称下拉菜单public Dropdown ClientMenu;//客户端名称下拉菜单public Button Connect;//连接按钮public Button DisConnect;//断开连接按钮public InputField SendInput;//聊天消息输入框public Button Send;//聊天信息发送按钮static string ipAddressStr;//IP地址字符串static int port;//端口static IPAddress iPAddress;//IP地址对象static IPEndPoint iPEndPoint;//IP端点对象string clientName, sendStr;//客户端名称和发送信息字符串Socket currentClientSocket;//当前客户端Socketbool isLockSend;//是否锁定聊天信息发送按钮byte[] buffer;//消息接收缓冲区Queue<string> echoContentQueue, chatContentQueue;//回显信息队列和聊天信息队列PCDataPacket dataPacket;//通用数据包List<string> desClientNames;//目标客户端名称合集//反映Socket是否与服务器有效连接的属性bool isConnected{get{if (currentClientSocket == null) return false;return !currentClientSocket.Poll(10, SelectMode.SelectRead) && currentClientSocket.Connected;}}void Start(){//初始化ipAddressStr = "127.0.0.1";clientName = ClientMenu.options.Count > 0 ? ClientMenu.options[0].text : "";port = 5500;iPAddress = IPAddress.Parse(ipAddressStr);iPEndPoint = new IPEndPoint(iPAddress, port);buffer = new byte[1024];echoContentQueue = new Queue<string>();chatContentQueue = new Queue<string>();desClientNames = new List<string>() { "None" };//为UI控件添加监听事件ClientMenu.onValueChanged.AddListener((index) =>{clientName = ClientMenu.options[index].text;});Connect.onClick.AddListener(() =>{Thread thread = new Thread(new ThreadStart(ConnectDeal));thread.Start();});DisConnect.onClick.AddListener(() =>{Thread thread = new Thread(new ThreadStart(DisConnectDeal));thread.Start();});Send.onClick.AddListener(() =>{sendStr = SendInput.text;Thread thread = new Thread(new ThreadStart(SendDeal));thread.Start();SendInput.text = string.Empty;});}void Update(){//不断更新Socket基本信息BaseInfo.text = $"ClientName:{clientName}" +string.Format("\nSocketHashCode:{0}", currentClientSocket == null ? "None" : currentClientSocket.GetHashCode().ToString()) +$"\nisLock:{isLockSend}" +string.Format("\nPoll:{0}", currentClientSocket == null ? "None" : (!currentClientSocket.Poll(10, SelectMode.SelectRead)).ToString()) +string.Format("\nIsConnected:{0}", currentClientSocket == null ? "False" : currentClientSocket.Connected.ToString());//更新回显信息if (echoContentQueue.Count > 0){while (echoContentQueue.Count > 0){SetEchoContents(echoContentQueue.Dequeue());}}//更新聊天信息if (chatContentQueue.Count > 0){while (chatContentQueue.Count > 0){SetChatContents(chatContentQueue.Dequeue());}}//更新目标客户端名称下拉菜单if (desClientNames?.Count > 0){Friends.AddOptions(desClientNames);desClientNames.Clear();}}//设置回显信息相关UI的内容void SetEchoContents(string text){EchoContents.text += text;}//设置聊天信息相关UI的内容void SetChatContents(string text){ChatContents.text += text;}//执行逻辑:Socket异步连接处理void ConnectDeal(){echoContentQueue.Enqueue($"\n客户端{clientName}正在请求服务器连接...");Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);clientSocket.BeginConnect(iPEndPoint, ConnectCallback, clientSocket);}//执行逻辑:Socket异步断开连接处理void DisConnectDeal(){echoContentQueue.Enqueue($"\n客户端{clientName}正在断开与服务器的连接...");if (isConnected){currentClientSocket.Shutdown(SocketShutdown.Both);currentClientSocket.BeginDisconnect(false, DisConnectCallback, currentClientSocket);}else echoContentQueue.Enqueue($"\n客户端{clientName}未与服务器建立连接,无法进行断开连接的操作...");}//执行逻辑:Socket异步接收信息处理void ReceiveDeal(){echoContentQueue.Enqueue($"\n客户端{clientName}开始监听服务器响应...");currentClientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, currentClientSocket);}//执行逻辑:Socket异步发送信息处理void SendDeal(){if (!isLockSend && !string.IsNullOrEmpty(sendStr)){dataPacket.mContent = sendStr;string v_desClientName = Friends.options[Friends.value].text;if (!v_desClientName.Equals("None")) dataPacket.mDestinationClientName = v_desClientName;byte[] bytes = dataPacket.ToBytes();currentClientSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallback, currentClientSocket);}}//执行逻辑:Socket异步连接处理回调void ConnectCallback(IAsyncResult ar){try{Socket socket = ar.AsyncState as Socket;socket.EndConnect(ar);currentClientSocket = socket;if (isConnected){dataPacket = new PCDataPacket(){mLocalEndPointStr = socket.LocalEndPoint.ToString(),mClientName = clientName,mDestinationClientName = string.Empty,mContent = $"成功与服务器建立连接!"};byte[] bytes = dataPacket.ToBytes();socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallback, currentClientSocket);isLockSend = false;echoContentQueue.Enqueue($"\n<color=orange>客户端{clientName}与服务器连接成功!</color>");ReceiveDeal();}else echoContentQueue.Enqueue($"\n<color=red>客户端{clientName}与服务器连接失败!</color>");}catch (SocketException se){echoContentQueue.Enqueue($"\n<color=red>客户端{clientName}与服务器连接失败!</color>\n错误信息:{se.Message}");}}//执行逻辑:Socket异步断开连接处理回调void DisConnectCallback(IAsyncResult ar){try{isLockSend = true;Socket socket = ar.AsyncState as Socket;socket.EndDisconnect(ar);dataPacket = null;echoContentQueue.Enqueue($"\n<color=orange>客户端{clientName}与服务器断开连接操作成功!</color>");}catch (SocketException se){echoContentQueue.Enqueue($"\n客户端{clientName}与服务器断开连接操作失败!\n错误信息:{se.Message}");}}//执行逻辑:Socket异步发送信息处理回调void SendCallback(IAsyncResult ar){try{Socket socket = ar.AsyncState as Socket;socket.EndSend(ar);echoContentQueue.Enqueue($"\n客户端{clientName}向服务器发送了一条消息!");}catch (SocketException se){echoContentQueue.Enqueue($"\n客户端{clientName}向服务器发送信息操作失败!\n错误信息:{se.Message}");}}//执行逻辑:Socket异步接收信息处理回调void ReceiveCallback(IAsyncResult ar){try{Socket socket = ar.AsyncState as Socket;int count = socket.EndReceive(ar);DataPacket dataPacket = DataPacket.ToObject<DataPacket>(buffer.Take(count).ToArray());if (dataPacket is PSDataPacket psdp && psdp.mClientNames?.Length > 0){desClientNames.Clear();foreach (string name in psdp.mClientNames){if (Friends.options.FindIndex((od) => od.text.Equals(name)) == -1) desClientNames.Add(name);}}else if (dataPacket is ServerDataPacket sdp){string v_res = sdp.mContent;if (!string.IsNullOrEmpty(v_res)) chatContentQueue.Enqueue("\n" + v_res);}//若Socket连接有效则继续接收消息if (isConnected)socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, socket);}catch (SocketException se){echoContentQueue.Enqueue($"\n客户端{clientName}接收服务器消息失败!\n错误信息:{se.Message}");}}
}

        服务器

using System.Net.Sockets;
using System.Net;/*
自定义网络通信共享库NetShare,其中包括了私聊频道服务器数据包PSDataPacket、通用数据包DataPacket、
服务器数据包ServerDataPacket和私聊频道客户端数据包PCDataPacket等。*/using NetShare;namespace UnityServer
{//私聊频道服务器internal class PersonalChannelServer{private static string ipAddress = "127.0.0.1";//IP地址字符串private static int port = 5500;//端口private static int maxConnect = 20;//最大连接数private static byte[] buffer = new byte[1024];//消息缓冲区//客户端Socket合集,key为IPEndPoint字符串,value为服务器为客户端分配的Socketprivate static Dictionary<string, Socket> clients = new Dictionary<string, Socket>();private static Socket? serverSocket;//服务器Socket//客户端键值对,key为客户端名称,value为IPEndPoint字符串private static Dictionary<string, string> clientKVs = new Dictionary<string, string>();private static void Main(string[] args){Thread thread = new Thread(new ThreadStart(ServerDeal));thread.Start();Console.ReadLine();}//判断Socket是否进行有效连接private static bool IsConnected(Socket socket){if (socket == null) return false;return !socket.Poll(10, SelectMode.SelectRead) && socket.Connected;}//执行逻辑:服务器处理private static void ServerDeal(){serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);IPAddress v_ipAddress = IPAddress.Parse(ipAddress);serverSocket.Bind(new IPEndPoint(v_ipAddress, port));serverSocket.Listen(maxConnect);Console.WriteLine($"开启服务器[{serverSocket.LocalEndPoint}]...");serverSocket.BeginAccept(AcceptCallback, null);}//执行逻辑:Socket异步接收消息private static void ReceiveDeal(object? clientSocket){Console.WriteLine("********************");if (clientSocket == null) return;Socket? v_clientSocket = clientSocket as Socket;if (v_clientSocket == null) return;Console.WriteLine("接收到客户端的连接请求!");if (IsConnected(v_clientSocket))v_clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, v_clientSocket);}//添加客户端Socket到客户端Socket合集private static void AddClient(Socket clientSocket){if (clientSocket == null) return;EndPoint? endPoint = clientSocket.RemoteEndPoint;if (endPoint != null){string? v_endPointStr = endPoint.ToString();if (v_endPointStr != null) clients[v_endPointStr] = clientSocket;}}//向所有客户端发送指定信息private static void SendToAll(PSDataPacket dataPacket){if (dataPacket == null) return;byte[] bytes = dataPacket.ToBytes();foreach (Socket clientSocket in clients.Values){if (IsConnected(clientSocket)){Thread thread = new Thread(() =>{clientSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallback, clientSocket);});thread.Start();}}}//向指定的客户端发送服务器数据包private static void SendTo(ServerDataPacket dataPacket, Socket? destinationSocket){if (dataPacket == null || destinationSocket == null) return;byte[] bytes = dataPacket.ToBytes();if (IsConnected(destinationSocket)){Thread thread = new Thread(() =>{destinationSocket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, SendCallback, destinationSocket);});thread.Start();}}//Socket监听请求回调private static void AcceptCallback(IAsyncResult ar){try{if (serverSocket != null){Socket clientSocket = serverSocket.EndAccept(ar);AddClient(clientSocket);Thread thread = new Thread(new ParameterizedThreadStart(ReceiveDeal));thread.Start(clientSocket);serverSocket.BeginAccept(AcceptCallback, null);}}catch (SocketException se){Console.WriteLine("AcceptException:" + se.Message);}}//Socket发送信息回调private static void SendCallback(IAsyncResult ar){try{Socket? clientSocket = ar.AsyncState as Socket;if (clientSocket != null) clientSocket.EndSend(ar);}catch (SocketException se){Console.WriteLine("SendException:" + se.Message);}}//Socket接收信息回调private static void ReceiveCallback(IAsyncResult ar){try{Socket? clientSocket = ar.AsyncState as Socket;if (clientSocket != null){int bytesCount = clientSocket.EndReceive(ar);PCDataPacket? dataPacket = DataPacket.ToObject<PCDataPacket>(buffer.Take(bytesCount).ToArray());if (dataPacket != null){if (clientSocket.RemoteEndPoint != null){string? v_endPointStr = clientSocket.RemoteEndPoint.ToString();if (!string.IsNullOrEmpty(v_endPointStr)){clientKVs[dataPacket.mClientName] = v_endPointStr;SendToAll(new PSDataPacket(){mClientNames = clientKVs.Keys.ToArray()});}}string v_content = $"客户端{dataPacket.mClientName}:{dataPacket.mContent}";Socket? destinationSocket;string? endPointStr;clientKVs.TryGetValue(dataPacket.mDestinationClientName, out endPointStr);if (!string.IsNullOrEmpty(endPointStr) && clients.TryGetValue(endPointStr, out destinationSocket))SendTo(new ServerDataPacket() { mContent = v_content }, destinationSocket);}if (IsConnected(clientSocket))clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, clientSocket);}}catch (SocketException se){Console.WriteLine("ReceiveException:" + se.Message);}}}
}

(五)测试

       测试流程大概是先启动服务器,然后启动三个客户端,三个客户端分别以A、B、C的名称作为客户端名称与服务器建立连接,连接后再由客户端A、B、C分别向服务器发送信息,通过观察三个客户端的消息面板来确定测试结果,这里之所以启动三个客户端是为了进行对比测试,以区分多客户端的同频道通信,具体测试流程请观看下列视频:

本地服务器客户端单独通信

(六)总结

       在服务器端,我们通过一个C#控制台项目来模拟服务器后台,服务器与客户端具有类似的功能,同样具有发送、接收消息的功能,不同的是服务器具有监听客户端连接的功能,而客户端具有向服务器发送连接请求的功能,本质上这些都是通过Socket实现的功能,人为划分成服务器端和客户端。在客户端我们通过GUI将用户的操作进行可视化构建,实现了回显、客户端名称选择、连接、断开连接、发送和显示消息等基本交互。

       为了模拟多客户端并发操作,所有功能我们都采用了异步的方式启动,对于真正的网络通信而言,这对我们来说才刚刚开始,不过通过这个案例也让我们了解了基本的网络通信流程。

如果这篇文章对你有帮助,请给作者点个赞吧!

相关文章:

C#实现本地服务器客户端私聊通信

&#xff08;一&#xff09;需求 在游戏中我们经常能够看到玩家与玩家之间可以进行私聊&#xff0c;在QQ或微信中最基本的功能就是用户与用户之间的通信。抽象成计算机网络&#xff0c;就是两个客户端通过服务器进行私聊通信&#xff0c;两个客户端可以互相看到对方发送过来的信…...

PyTorch 之 Dataset 类入门学习

PyTorch 之 Dataset 类入门学习 Dataset 类简介 PyTorch 中的 Dataset 类是一个抽象类&#xff0c;用来表示数据集。通过继承 Dataset 类可以进行自定义数据集的格式、大小和其它属性&#xff0c;供后续使用&#xff1b; 可以看到官方封装好的数据集也是直接或间接的继承自 …...

Java update scheduler

引言 Java 更新调度器是 Java 中的一个特性&#xff0c;可以自动化 Java 应用程序的更新过程。它提供了一种方便的方式来安排 Java 应用程序的更新&#xff0c;确保其与最新的功能、错误修复和安全补丁保持同步。本文将深入介绍如何使用 Java 更新调度器&#xff0c;并解释它对…...

常见树种(贵州省):006栎类

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、麻栎 …...

拓扑排序-

有向无环图是拓扑排序 拓扑排序将图中所有的顶点排成一个线性序列&#xff0c;使得所有的有向边均从序列的前面指向后面。 拓扑排序使用深度优先搜索来实现&#xff0c;图中有环则无法进行拓扑排序 一个有向图&#xff0c;如果图中有入度为0的点&#xff0c;就把这个点删掉…...

Oracle数据库如何定位trace file位置

用一个示例来说明吧。 在导入master key时&#xff0c;出现错误&#xff1a; ADMINISTER KEY MANAGEMENTIMPORT KEYS WITH SECRET "my_secret"FROM /tmp/export.expIDENTIFIED BY keypwd5 WITH BACKUP; ADMINISTER KEY MANAGEMENT * ERROR at line 1: ORA-46655…...

电脑盘符错乱,C盘变成D盘怎么办?

在一些特殊情况下&#xff0c;磁盘盘符会出现错乱&#xff0c;C盘可能会变成D盘。那么&#xff0c;这该怎么办呢&#xff1f;下面我们就来了解一下。 通过磁盘管理更改盘符 磁盘管理是Windows自带的工具&#xff0c;它位于“计算机管理”的控制台中。管理硬盘及其所包含的卷或…...

Android WMS——客户端输入事件处理(十九)

前面的文章我们介绍了 WMS 中的输入服务的启动及事件处理,这一篇我们来看一下客户端对输入事件的处理。 一、事件初始化 事件的初始化就是在添加窗口的过程。 1、ViewRootImpl 源码位置:/frameworks/base/core/java/android/view/ViewRootImpl.java public void setView(…...

Python基础学习__测试报告

# 使用pycharm生成报告:只有在单独执行一个TestCase文件时可以生成,使用TestSuite等就不能用了 # 使用第三方的测试报告:例如:HTMLTestRunner第三方类库 #使用HTMLTestRunner这个执行对象# 1.获取第三方的测试运行类Runner模块(一个py文件),将其放在代码目录下 # 2.导包:unitte…...

bclinux aarch64 ceph 14.2.10 云主机 4节点 fio

ceph -s 由于是基于底层分布式存储的云主机&#xff0c;数据仅供参考 本地云盘性能 direct1 1M读取 IOPS134, BW134MiB/s [rootceph-client rbd]# cd / [rootceph-client /]# fio -filenamefio.bin -direct1 -iodepth 128 -thread -rwread -ioenginelibaio -bs1M -size10G -n…...

智能座舱架构与芯片- (14) 测试篇 上

一、 验证平台概要 1.1 测试软件方法论 “软件定义汽车” 的时代&#xff0c;软件在整车制造中的重要性日渐凸显。但不同于其他行业的软件开发&#xff0c;汽车行业有自己独特的软件开发要求。首先是需求严谨、需求层次复杂、需要通过专业的工具进行管理&#xff1b;其次开发…...

【Django-DRF用法】多年积累md笔记,第3篇:Django-DRF的序列化和反序列化详解

本文从分析现在流行的前后端分离Web应用模式说起&#xff0c;然后介绍如何设计REST API&#xff0c;通过使用Django来实现一个REST API为例&#xff0c;明确后端开发REST API要做的最核心工作&#xff0c;然后介绍Django REST framework能帮助我们简化开发REST API的工作。 全…...

Redis主从复制,哨兵和Cluster集群

主从复制&#xff1a; 主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。主从复制主要实现了数据的多机备份&#xff08;和同步&#xff09;&#xff0c;以及对于读操作的负载均衡和简单的故障恢复。 缺陷&#xff1a;故障恢复无法自动化…...

Linux嵌入式I2C协议笔记

硬件&#xff1a; 1.I2C结构 在一个SOC中有一个或者多个I2C控制器&#xff0c;一个I2C控制器可以连接一个或多个I2C设备。 I2C总线需要两条线&#xff0c;时钟线SCL和数据线SDA 2.I2C传输数据格式 开始信号&#xff08;S&#xff09;&#xff1a;SCL为高电平时&#xff0c;S…...

科技的成就(五十三)

503、任天堂首次公开 Switch 2016 年 10 月 20 日&#xff0c;任天堂首次公开 Switch 正式名称及造型。Switch 是任天堂推出的混合型游戏机&#xff0c;可作为家用游戏机&#xff0c;也可作为便携式掌机。Switch 在开发过程中就以代号 NX 而闻名&#xff0c;成为当年的现象级产…...

Ubuntu22.04 编译 AOSP

在 Ubuntu 22.04 系统上搭建环境编译 AOSP(Android Open Source Project)需要进行以下步骤: 1, 更新系统:首先,确保您的 Ubuntu 22.04 系统已经更新到最新版本。可以使用以下命令进行系统更新: sudo apt update sudo apt upgrade2,安装必要的软件包:AOSP 编译需要一些…...

【计算机网络】多路复用的三种方案

文章目录 1. selectselect函数select的工作特性select的缺点 2. pollpoll函数poll与select的对比 3. epollepoll的三个接口epoll的工作原理epoll的优点LT和ET模式epoll的应用场景 &#x1f50e;Linux提供三种不同的多路转接&#xff08;又称多路复用&#xff09;的方案&#xf…...

供应链和物流的自动化新时代

今天&#xff0c;当大多数人想到物流自动化时&#xff0c;他们会想到设备。机器人、无人机和自主卡车运输在大家的谈话中占主导地位。全自动化仓库的视频在网上流传&#xff0c;新闻主播们为就业问题绞尽脑汁。这种炒作是不完整的&#xff0c;它错过了供应链和物流公司的机会。…...

Python与ArcGIS系列(九)自定义python地理处理工具

目录 0 简述1 创建自定义地理处理工具2 创建python工具箱0 简述 在arcgis中可以进行自定义工具箱,将脚本嵌入到自定义的可交互窗口工具中。本篇将介绍如何利用arcpy实现创建自定义地理处理工具以及创建python工具箱。 1 创建自定义地理处理工具 在arctoolbox中的自定义工具箱…...

Nginx部署前端项目

Nginx部署前端项目 1.在nginx官网http://nginx.org/en/download.html &#xff0c;下载稳定版本&#xff1a; 2.解压后&#xff0c;点击根目录中的nginx.exe即可启动Nginx&#xff0c;或是在nginx安装目录中启动cmd并输入以下命令启动&#xff1a; nginx.exe 或 start nginx3…...

告别强制登录!保姆级教程:在Mac/Windows上降级Postman到9.31.28,完整恢复Runner测试功能

告别强制登录&#xff01;保姆级教程&#xff1a;在Mac/Windows上降级Postman到9.31.28&#xff0c;完整恢复Runner测试功能 Postman作为API开发者的瑞士军刀&#xff0c;其强制登录策略让不少用户感到困扰。特别是当我们需要快速验证接口限流策略或在内网环境调试时&#xff0…...

FanControl终极指南:免费开源Windows风扇控制软件完全配置教程

FanControl终极指南&#xff1a;免费开源Windows风扇控制软件完全配置教程 【免费下载链接】FanControl.Releases This is the release repository for Fan Control, a highly customizable fan controlling software for Windows. 项目地址: https://gitcode.com/GitHub_Tre…...

llm-x:一站式大语言模型本地部署与管理工具详解

1. 项目概述&#xff1a;一个为大型语言模型量身定制的“瑞士军刀”最近在折腾大语言模型&#xff08;LLM&#xff09;本地部署和推理的朋友&#xff0c;估计都绕不开一个核心痛点&#xff1a;模型文件的管理。从Hugging Face上下载的模型&#xff0c;动辄几个G甚至几十个G&…...

告别繁琐配置!Android Studio 2023.9 + Chaquopy 14.0.2 保姆级Python环境搭建教程

Android Studio 2023.9 Chaquopy 14.0.2&#xff1a;零基础Python混合开发实战指南 第一次在Android项目中集成Python环境时&#xff0c;我盯着Gradle报错的红色提示整整两小时。直到发现Chaquopy这个神器&#xff0c;才发现原来只需要5分钟就能完成配置——前提是避开那些新…...

【收藏级】2026年大模型学习避坑手册:小白零门槛入门,程序员高效进阶(实战向)

2026年&#xff0c;大模型早已从“前沿概念”走进日常开发和职场&#xff0c;成为小白转行、程序员提升竞争力的核心抓手。但随之而来的是&#xff0c;越来越多人陷入“学了就忘、练了不会、懂了不用”的困境——刷了几十节课程、记了上百个名词&#xff0c;却依然做不出一个能…...

分布式数据库读操作一致性

问题描述这张图片直观地展示了分布式事务中一个非常经典且棘手的痛点&#xff1a;全局读原子性&#xff08;Global Read Atomicity&#xff09; 缺失导致的 “部分可见性” 问题。 通俗点说&#xff0c;它反映了在分布式环境下&#xff0c;即便使用了 XA 协议&#xff0c;如果不…...

别再只会用插件了!手把手教你用Vue3+TypeScript从零撸一个九宫格抽奖组件

从零构建高定制化九宫格抽奖组件&#xff1a;Vue3与TypeScript深度实践 每次营销活动季来临&#xff0c;那些千篇一律的抽奖插件总让人感到审美疲劳。当设计师拿出充满品牌特色的交互稿&#xff0c;而现有插件无法实现时&#xff0c;你是否也经历过在CSS hack和API限制之间挣扎…...

创业个体2026 AI数字人软件选型:10 款轻量化工具易上手省成本

摘要如果你正考虑用AI数字人开启副业或为线下生意引流&#xff0c;市面上几十款工具鱼龙混杂&#xff0c;选错一个就是几百上千元的试错成本。本文抛开厂家营销话术&#xff0c;用真实的评测标准实测了10款轻量化AI数字人软件&#xff0c;从功能完整性、上手难度、成本控制三个…...

换新手机后,微信聊天记录怎么无缝‘搬家’?保姆级避坑指南(附熄屏、网络设置)

换新手机后&#xff0c;微信聊天记录无缝迁移全攻略&#xff1a;从防坑设置到完整验证 刚拿到新手机的兴奋感&#xff0c;往往在想到要迁移微信聊天记录时瞬间降温——那些工作群的重要文件、家人朋友的珍贵对话、收藏多年的表情包&#xff0c;一旦丢失就再也找不回来。作为一个…...

MAA智能辅助工具:3分钟掌握明日方舟全自动游戏管理方案

MAA智能辅助工具&#xff1a;3分钟掌握明日方舟全自动游戏管理方案 【免费下载链接】MaaAssistantArknights 《明日方舟》小助手&#xff0c;全日常一键长草&#xff01;| A one-click tool for the daily tasks of Arknights, supporting all clients. 项目地址: https://gi…...