基于Socket和ServerSocket自定义协议的实现多端通信、消息发送、群聊发送

通信基础
  • Socket
    Socket套接字是实现网络通信的重要接口,Socket绑定的应用进程,目标Ip的端口号,以及数据传输对应的输入输出缓冲区。一个数据包到达一台计算机物理设备后,经过网络协议栈的解析,然后由操作系统调度到对应端口的输入缓冲区中。

  • 通信双方需要遵循共同的协议
    客户端与服务端需要遵循共同的协议来完成一次通信,标准的协议对双方解析一次完整的会话、响应的数据标准非常重要。

案例实现

这里基于Socket和ServerSocket编写一个实际通信案例,可以实现单发消息、创建群聊、群发消息等功能。
既然要实现多端通信,那么就需要客户端与服务端或者是A端和B端约定好共同的协议或者叫数据格式,只要遵循同一种协议通信双方才能完成数据解析与处理。

  • 通信协议
    这里使用Json格式来约定为一次通信的数据格式。其他的公共协议有Http、Ftp等。

  • 会话标识
    这里约定以服务端或者客户端读取到对方输入缓冲区中的换行符(\n)为一次数据传输完成的标识。

在这里插入图片描述

定义类实现
  • 操作枚举类,定义服务端支持的操作
public enum OpType {SEND_TO_USER(1,"单发消息"),SEND_TO_GROUP(2,"群发消息"),USER_LOGIN(3,"用户登录"),CREATE_GROUP(4,"创建群聊"),LIST_ONLINE_USER(5,"列出所有在线用户"),LIST_GROUP_USER(6,"列出某个群聊的在线用户"),LIST_USER_GROUP(7,"列出当前用户加入的群聊");@Getterprivate Integer code;@Getterprivate String desc;private OpType(Integer code, String desc) {this.code = code;this.desc = desc;}public static OpType getOpType(Integer code) {return Arrays.stream(values()).filter(opType -> opType.getCode().equals(code)).findFirst().orElse(null);}public static String list(){List<String> collect = Arrays.stream(values()).sorted().map(item -> item.getCode() + "." + item.getDesc()).collect(Collectors.toList());return JSONObject.toJSONString(collect);}}
  • 定义通信用到的常量
public class Constant {public static String USERNAME = "userName";public static String PASSWORD = "password";public static String LOGIN_SUCCESS = "loginSuccess";public static String LOGIN_FAILED = "loginFailed";}
  • 定义一次数据包中的消息体,可以看作类似于Htt协议的响应体。
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Message {//消息发送者private String from;//消息接收者private String to;//消息内容private String messageBody;}
  • 定义一个数据包对象,客户端和服务器之间的通信就是一次发送一个数据包对象
/*** 每一次发送的封装的数据包*/
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class DataPacket {//操作类型private String opType;//内容private Message message;/*** 获取socket输入流中请求的数据,封装为dataPacket对象** @param br 字符输入流* @return 本次读取到的数据* @throws Exception 没有使用代理模式等需要捕获异常后自定义处理逻辑的场景下,可以直接将异常抛出去*/public static DataPacket acceptMessage(BufferedReader br) throws Exception {//阻塞当前线程读取缓冲区中的字节数据,约定以读取到换行符为结束String content = br.readLine();DataPacket dataPacket = JSONObject.parseObject(content, DataPacket.class);return dataPacket;}}
  • 用户会话对象,保存了用户的Socket以及输入输出流
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class UserSession {//登录用户名private String username;//用户的会话private Socket socket;//是否下线private boolean isOnline;//字符输入流private BufferedReader reader;//字符输出流private PrintWriter writer;@Overridepublic boolean equals(Object o) {return o instanceof UserSession && ((UserSession) o).getUsername().equals(username);}@Overridepublic int hashCode() {return Objects.hash(username);}public void sendMessage(DataPacket dataPacket) {writer.println(JSONObject.toJSONString(dataPacket));}
}
  • 定义一个用户会话管理对象,用于管理用户的会话,例如单发消息和群发消息
public class UserSessionManager {private static List<UserSession> userSessionList = new ArrayList<UserSession>();public static List<String> getOnLineUsers() {return userSessionList.stream().map(UserSession::getUsername).collect(Collectors.toList());}//保存用户会话public static void addUserSession(UserSession userSession) {userSessionList.add(userSession);System.out.println("保存用户会话成功,当前上线用户:" + userSessionList.stream().map(UserSession::getUsername).collect(Collectors.joining(",")));}//下线后剔除用户会话public static void removeUserSession(UserSession userSession) {userSessionList.remove(userSession);}//A用户向B用户发送消息public static void sendMessage(OpType opType, Message message) {String fromUser = message.getFrom();String toUser = message.getTo();String messageBody = message.getMessageBody();UserSession fromUserSession = getUserSession(fromUser);//判断发送目标是否在线UserSession toUserSession = getUserSession(toUser);if (toUserSession != null) {toUserSession.sendMessage(DataPacket.builder().opType(opType.name()).message(Message.builder().from(fromUser).to(toUser).messageBody(messageBody).build()).build());}else {//提示对方不在线fromUserSession.sendMessage(DataPacket.builder().opType(opType.name()).message(Message.builder().from("服务器").messageBody(toUser + "不在线,发送失败!").build()).build());}}//根据一组用户名获取用户数据public static List<UserSession> getUserSession(String[] userNames) {return userSessionList.stream().filter(userSession -> containsInArr(userNames,userSession.getUsername())).collect(Collectors.toList());}//根据单个用户名获取用户数据public static UserSession getUserSession(String userName) {return userSessionList.stream().filter(userSession -> userSession.getUsername().equals(userName)).findFirst().orElse(null);}private static boolean containsInArr(String[] userNames, String username) {for (String userName : userNames) {if (userName.equals(username)) {return true;}}return false;}
}
  • 定义用户认证类,用于用户登录认证
/*** 用户认证管理相关*/
public class UserAuthManager {private static Map<String,String> userMap=new HashMap<String,String>();static {userMap.put("a","1");userMap.put("b","1");userMap.put("c","1");userMap.put("d","1");}public static boolean login(String username,String password){if(userMap.containsKey(username)){if(userMap.get(username).equals(password)){return true;}}return false;}}
  • 定义一个群聊对象,包括群名称和管理的群用户
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class GroupSession {//群聊名称private String groupName;//群聊成员private List<UserSession> userSessions;}
  • 定义群会话管理对象,主要实现群发和创建群聊
/**群会话管理:1. 创建群聊2. 群成员发消息*/
public class GroupSessionManager {private static List<GroupSession> sessions = new ArrayList<GroupSession>();//创建群聊public static boolean createGroup(String groupName,String owner,String ...userNames) {//判断群聊是否存在boolean match = sessions.stream().anyMatch(session -> session.getGroupName().equals(groupName));if (match) {//群聊已存在,创建失败!return false;}//将创建者也加入到群组中List<UserSession> userSessions = UserSessionManager.getUserSession(userNames);UserSession userSession = UserSessionManager.getUserSession(owner);userSessions.add(userSession);sessions.add(new GroupSession(groupName,userSessions));return true;}//群聊发送消息public static void sendGroupMessage(String from, String groupName, String content) {//获取群组GroupSession groupSession = sessions.stream().filter(session -> session.getGroupName().equals(groupName)).findFirst().get();//遍历群组中的人List<UserSession> userSessions = groupSession.getUserSessions();for (UserSession userSession : userSessions) {String toUser = userSession.getUsername();//封装一条消息JSONObject jsonObject = new JSONObject();jsonObject.put("groupName", groupName);jsonObject.put("content", content);Message message = Message.builder().to(toUser).from(from).messageBody(jsonObject.toJSONString()).build();UserSessionManager.sendMessage(OpType.SEND_TO_GROUP, message);}}}
  • 定义服务端实现
public class ChatServer {public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(6666);System.out.println("服务器启动成功");while (true) {Socket socket = serverSocket.accept();new Thread(() ->{try {System.out.println("客户端:" + socket.getRemoteSocketAddress() + "连接成功!");InputStream is = socket.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(is));OutputStream os = socket.getOutputStream();PrintWriter pw = new PrintWriter(new OutputStreamWriter(os), true);while (true){//使用br阻塞监听读取此socket输入缓冲区中的字节数据,以读取到\r\n换行符为【一次结束】,客户端发送的数据既然会被缓存在对应socket输入流的字节缓冲区中。DataPacket dataPacket = DataPacket.acceptMessage(br);//判断客户端的请求类型String type = dataPacket.getOpType();OpType opType = valueOf(type);switch(opType){case SEND_TO_USER:{Message message = dataPacket.getMessage();UserSessionManager.sendMessage(SEND_TO_USER,message);};break;case SEND_TO_GROUP:{doSendToGroup(dataPacket);};break;case USER_LOGIN:{doUserLogin(socket,dataPacket,br,pw);};break;case CREATE_GROUP:{doCreateGroup(dataPacket, pw);};break;case LIST_ONLINE_USER:{doListOnlineUser(pw);};break;case LIST_GROUP_USER:{};break;case LIST_USER_GROUP:{};break;}}}catch (Exception e){e.printStackTrace();}}).start();}}private static void doSendToGroup(DataPacket dataPacket) {Message message = dataPacket.getMessage();String from = message.getFrom();String messageBody = message.getMessageBody();JSONObject jsonObject = JSONObject.parseObject(messageBody);String groupName = jsonObject.getString("groupName");String content = jsonObject.getString("content");GroupSessionManager.sendGroupMessage(from,groupName,content);}/*** 处理创建群聊* @param dataPacket* @param pw*/private static void doCreateGroup(DataPacket dataPacket, PrintWriter pw) {Message message = dataPacket.getMessage();String from = message.getFrom();String messageBody = message.getMessageBody();JSONObject jsonObject = JSONObject.parseObject(messageBody);String groupName = jsonObject.getString("groupName");String groupUsers = jsonObject.getString("groupUsers");String[] userArr = groupUsers.split(",");boolean isCreate  = GroupSessionManager.createGroup(groupName,from,userArr);String content = from + "邀请" + groupUsers + " 共同加入了群聊:" + groupName;Message sendMessage = Message.builder().messageBody(content).build();if (isCreate){//创建成功DataPacket packet = DataPacket.builder().opType(CREATE_GROUP.name()).message(sendMessage).build();pw.println(JSONObject.toJSONString(packet));//给群聊中其他人发for (String itemUser : userArr) {sendMessage.setTo(itemUser);UserSessionManager.sendMessage(CREATE_GROUP,sendMessage);}}else {//创建失败!DataPacket packet = DataPacket.builder().opType(CREATE_GROUP.name()).message(Message.builder().messageBody("创建群聊失败!").build()).build();pw.println(JSONObject.toJSONString(packet));}}/*** 给客户端响应当前服务器在线用户* @param pw*/private static void doListOnlineUser(PrintWriter pw) {List<String> onLineUsers = UserSessionManager.getOnLineUsers();DataPacket packet = DataPacket.builder().opType(LIST_ONLINE_USER.name()).message(Message.builder().messageBody(JSONObject.toJSONString(onLineUsers)).build()).build();pw.println(JSONObject.toJSONString(packet));System.out.println("服务器返回了当前所有在线用户");}/*** 处理用户登录* @param socket* @param dataPacket* @param br* @param pw*/private static void doUserLogin(Socket socket, DataPacket dataPacket, BufferedReader br, PrintWriter pw) {Message message = dataPacket.getMessage();JSONObject messageBody = JSONObject.parseObject(message.getMessageBody());String username = messageBody.getString(Constant.USERNAME);String password = messageBody.getString(Constant.PASSWORD);//登录boolean login = UserAuthManager.login(username, password);if (login) {//登录成功,存储用户会话UserSessionManager.addUserSession(UserSession.builder().socket(socket).isOnline(true).username(username).reader(br).writer(pw).build());DataPacket packet = DataPacket.builder().message(Message.builder().messageBody(Constant.LOGIN_SUCCESS).build()).build();//写出去pw.println(JSONObject.toJSONString(packet));}else {DataPacket packet = DataPacket.builder().message(Message.builder().messageBody(Constant.LOGIN_FAILED).build()).build();//写出去pw.println(JSONObject.toJSONString(packet));}}}
  • 定义客户端实现
public class ChatClient {public static void main(String[] args) throws Exception {Socket socket = new Socket("127.0.0.1", 6666);InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();BufferedReader br = new BufferedReader(new InputStreamReader(is));PrintWriter pw = new PrintWriter(new OutputStreamWriter(os), true);Scanner scanner = new Scanner(System.in);String currentUserName = "";while (true) {String username = getInput("请输入用户名", scanner);String password = getInput("请输入密码", scanner);//封装成数据包JSONObject jsonObject = new JSONObject(2);jsonObject.put(Constant.USERNAME, username);jsonObject.put(Constant.PASSWORD, password);DataPacket packet = DataPacket.builder().message(Message.builder().messageBody(jsonObject.toJSONString()).build()).opType(OpType.USER_LOGIN.name()).build();pw.println(JSONObject.toJSONString(packet));DataPacket dataPacket = DataPacket.acceptMessage(br);String messageBody = dataPacket.getMessage().getMessageBody();//判断是否登录成功,如果成功就跳出循环,否则继续登录if (messageBody.equals(Constant.LOGIN_SUCCESS)) {//开启新线程,并行处理输入流handlerSocketInput(br);System.out.println("登录成功!");currentUserName = username;break;}System.out.println(messageBody);}while (true) {System.out.print("请选择操作:" + OpType.list());OpType opType = OpType.getOpType(scanner.nextInt());//继续调用一下scanner的nextLine方法来消耗缓冲区中的换行符scanner.nextLine();switch (opType) {//单发消息case SEND_TO_USER: {String toUser = getInput("请输入要发送的用户", scanner);String content = getInput("请输入发送内容", scanner);DataPacket dataPacket = DataPacket.builder().opType(opType.name()).message(Message.builder().from(currentUserName).messageBody(content).to(toUser).build()).build();pw.println(JSONObject.toJSONString(dataPacket));};break;//列出服务器所有在线用户case LIST_ONLINE_USER: {DataPacket dataPacket = DataPacket.builder().opType(opType.name()).message(Message.builder().from(currentUserName).build()).build();pw.println(JSONObject.toJSONString(dataPacket));};break;//创建群聊case CREATE_GROUP:{String groupName = getInput("请输入群聊名称", scanner);String groupUsers = getInput("请输入群聊用户,多个用,隔开", scanner);//封装数据包JSONObject groupObject = new JSONObject();groupObject.put("groupName", groupName);groupObject.put("groupUsers", groupUsers);DataPacket dataPacket = DataPacket.builder().opType(CREATE_GROUP.name()).message(Message.builder().from(currentUserName).messageBody(groupObject.toJSONString()).build()).build();pw.println(JSONObject.toJSONString(dataPacket));};break;//群发消息case SEND_TO_GROUP:{String groupName = getInput("请输入群发群聊", scanner);String content = getInput("请输入要发送的消息", scanner);JSONObject groupObject = new JSONObject();groupObject.put("groupName", groupName);groupObject.put("content", content);DataPacket packet = DataPacket.builder().opType(SEND_TO_GROUP.name()).message(Message.builder().from(currentUserName).messageBody(groupObject.toJSONString()).build()).build();pw.println(JSONObject.toJSONString(packet));}}}}/*** 开启新线程,并行处理服务器输入** @param br*/private static void handlerSocketInput(BufferedReader br) throws Exception {new Thread(() -> {while (true) { // 客户端的输入监听线程需要一直while true 循环读取每一次的数据包try {DataPacket dataPacket = DataPacket.acceptMessage(br);OpType opType = OpType.valueOf(dataPacket.getOpType());switch (opType) {case SEND_TO_USER: {Message message = dataPacket.getMessage();String sender = message.getFrom();String messageBody = message.getMessageBody();System.out.println("\n" + sender + " 对你说:" + messageBody);};break; //这里break将跳出循环case LIST_ONLINE_USER: {Message message = dataPacket.getMessage();String messageBody = message.getMessageBody();System.out.println("\n" + "当前服务在线用户 " + messageBody);};break;case CREATE_GROUP: {Message message = dataPacket.getMessage();String messageBody = message.getMessageBody();System.out.println("\n" + "群消息:" + messageBody);};break;//接收群聊消息case SEND_TO_GROUP:{Message message = dataPacket.getMessage();String from = message.getFrom();String messageBody = message.getMessageBody();JSONObject jsonObject = JSONObject.parseObject(messageBody);String groupName = jsonObject.getString("groupName");String content = jsonObject.getString("content");System.out.println("\n来自[" + groupName + "]的群消息,群成员[" + from + "]对大家说:" + content);}}} catch (Exception e) {e.printStackTrace();break;}}}).start();}public static String getInput(String placeHolder, Scanner scanner) {System.out.print(placeHolder + ":");String s = scanner.nextLine();return s;}}
运行查看

由于需要启动多个客户端窗口,需要复制出多个chatclient程序。

  1. 点击Servrice ,创建Application分组,Application是普通Java程序Main方法的分组。
    在这里插入图片描述

  2. 复制出4个客户端。
    在这里插入图片描述

  3. 启动并运行
    单发消息
    在这里插入图片描述
    在这里插入图片描述

群发消息

在这里插入图片描述

在这里插入图片描述

  • 客户端分线程

由于客户端使用的是scanner从标准输入中读取字节数据,而这个操作是阻塞的,此时如果不开启一个分线程,那么用户就会一直被标准输入阻塞,而无法处理服务器的响应的数据。当然,如果涉及到主线程和分线程之前的通信协作,就需要借助wait、notify等方法实现。

  • 网络与文件的输入缓冲区

对于一个输入流来说,它的数据来源可能是从标准输入(控制台)、文件输入、网络输入,其中对于文件输入来说,这个输入流能读取到的内容大小是固定的,因为一个文件的大小就是固定的。如果使用字节输入流读取,读取不到内容时就会返回-1。
而对于标准输入或者网络输入来说,一个输入流不知道读到到什么地方才能算结束,那这个时候如果输入缓冲区中已经没有可读的数据了,则就会阻塞。所以要通过协议约定,例如在标准输入时,告诉控制台以用户按下回车为结束,在网络输入时,以读取到固定长度的字节或者读取到某个字符时为结束,这样就保证了一次完整的数据传输。

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

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

相关文章

【Uniapp-Vue3】Prop校验与prop默认值用法及循环遍历数组对象

一、prop校验 如果我们在想要限制prop的类型&#xff0c;就可以在接收prop的时候对接收类型进行限制&#xff1a; defineProps({ 属性名:{ type:类型 } }) 需要注意类型的首字母大写 但是设置了传入参数类型限制并不能严格限制&#xff0c;只会在后台进行提示&#xff1a; 二、…

Java Stream流操作List全攻略:Filter、Sort、GroupBy、Average、Sum实践

在Java 8及更高版本中&#xff0c;Stream API为集合处理带来了革命性的改变。本文将深入解析如何运用Stream对List进行高效的操作&#xff0c;包括筛选&#xff08;Filter&#xff09;、排序&#xff08;Sort&#xff09;、分组&#xff08;GroupBy&#xff09;、求平均值&…

ExplaineR:集成K-means聚类算法的SHAP可解释性分析 | 可视化混淆矩阵、决策曲线、模型评估与各类SHAP图

集成K-means聚类算法的SHAP可解释性分析 加载数据集并训练机器学习模型 SHAP 分析以提取特征对预测的影响 通过混淆矩阵可视化模型性能 决策曲线分析 模型评估&#xff08;多指标和ROC曲线的目视检查&#xff09; 带注释阈值的 ROC 曲线 加载 SHAP 结果以进行下游分析 与…

【Python通过UDP协议传输视频数据】(界面识别)

提示&#xff1a;界面识别项目 前言 随着网络通信技术的发展&#xff0c;视频数据的实时传输在各种场景中得到了广泛应用。UDP&#xff08;User Datagram Protocol&#xff09;作为一种无连接的协议&#xff0c;凭借其低延迟、高效率的特性&#xff0c;在实时性要求较高的视频…

浅谈云计算03 | 云计算的技术支撑(云使能技术)

云计算的技术支撑 一、定义与内涵1.1 定义与内涵 二、云计算使能技术架构2.1 宽带网络和 Internet 架构2.2 数据中心技术2.3 虚拟化技术2.4 Web 技术2.5 多租户技术2.6 服务技术 一、定义与内涵 1.1 定义与内涵 云计算技术包含一些基础的关键技术&#xff0c;这里称为使能技术…

QGraphicsView QGraphicsScene QGraphicsItem

QGraphicsView && QGraphicsScene &#xff1a; QGraphicsView 是 Qt 中用于显示和交互处理 2D 图形的控件&#xff0c;QGraphicsView 用于显示 QGraphicsScene 中的内容。 .h #ifndef MAINWINDOW_H #define MAINWINDOW_H#include <QMainWindow> #include &…

【大数据】机器学习-----线性模型

一、线性模型基本形式 线性模型旨在通过线性组合输入特征来预测输出。其一般形式为&#xff1a; 其中&#xff1a; x ( x 1 , x 2 , ⋯ , x d ) \mathbf{x}(x_1,x_2,\cdots,x_d) x(x1​,x2​,⋯,xd​) 是输入特征向量&#xff0c;包含 d d d 个特征。 w ( w 1 , w 2 , ⋯ ,…

基于千帆(ERNIE-Functions-8K)Function Calling的简单使用

1.Function Calling是什么&#xff1f; 1.1 概念 Function calling是一种将LLM(大语言模型Large language model)连接外部工具的能力&#xff0c;LLM经过微调后&#xff0c;可以检测出何时需要调用函数以及需要调用函数的方法名及参数&#xff0c;并返回给调用方以便调用外部…

Deep Attentional Guided Image Filtering

This work achieved the first place in the real depth map SR challenge held in ACM ICMR 2021. 目的&#xff1a;从一个target image(如低分辨的深度图)和guidance image(如高分辨的RGB图)&#xff0c;得到输出(如高分辨的深度图)。 主要思想是考虑了target和guidance之间的…

SpringBoot+Lombok项目实体属性名xXxx格式,前端接收不到

问题解析 今天发现后端传给前端的实体类中&#xff0c;有属性为xXxxx格式的&#xff0c;前端也使用相同名称接收&#xff0c;结果却不显示值&#xff01;研究了一会发现接口请求回来后&#xff0c;原xXxxx的属性名&#xff0c;会被转为全小写。具体原因为&#xff1a;使用Lombo…

高级运维:shell练习2

1、需求&#xff1a;判断192.168.1.0/24网络中&#xff0c;当前在线的ip有哪些&#xff0c;并编写脚本打印出来。 vim check.sh #!/bin/bash# 定义网络前缀 network_prefix"192.168.1"# 循环遍历1-254的IP for i in {1..254}; do# 构造完整的IP地址ip"$network_…

为深度学习创建PyTorch张量 - 最佳选项

为深度学习创建PyTorch张量 - 最佳选项 正如我们所看到的&#xff0c;PyTorch张量是torch.Tensor​ PyTorch类的实例。张量的抽象概念与PyTorch张量之间的区别在于&#xff0c;PyTorch张量为我们提供了一个可以在代码中操作的具体实现。 在上一篇文章中&#xff0c;我们看到了…

28.找出字符串中第一个匹配项的下标【力扣】KMP前缀表 ≈ find() 函数、暴力解法

class Solution { public: //得到前缀表void getNext(int *next,string needle){int j0;for(int i1;i<needle.size();i){while(j>0 && needle[j]!needle[i]) jnext[j-1];//**j>0**>j0是出口if(needle[i]needle[j]) j;next[i]j;//若写入if中&#xff0c;则该…

mysql 创建临时表报错

1. 问题描述 5.7.31 版本 mysql 数据库创建临时表报错 -- 报错语句 CREATE TEMPORARY TABLE temp_table_name SELECT * FROM table_name LIMIT 0;报错截图 [HY000][1005] Can’t create table ‘new_tbl’ (errno: 13) 2. 解决方案 步骤一&#xff1a; 查询 linux m…

使用 WPF 和 C# 将纹理应用于三角形

此示例展示了如何将纹理应用于三角形,以使场景比覆盖纯色的场景更逼真。以下是为三角形添加纹理的基本步骤。 创建一个MeshGeometry3D对象。像往常一样定义三角形的点和法线。通过向网格的TextureCoordinates集合添加值来设置三角形的纹理坐标。创建一个使用想要显示的纹理的 …

mac homebrew配置使用

本文介绍mac上homebrew工具的安装、配置过程。homebrew功能类似于centos的yum&#xff0c;用于软件包的管理&#xff0c;使用上有命令的差异。 本次配置过程使用mac&#xff0c;看官方文档&#xff0c;在linux上也可以用&#xff0c;但我没试过&#xff0c;有兴趣的同学可以试试…

OpenCV基础:矩阵的创建、检索与赋值

本文主要是介绍如何使用numpy进行矩阵的创建&#xff0c;以及从矩阵中读取数据&#xff0c;修改矩阵数据。 创建矩阵 import numpy as npa np.array([1,2,3]) b np.array([[1,2,3],[4,5,6]]) #print(a) #print(b)# 创建全0数组 eros矩阵 c np.zeros((8,8), np.uint8) #prin…

Unreal Engine 5 (UE5) Metahuman 的头部材质

在图中&#xff0c;你展示了 Unreal Engine 5 (UE5) Metahuman 的头部材质部分&#xff0c;列出了头部材质的多个元素。以下是对每个部分的解释&#xff1a; 材质解释 Element 0 - MI_HeadSynthesized_Baked 作用&#xff1a; 这是 Metahuman 的主要头部材质&#xff0c;控制整…

鸿蒙中自定义slider实现字体大小变化

ui&#xff1a; import { display, mediaquery, router } from kit.ArkUI import CommonConstants from ./CommonConstants; import PreferencesUtil from ./PreferencesUtil; import StyleConstants from ./StyleConstants;// 字体大小 Entry Component struct FontSize {Sta…

LabVIEW智能水肥一体灌溉控制系统

本文详细介绍了一种基于LabVIEW的智能水肥一体灌溉控制系统的设计与实现。该系统采用模糊控制策略&#xff0c;能够自动调节土壤湿度和肥液浓度&#xff0c;满足不同作物在不同生长阶段的需求&#xff0c;有效提高水肥利用效率&#xff0c;对现代精准农业具有重要的实践和推广价…