Flink Sink的使用

经过一系列Transformation转换操作后,最后一定要调用Sink操作,才会形成一个完整的DataFlow拓扑。只有调用了Sink操作,才会产生最终的计算结果,这些数据可以写入到的文件、输出到指定的网络端口、消息中间件、外部的文件系统或者是打印到控制台.

flink在批处理中常见的sink

  1. print 打印
  2. writerAsText 以文本格式输出
  3. writeAsCsv 以csv格式输出
  4. writeUsingOutputFormat 以指定的格式输出
  5. writeToSocket 输出到网络端口
  6. 自定义连接器(addSink)

参考官网:https://ci.apache.org/projects/flink/flink-docs-release-1.13/zh/docs/dev/datastream/overview/#data-sinks

1、print

打印是最简单的一个Sink,通常是用来做实验和测试时使用。如果想让一个DataStream输出打印的结果,直接可以在该DataStream调用print方法。另外,该方法还有一个重载的方法,可以传入一个字符,指定一个Sink的标识名称,如果有多个打印的Sink,用来区分到底是哪一个Sink的输出。

以下演示了print打印,以及自定义print打印。

package com.bigdata.day03;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;


public class SinkPrintDemo {

    public static void main(String[] args) throws Exception {
        //1. env-准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);

        DataStreamSource<String> dataStreamSource = env.socketTextStream("localhost", 8888);
        // 打印,普通的打印
        // 6> helllo world
        //dataStreamSource.print();

        dataStreamSource.addSink(new MySink());

        // 接着手动实现该print 打印

        env.execute();
    }

    static class MySink extends RichSinkFunction<String> {

        @Override
        public void invoke(String value, Context context) throws Exception {
            // 得到一个分区号,因为要模仿print打印效果
            int partitionId = getRuntimeContext().getIndexOfThisSubtask() + 1;
            String msg = partitionId +"> " +value;
            System.out.println(msg);
        }
    }

}

 

package com.bigdata.day03;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.streaming.api.datastream.DataStream;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;


public class Demo01 {
    static class MyPrint extends RichSinkFunction<String>{
        private String msg;
        public MyPrint(){

        }
        public MyPrint(String msg){
            this.msg = msg;
        }
        @Override
        public void invoke(String value, Context context) throws Exception {
            int partition = getRuntimeContext().getIndexOfThisSubtask();
            if(msg == null){
                System.out.println(partition+"> "+value);
            }else{
                System.out.println(msg+">>>:"+partition+"> "+value);
            }

        }
    }

    public static void main(String[] args) throws Exception {

        //1. env-准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);

        //2. source-加载数据
        DataStream<String> data = env.fromElements("hello", "world", "baotianman", "laoyan");
        //3. transformation-数据处理转换
        //4. sink-数据输出
        //data.print();
        //data.print("普通打印>>>");
        data.addSink(new MyPrint());
        data.addSink(new MyPrint("模仿:"));


        //5. execute-执行
        env.execute();
    }
}

 

下面的结果是WordCount例子中调用print Sink输出在控制台的结果,细心的读者会发现,在输出的单词和次数之前,有一个数字前缀,我这里是1~4,这个数字是该Sink所在subtask的Index + 1。有的读者运行的结果数字前缀是1~8,该数字前缀其实是与任务的并行度相关的,由于该任务是以local模式运行,默认的并行度是所在机器可用的逻辑核数即线程数,我的电脑是2核4线程的,所以subtask的Index范围是0~3,将Index + 1,显示的数字前缀就是1~4了。这里在来仔细的观察一下运行的结果发现:相同的单词输出结果的数字前缀一定相同,即经过keyBy之后,相同的单词会被shuffle到同一个subtask中,并且在同一个subtask的同一个组内进行聚合。一个subtask中是可能有零到多个组的,如果是有多个组,每一个组是相互独立的,累加的结果不会相互干扰。

sum之后的:

1> hello 3

2> world 4

汇总之前,keyBy之后

1> hello 1

1> hello 1

1> hello 1

2、writerAsText 以文本格式输出

该方法是将数据以文本格式实时的写入到指定的目录中,本质上使用的是TextOutputFormat格式写入的。每输出一个元素,在该内容后面同时追加一个换行符,最终以字符的形式写入到文件中,目录中的文件名称是该Sink所在subtask的Index + 1。该方法还有一个重载的方法,可以额外指定一个枚举类型的参数writeMode,默认是WriteMode.NO_OVERWRITE,如果指定相同输出目录下有相同的名称文件存在,就会出现异常。如果是WriteMode.OVERWRITE,会将以前的文件覆盖。

package com.bigdata.day03;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.core.fs.FileSystem;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;


public class SinkTextDemo {

    public static void main(String[] args) throws Exception {
        //1. env-准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
        env.setParallelism(2);

        DataStreamSource<String> dataStreamSource = env.socketTextStream("localhost", 8880);
        // 写入到文件的时候,OVERWRITE 模式是重写的意思,假如以前有结果直接覆盖
        // 如果并行度为1 ,最后输出的结果是一个文件,假如并行度 > 1 最后的结果是一个文件夹,文件夹中的文件名是 分区号(任务号)

        dataStreamSource.writeAsText("F:\\BD230801\\FlinkDemo\\datas\\result", FileSystem.WriteMode.OVERWRITE);

        env.execute();
    }
}
package com.bigdata.day03;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.java.tuple.Tuple2;
import org.apache.flink.core.fs.FileSystem;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;


public class Demo02 {

    public static void main(String[] args) throws Exception {

        //1. env-准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);

        //2. source-加载数据
        //3. transformation-数据处理转换
        //4. sink-数据输出
        //DataStreamSource<String> streamSource = env.socketTextStream("localhost", 8899);
        //streamSource.writeAsText("datas/socket", FileSystem.WriteMode.OVERWRITE).setParallelism(1);
        DataStreamSource<Tuple2<String, Integer>> streamSource = env.fromElements(
                Tuple2.of("篮球", 1),
                Tuple2.of("篮球", 2),
                Tuple2.of("篮球", 3),
                Tuple2.of("足球", 3),
                Tuple2.of("足球", 2),
                Tuple2.of("足球", 3)
        );
        // writeAsCsv 只能保存 tuple类型的DataStream流,因为如果不是多列的话,没必要使用什么分隔符
        streamSource.writeAsCsv("datas/csv", FileSystem.WriteMode.OVERWRITE).setParallelism(1);


        //5. execute-执行
        env.execute();
    }
}

 3、连接器Connectors

JDBC Connector

该连接器可以向JDBC 数据库写入数据

JDBC | Apache Flink

<dependency>
  <groupId>org.apache.flink</groupId>
  <artifactId>flink-connector-jdbc_2.11</artifactId>
  <version>${flink.version}</version>
</dependency>

<!--假如你是连接低版本的,使用5.1.49-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.25</version>
        </dependency>

案例演示:

将结果读取,写入到MySQL

package com.bigdata.day03;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.flink.connector.jdbc.JdbcConnectionOptions;
import org.apache.flink.connector.jdbc.JdbcExecutionOptions;
import org.apache.flink.connector.jdbc.JdbcSink;
import org.apache.flink.connector.jdbc.JdbcStatementBuilder;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

import java.sql.PreparedStatement;
import java.sql.SQLException;

@Data
@AllArgsConstructor
@NoArgsConstructor
class Student{
    private int id;
    private String name;
    private int age;
}
public class JdbcSinkDemo {


    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<Student> studentStream = env.fromElements(new Student(1, "jack", 54));


        JdbcConnectionOptions jdbcConnectionOptions = new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
                .withUrl("jdbc:mysql://localhost:3306/test1")
                .withDriverName("com.mysql.jdbc.Driver")
                .withUsername("root")
                .withPassword("123456")
                .build();
        studentStream.addSink(JdbcSink.sink(
                "insert into student values(null,?,?)",
                new JdbcStatementBuilder<Student>() {
                    @Override
                    public void accept(PreparedStatement preparedStatement, Student student) throws SQLException {

                        preparedStatement.setString(1,student.getName());
                        preparedStatement.setInt(2,student.getAge());
                    }
                    // 假如是流的方式可以设置两条插入一次
                }, JdbcExecutionOptions.builder().withBatchSize(2).build(),jdbcConnectionOptions

        ));

        env.execute();
    }
}
package com.bigdata.day03;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.connector.jdbc.JdbcConnectionOptions;
import org.apache.flink.connector.jdbc.JdbcExecutionOptions;
import org.apache.flink.connector.jdbc.JdbcSink;
import org.apache.flink.connector.jdbc.JdbcStatementBuilder;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;

import java.sql.PreparedStatement;
import java.sql.SQLException;


@Data
@AllArgsConstructor
@NoArgsConstructor
class Student{
    private int id;
    private String name;
    private int age;
}
public class Demo03 {

    public static void main(String[] args) throws Exception {

        //1. env-准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);
        DataStreamSource<Student> studentDataStreamSource = env.fromElements(
                new Student(1, "张三", 19),
                new Student(2, "lisi", 20),
                new Student(3, "wangwu", 19)
        );

        JdbcConnectionOptions jdbcConnectionOptions = new JdbcConnectionOptions.JdbcConnectionOptionsBuilder()
                .withUrl("jdbc:mysql://localhost:3306/kettle")
                .withDriverName("com.mysql.cj.jdbc.Driver")
                .withUsername("root")
                .withPassword("root")
                .build();

        studentDataStreamSource.addSink(JdbcSink.sink(
                "insert into student values(null,?,?)",
                new JdbcStatementBuilder<Student>() {
                    @Override
                    public void accept(PreparedStatement preparedStatement, Student student) throws SQLException {
                        preparedStatement.setString(1,student.getName());
                        preparedStatement.setInt(2,student.getAge());
                    }
                },jdbcConnectionOptions
        ));
        //2. source-加载数据
        //3. transformation-数据处理转换
        //4. sink-数据输出


        //5. execute-执行
        env.execute();
    }
}

运行结果正常:

KafkaConnector

Kafka | Apache Flink

从Kafka的topic1中消费日志数据,并做实时ETL,将状态为success的数据写入到Kafka的topic2中

 

kafka-topics.sh --bootstrap-server bigdata01:9092 --create --partitions 1 --replication-factor 3 --topic topic1
kafka-topics.sh --bootstrap-server bigdata01:9092 --create --partitions 1 --replication-factor 3 --topic topic2

使用控制台当做kafka消息的生产者向kafka中的topic1 发送消息
kafka-console-producer.sh  --bootstrap-server bigdata01:9092 --topic topic1

消费kafka中topic2中的数据
kafka-console-consumer.sh  --bootstrap-server bigdata01:9092 --topic topic2

操作:
通过黑窗口向topic1中发送消息,含有success字样的消息,会出现在topic2中。

package com.bigdata.day03;

import org.apache.flink.api.common.RuntimeExecutionMode;
import org.apache.flink.api.common.functions.FilterFunction;
import org.apache.flink.api.common.serialization.SimpleStringSchema;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer;
import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer;
import org.apache.kafka.clients.producer.KafkaProducer;

import java.util.Properties;


public class KafkaSinkDemo {

    // 从topic1中获取数据,放入到topic2中,训练了读和写
    public static void main(String[] args) throws Exception {

        //1. env-准备环境
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setRuntimeMode(RuntimeExecutionMode.AUTOMATIC);

        //2. source-加载数据
        Properties properties = new Properties();
        properties.setProperty("bootstrap.servers", "bigdata01:9092");
        properties.setProperty("group.id", "g1");
        FlinkKafkaConsumer<String> kafkaSource = new FlinkKafkaConsumer<String>("topic1",new SimpleStringSchema(),properties);
        DataStreamSource<String> dataStreamSource = env.addSource(kafkaSource);
        //3. transformation-数据处理转换
        SingleOutputStreamOperator<String> filterStream = dataStreamSource.filter(new FilterFunction<String>() {
            @Override
            public boolean filter(String s) throws Exception {
                return s.contains("success");
            }
        });
        //4. sink-数据输出
        FlinkKafkaProducer kafkaProducer = new FlinkKafkaProducer<String>("topic2",new SimpleStringSchema(),properties);
        filterStream.addSink(kafkaProducer);


        //5. execute-执行
        env.execute();
    }
}

Flink Kafka Consumer 需要知道如何将 Kafka 中的二进制数据转换为 Java 或者Scala 对象。KafkaDeserializationSchema 允许用户指定这样的 schema,每条 Kafka 中的消息会调用 T deserialize(ConsumerRecord<byte[], byte[]> record) 反序列化。

为了方便使用,Flink 提供了以下几种 schemas:

SimpleStringSchema:按照字符串方式序列化、反序列化

剩余还有 TypeInformationSerializationSchema、JsonDeserializationSchema、AvroDeserializationSchema等。

自定义Sink--模拟jdbcSink的实现

jdbcSink官方已经提供过了,此处仅仅是模拟它的实现,从而学习如何自定义sink

package com.bigdata.day03;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.connector.jdbc.JdbcConnectionOptions;
import org.apache.flink.connector.jdbc.JdbcExecutionOptions;
import org.apache.flink.connector.jdbc.JdbcSink;
import org.apache.flink.connector.jdbc.JdbcStatementBuilder;
import org.apache.flink.streaming.api.datastream.DataStreamSource;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.sink.RichSinkFunction;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;



public class CustomJdbcSinkDemo {

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    static class Student{
        private int id;
        private String name;
        private int age;
    }

    static class MyJdbcSink  extends RichSinkFunction<Student> {

        Connection conn =null;
        PreparedStatement ps = null;
        @Override
        public void open(Configuration parameters) throws Exception {
            // 这个里面编写连接数据库的代码
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test1", "root", "123456");
            ps = conn.prepareStatement("INSERT INTO `student` (`id`, `name`, `age`) VALUES (null, ?, ?)");
        }

        @Override
        public void close() throws Exception {
            // 关闭数据库的代码
            ps.close();
            conn.close();
        }

        @Override
        public void invoke(Student student, Context context) throws Exception {
            // 将数据插入到数据库中
            ps.setString(1,student.getName());
            ps.setInt(2,student.getAge());
            ps.execute();

        }
    }

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        DataStreamSource<Student> studentStream = env.fromElements(new Student(1, "马斯克", 51));

        studentStream.addSink(new MyJdbcSink());

        env.execute();
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java【多线程】(1)进程与线程

目录 1.前言 2.正文 2.1什么是进程 2.2PCB&#xff08;进程控制块&#xff09; 2.2.1进程id 2.2.2内存指针 2.2.3文件描述符表 2.2.4进程状态 2.2.4.1就绪状态 2.2.4.2阻塞状态 2.2.5进程优先级 2.2.6进程上下文 2.2.7进程的记账信息 2.3CPU操作进程的方法 2.4什…

一个专为云原生环境设计的高性能分布式文件系统

大家好&#xff0c;今天给大家分享一款开源创新的分布式 POSIX 文件系统JuiceFS&#xff0c;旨在解决海量云存储与各类应用平台&#xff08;如大数据、机器学习、人工智能等&#xff09;之间高效对接的问题。 项目介绍 JuiceFS 是一款面向云原生设计的高性能分布式文件系统&am…

Jmeter中的断言

7&#xff09;断言 1--响应断言 功能特点 数据验证&#xff1a;验证响应数据是否包含或不包含特定的字符串、模式或值。多种匹配类型&#xff1a;支持多种匹配类型&#xff0c;如文本、正则表达式、文档等。灵活配置&#xff1a;可以设置多个断言条件&#xff0c;满足复杂的测…

游戏引擎学习第23天

实时代码编辑功能的回顾 当前实现的实时代码编辑功能已经取得了显著的成功&#xff0c;表现出强大的性能和即时反馈能力。该功能允许开发者在修改代码后几乎立即看到变化在运行中的程序中体现出来&#xff0c;极大提升了开发效率。尽管目前的演示内容较为简单&#xff0c;呈现…

排序算法之冒泡排序篇

冒泡排序的思想&#xff1a; 是一个把元素从小到大排的一个算法思想 相邻的两个元素两两比较&#xff0c;大的那一个元素向后移&#xff0c;小的那个元素向前移 核心逻辑&#xff1a; 比较所有相邻的两个项&#xff0c;如果第一个比第二个大&#xff0c;就交换它们 从头开始…

Java ArrayList 与顺序表:在编程海洋中把握数据结构的关键之锚

我的个人主页 我的专栏&#xff1a;Java-数据结构&#xff0c;希望能帮助到大家&#xff01;&#xff01;&#xff01;点赞❤ 收藏❤ 前言&#xff1a;在 Java编程的广袤世界里&#xff0c;数据结构犹如精巧的建筑蓝图&#xff0c;决定着程序在数据处理与存储时的效率、灵活性以…

【笔记】自动驾驶预测与决策规划_Part8_数据驱动的规划方法

文章目录 0. 前言1.生成模型1.1 Diffusion-ES1. Diffusion-ES算法介绍2. Diffusion-ES算法具体流程Diffusion Model 是什么&#xff1f;Diffusion-ES: Evolutionary StrategiesDiffusion-ES MethodDiffusion-ES Mapping Language instructions to reward functions with LLM pr…

React中事件处理和合成事件:理解与使用

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

Redis设计与实现第14章 -- 服务器 总结(命令执行器 serverCron函数 初始化)

14.1 命令请求的执行过程 一个命令请求从发送到获得回复的过程中&#xff0c;客户端和服务器都需要完成一系列操作。 14.1.1 发送命令请求 当用户在客户端中输入一个命令请求的时候&#xff0c;客户端会把这个命令请求转换为协议格式&#xff0c;然后通过连接到服务器的套接字…

【C语言】字符串左旋的三种解题方法详细分析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C语言 文章目录 &#x1f4af;前言&#x1f4af;题目描述&#x1f4af;方法一&#xff1a;逐字符移动法&#x1f4af;方法二&#xff1a;使用辅助空间法&#x1f4af;方法三&#xff1a;三次反转法&#x1f4af;方法对…

【AI绘画】Midjourney进阶:色调详解(上)

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: AI绘画 | Midjourney 文章目录 &#x1f4af;前言&#x1f4af;Midjourney中的色彩控制为什么要控制色彩&#xff1f;为什么要在Midjourney中控制色彩&#xff1f; &#x1f4af;色调白色调淡色调明色调 &#x1f4af…

零基础学安全--云技术基础

目录 学习连接 前言 云技术历史 云服务 公有云服务商 云分类 基础设施即服务&#xff08;IaaS&#xff09; 平台即服务&#xff08;PaaS&#xff09; 软件即服务&#xff08;SaaS&#xff09; 云架构 虚拟化 容器 云架构设计 组件选择 基础设施即代码 集成部署…

【Linux】网络通信

TCP协议是一个安全的、面向连接的、流式传输协议&#xff0c;所谓的面向连接就是三次握手&#xff0c;对于程序猿来说只需要在客户端调用connect()函数&#xff0c;三次握手就自动进行了。先通过下图看一下TCP协议的格式&#xff0c;然后再介绍三次握手的具体流程。 TCP的三次握…

Pgsql:json字段查询与更新

1.查询json字段的值 SELECT attribute_data->>设施类别 mycol, * FROM gis_coord_data WHERE attribute_data->>设施类别阀门井 查询结果如下&#xff1a; 2.更新json字段中的某个属性值 UPDATE gis_coord_data SET attribute_data(attribute_data::jsonb ||{&quo…

对于GC方面,在使用Elasticsearch时要注意什么?

大家好&#xff0c;我是锋哥。今天分享关于【对于GC方面&#xff0c;在使用Elasticsearch时要注意什么&#xff1f;】面试题。希望对大家有帮助&#xff1b; 对于GC方面&#xff0c;在使用Elasticsearch时要注意什么&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java…

基于Netty实现聊天室

前言 了解了Netty的基本功能和相关概念&#xff0c;使用基于Netty实现多人聊天的功能。 需求 1.服务端能够接收客户端的注册&#xff0c;并且接受用户的信息注册 2.服务端能够处理客户端发送的消息&#xff0c;并且根据消息类型进行私发或者广播发送消 3.服务端能够私发消…

Linux -日志 | 线程池 | 线程安全 | 死锁

文章目录 1.日志1.1日志介绍1.2策略模式1.3实现日志类 2.线程池2.1线程池介绍2.2线程池的应用场景2.3线程池的设计2.4代码实现2.5修改为单例模式 3.线程安全和函数重入问题3.1线程安全和函数重入的概念3.2总结 4.死锁4.1什么是死锁4.2产生死锁的必要条件4.3避免死锁 1.日志 1.…

【博主推荐】C#的winfrom应用中datagridview常见问题及解决方案汇总

文章目录 1.datagridview绘制出现鼠标悬浮数据变空白2.datagridview在每列前动态添加序号2.1 加载数据集完成后绘制序号2.2 RowPostPaint事件绘制 3.datagridview改变行样式4.datagridview后台修改指定列数据5.datagridview固定某个列宽6.datagridview某个列的显示隐藏7.datagr…

【设计模式】创建型模式之单例模式(饿汉式 懒汉式 Golang实现)

定义 一个类只允许创建一个对象或实例&#xff0c;而且自行实例化并向整个系统提供该实例&#xff0c;这个类就是一个单例类&#xff0c;它提供全局访问的方法。这种设计模式叫单例设计模式&#xff0c;简称单例模式。 单例模式的要点&#xff1a; 某个类只能有一个实例必须…

Vivado程序固化到Flash

在上板调试FPGA时&#xff0c;通常使用JTAG接口下载程序到FPGA芯片中&#xff0c;FPGA本身是基于RAM工艺的器件&#xff0c;因此掉电后会丢失芯片内的程序&#xff0c;需要重新烧写程序。但是当程序需要投入使用时不能每一次都使用JTAG接口下载程序&#xff0c;一般FPGA的外围会…