Websocket学习

news2025/7/20 13:06:09

参考:http://www.mydlq.club/article/86/

这里写目录标题

    • 一、WebSocket 简介
    • 二、WebSocket 特点
    • 三、为什么需要 WebSocket
    • 四、WebSocket 连接流程
    • 五、WebSocket 使用场景
    • 六、使用案例
      • 1.提醒客户端有新订单
      • 2.客户端交互

一、WebSocket 简介

WebSocket 是一种基于 TCP 的网络协议。是一种全双工通信的协议,既允许客户端向服务器主动发送消息,也允许服务器主动向客户端发送消息。在 WebSocket 中,浏览器和服务器只需要完成一次握手,两者之间就可以建立持久性的连接,进行双向数据传输。

二、WebSocket 特点

  • 连接握手阶段使用 HTTP 协议;
  • 协议标识符是 ws,如果采用加密则是 wss;
  • 数据格式比较轻量,性能开销小,通信高效;
  • 没有同源限制,客户端可以与任意服务器通信;
  • 建立在 TCP 协议之上,服务器端的实现比较容易;
  • 通过 WebSocket 可以发送文本,也可以发送二进制数据;
  • 与 HTTP 协议有着良好的兼容性。默认端口也是 80 和 443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器;

三、为什么需要 WebSocket

谈起为什么需要 WebSocket 前,那得先了解在没有 WebSocket 那段时间说起,那时候基于 Web 的消息基本上是靠 Http 协议进行通信,而经常有"聊天室"、“消息推送”、"股票信 息实时动态"等这样需求,而实现这样的需求常用的有以下几种解决方案:
在这里插入图片描述
(1)、短轮询(Traditional Polling)

短轮询是指客户端每隔一段时间就询问一次服务器是否有新的消息,如果有就接收消息。这样方式会增加很多次无意义的发送请求信息,每次都会耗费流量及处理器资源。

  • 优点:短连接,服务器处理简单,支持跨域、浏览器兼容性较好。
  • 缺点:有一定延迟、服务器压力较大,浪费带宽流量、大部分是无效请求。

(2)、长轮询(Long Polling)

长轮询是段轮询的改进,客户端执行 HTTP 请求发送消息到服务器后,等待服务器回应,如果没有新的消息就一直等待,知道服务器有新消息传回或者超时。这也是个反复的过程,这种做法只是减小了网络带宽和处理器的消耗,但是带来的问题是导致消息实时性低,延迟严重。而且也是基于循环,最根本的带宽及处理器资源占用并没有得到有效的解决。

  • 优点:减少轮询次数,低延迟,浏览器兼容性较好。
  • 缺点:服务器需要保持大量连接。

(3)、服务器发送事件(Server-Sent Event)

服务器发送事件是一种服务器向浏览器客户端发起数据传输的技术。一旦创建了初始连接,事件流将保持打开状态,直到客户端关闭。该技术通过传统的 HTTP 发送,并具有 WebSockets 缺乏的各种功能,例如"自动重新连接"、“事件ID” 及 "发送任意事件"的能力。

服务器发送事件是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。

  • 优点:适用于更新频繁、低延迟并且数据都是从服务端发到客户端。
  • 缺点:浏览器兼容难度高。

显然,上面这几种方式都有各自的优缺点,虽然靠轮询方式能够实现这些一些功能,但是其对性能的开销和低效率是非常致命的,尤其是在移动端流行的现在。现在客户端与服务端双向通信的需求越来越多,且现在的浏览器大部分都支持 WebSocket。所以对实时性和双向通信及其效率有要求的话,比较推荐使用 WebSocket。

四、WebSocket 连接流程

(1)、客户端先用带有 Upgrade:Websocket 请求头的 HTTP 请求,向服务器端发起连接请求,实现握手(HandShake)。

Connection: Upgrade
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Sec-WebSocket-Key: IRQYhWINfX5Fh1zdocDl6Q==
Sec-WebSocket-Version: 13
Upgrade: websocket

(2)、握手成功后,由 HTTP 协议升级成 Websocket 协议,进行长连接通信,两端相互传递信息。

五、WebSocket 使用场景

  • 数据流状态: 比如说上传下载文件,文件进度,文件是否上传成功。
  • 协同编辑文档: 同一份文档,编辑状态得同步到所有参与的用户界面上。
  • 多玩家游戏: 很多游戏都是协同作战的,玩家的操作和状态肯定需要及时同步到所有玩家。
  • 多人聊天: 很多场景下都需要多人参与讨论聊天,用户发送的消息得第一时间同步到所有用户。
  • 社交订阅: 有时候我们需要及时收到订阅消息,比如说开奖通知,比如说在线邀请,支付结果等。
  • 股票虚拟货币价格: 股票和虚拟货币的价格都是实时波动的,价格跟用户的操作息息相关,及时推送对用户跟盘有很大的帮助。

六、使用案例

官方案例:https://spring.io/guides/gs/messaging-stomp-websocket/

1.提醒客户端有新订单

pom.xml

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

配置类

@Configuration
public class WebSocketConfig{

    @Bean
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

websoket类

@Slf4j
@Component
@ServerEndpoint("/websocket")
public class WebSocket {

    private Session session;

    private static CopyOnWriteArraySet<WebSocket> webSocketSet = new CopyOnWriteArraySet<>();

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);
        log.info("【websocket消息】有新的连接, 总数:{}", webSocketSet.size());
    }

    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        log.info("【websocket消息】连接断开, 总数:{}", webSocketSet.size());
    }

    @OnMessage
    public void onMessage(String message) {
        log.info("【websocket消息】收到客户端发来的消息:{}", message);
    }

    public void sendMessage(String message) {
        for (WebSocket webSocket: webSocketSet) {
            log.info("【websocket消息】广播消息, message={}", message);
            try {
                webSocket.session.getBasicRemote().sendText(message);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

订单实体类

@Data
@AllArgsConstructor
public class Order {
    private String orderId;
    private String buyerName;
    private String buyerPhone;
    private String buyerAddress;
    private Double orderAmount;
    private Integer state;
    private String createDate;
}

controller

@Slf4j
@RestController
public class OrderController {

    @Autowired
    private WebSocket webSocket;

    static List<Order> orderList=new ArrayList();
    {
        orderList.add(new Order("1","张三","13512341234","上海市",11.11,1,"2022-10-01"));
        orderList.add(new Order("2","李四","18367445678","北京市",22.22,1,"2022-10-02"));
        orderList.add(new Order("3","王五","13812345678","天津市",33.33,1,"2022-10-03"));
    }

    /**
     * 模拟下单方法
     */
    @GetMapping("/create")
    public void createOrder(){
        //1.扣库存
        //2.插入数据
        orderList.add(new Order("4","赵六","13452057018","辽宁市",44.44,1,"2022-10-04"));
        //3.websocket通知客户端有新订单
        webSocket.sendMessage("有新的订单!");
    }

    @GetMapping("/getOrder")
    public List getOrder(){
        return orderList;
    }
}

前端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div id="app">
    <el-table :data="items" stripe style="width: 100%">
        <el-table-column prop="orderId" label="订单号" width="180"></el-table-column>
        <el-table-column prop="buyerName" label="姓名" width="180"></el-table-column>
        <el-table-column prop="buyerPhone" label="电话" width="180"></el-table-column>
        <el-table-column prop="buyerAddress" label="地址" width="180"></el-table-column>
        <el-table-column prop="orderAmount" label="订单金额" width="180"></el-table-column>
        <el-table-column prop="state" label="状态" width="180"></el-table-column>
        <el-table-column prop="createDate" label="创建时间" width="180"></el-table-column>
    </el-table>
    <el-button type="text" @click="open">点击打开 Message Box</el-button>
</div>

<!--1.导入Vue.js-->
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.21/dist/vue.min.js"></script>
<!-- 引入样式element-ui -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
    var vm = new Vue({
        el: "#app",
        data() {
            return {
                items: [],
                message: ""
            }
        },
        methods: {
            open() {
                this.$alert(this.message, {
                    confirmButtonText: '查看',
                    callback: action => {
                        this.init();
                    }
                });
            },
            init(){
                axios.get('http://localhost:8080/getOrder').then(response => (this.items = response.data));
            }
        },
        mounted() {
            this.init();
        }
    })
    var websocket = null;
    if ('WebSocket' in window) {
        websocket = new WebSocket('ws://localhost:8080/websocket');
    } else {
        alert('该浏览器不支持websocket!');
    }

    websocket.onopen = function (event) {
        console.log('建立连接');
    }

    websocket.onclose = function (event) {
        console.log('连接关闭');
    }

    websocket.onmessage = function (event) {
        console.log('收到消息:' + event.data)
        vm.message = event.data;
        vm.open();
    }

    websocket.onerror = function () {
        alert('websocket通信发生错误!');
    }

    window.onbeforeunload = function () {
        websocket.close();
    }
</script>
</body>
</html>

启动项目,测试报错
在这里插入图片描述
开启跨域

@Configuration
public class AppConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")  // 拦截所有的请求
                .allowedOriginPatterns("*")  // 可跨域的域名,可以为 *
                .allowCredentials(true)
                .allowedMethods("*")   // 允许跨域的方法,可以单独配置
                .allowedHeaders("*");  // 允许跨域的请求头,可以单独配置
    }
}

效果:
在这里插入图片描述
在这里插入图片描述

开始测试:
1.请求新增订单
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
点击查看
在这里插入图片描述

2.客户端交互

pom.xml

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<optional>true</optional>
</dependency>

配置类

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /**
     * 配置 WebSocket 进入点,及开启使用 SockJS,这些配置主要用配置连接端点,用于 WebSocket 连接
     *
     * @param registry STOMP 端点
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/mydlq").withSockJS();
    }

    /**
     * 配置消息代理选项
     *
     * @param registry 消息代理注册配置
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 设置一个或者多个代理前缀,在 Controller 类中的方法里面发生的消息,会首先转发到代理从而发送到对应广播或者队列中。
        // ⽤户可以订阅来⾃以"/topic"为前缀的消息,客户端只可以订阅这个前缀的主题
        registry.enableSimpleBroker("/topic/abc");
        // 配置客户端发送请求消息的一个或多个前缀,该前缀会筛选消息目标转发到 Controller 类中注解对应的方法里
        //客户端发送过来的消息,需要以"/app"为前缀,再经过Broker转发给响应的Controller
        registry.setApplicationDestinationPrefixes("/app");
    }
    
}

实体类

@Data
@ToString
public class MessageBody {
    /** 消息内容 */
    private String content;
    /** 广播转发的目标地址(告知 STOMP 代理转发到哪个地方) */
    private String destination;
}

cotroller

@Controller
public class MessageController {

    /** 消息发送工具对象 */
    @Autowired
    private SimpMessageSendingOperations simpMessageSendingOperations;

    /** 广播发送消息,将消息发送到指定的目标地址 */
    @MessageMapping("/test")
    public void sendTopicMessage(MessageBody messageBody) {
        // 将消息发送到 WebSocket 配置类中配置的代理中(/topic)进行消息转发
        System.out.println(messageBody);
        simpMessageSendingOperations.convertAndSend(messageBody.getDestination(), messageBody);
    }

}

前端页面

<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css" rel="stylesheet">
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/sockjs-client/1.4.0/sockjs.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<!--    <script src="app-websocket.js"></script>-->
</head>
<body>
<div id="main-content" class="container" style="margin-top: 10px;">
    <div class="row">
        <form class="navbar-form" style="margin-left:0px">
            <div class="col-md-12">
                <div class="form-group">
                    <label>WebSocket 连接:</label>
                    <button class="btn btn-primary" type="button" onclick="connect();">进行连接</button>
                    <button class="btn btn-danger" type="button" onclick="disconnect();">断开连接</button>
                </div>
                <label>订阅地址:</label>
                <div class="form-group">
                    <input type="text" id="subscribe" class="form-control" placeholder="订阅地址">
                </div>
                <button class="btn btn-warning" onclick="subscribeSocket();" type="button">订阅</button>
            </div>
        </form>
    </div>
    </br>
    <div class="row">
        <div class="form-group">
            <label for="content">发送的消息内容:</label>
            <input type="text" id="content" class="form-control" placeholder="消息内容">
        </div>
        <button class="btn btn-info" onclick="sendMessageNoParameter();" type="button">发送</button>
    </div>
    </br>
    <div class="row">
        <div class="col-md-12">
            <h5 class="page-header" style="font-weight:bold">接收到的消息:</h5>
            <table class="table table-striped">
                <tbody id="information"></tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>
<script>
    // 设置 STOMP 客户端
    var stompClient = null;
    // 设置 WebSocket 进入端点
    var SOCKET_ENDPOINT = "/mydlq";
    // 设置订阅消息的请求前缀
    var SUBSCRIBE_PREFIX = "/topic"
    // 设置订阅消息的请求地址
    var SUBSCRIBE = "";
    // 设置服务器端点,访问服务器中哪个接口
    var SEND_ENDPOINT = "/app/test";

    /* 进行连接 */
    function connect() {
        // 设置 SOCKET
        var socket = new SockJS(SOCKET_ENDPOINT);
        // 配置 STOMP 客户端
        stompClient = Stomp.over(socket);
        // STOMP 客户端连接
        stompClient.connect({}, function (frame) {
            alert("连接成功");
        });
    }

    /* 订阅信息 */
    function subscribeSocket(){
        // 设置订阅地址
        SUBSCRIBE = SUBSCRIBE_PREFIX + $("#subscribe").val();
        // 输出订阅地址
        alert("设置订阅地址为:" + SUBSCRIBE);
        // 执行订阅消息
        stompClient.subscribe(SUBSCRIBE, function (responseBody) {
            var receiveMessage = JSON.parse(responseBody.body);
            $("#information").append("<tr><td>" + receiveMessage.content + "</td></tr>");
        });
    }

    /* 断开连接 */
    function disconnect() {
        stompClient.disconnect(function() {
            alert("断开连接");
        });
    }

    /* 发送消息并指定目标地址(这里设置的目标地址为自身订阅消息的地址,当然也可以设置为其它地址) */
    function sendMessageNoParameter() {
        // 设置发送的内容
        var sendContent = $("#content").val();
        // 设置待发送的消息内容
        var message = '{"destination": "' + SUBSCRIBE + '", "content": "' + sendContent + '"}';
        // 发送消息
        stompClient.send(SEND_ENDPOINT, {}, message);
    }
</script>

输入地址 ​​http://localhost:8080/index.html​​ 访问测试的前端页面,然后执行下面步骤进行测试:
1.点击进行连接按钮,连接 WebSocket 服务端;
2.在订阅地址栏输入订阅地址
3.点击订阅按钮订阅对应地址的消息;
4.在发送消息内容的输入框中输入​​hello world!​​,然后点击发送按钮发送消息;

流程:先连接服务端,订阅一个地址(当这个地址有消息,服务端就会发送过来,实时显示在界面上),然后发送消息

例:
连接服务端后,订阅了/topic/qqq这个地址
在这里插入图片描述

然后向/app/test 接口发送消息MessageBody(content=999, destination=/topic/qqq)在这里插入图片描述

/topic/qqq这个地址发送消息999在这里插入图片描述
WebSocket 配置类中配置的代理中(/topic/abc)进行消息转发也就是向/topic/abc发消息999
在这里插入图片描述
而前端订阅的是/topic/qqq,所以收不到消息

当订阅的是/topic/abc就可以收到消息了
在这里插入图片描述

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

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

相关文章

为什么越来越多的企业在会议室使用无线流媒体网关?

1&#xff0c;用户已有华为&#xff0c;MAXHUB等投屏功能设备&#xff0c;不需要这个设备了。但是市面上大部分投屏设备的使用存在以下问题&#xff1a; 操作麻烦&#xff0c;我们发射器是直接触摸投屏&#xff0c;安全性低&#xff0c;需要驱动软件。 2&#xff0c;市场上有很…

实现矩阵连乘积(动态规划)

目录 实现矩阵连乘积 题目 问题分析 算法分析 时间复杂度 代码实现 执行结果 动态规划 基本思想 举例 个人主页&#xff1a;天寒雨落的博客_CSDN博客-初学者入门C语言,python,数据库领域博主 &#x1f4ac; 热门专栏&#xff1a;初学者入门C语言_天寒雨落的博客-CSDN…

【SVM分类】基于鸽群算法优化支持向量机SVM实现分类附matlab的代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

arduino 复习题

名词解释 中断 计算机运行过程中&#xff0c;出现某些意外情况需主机干预时&#xff0c;机器能自动停止正在运行的程序并转入处理新情况的程序&#xff0c;处理完毕后又返回原被暂停的程序继续运行 中断服务程序 用于 CPU 处理中断的程序 中断源 引起中断的原因&#xff0c;或…

【JVS低代码平台】如何实现与外部系统/内部代码直接对接?

JVS是开放性的低代码开发平台&#xff0c;为开发团队预留了多种对接的方式。我这里列举集中对接的模式。 用户对接&#xff08;统一登录/单点跳转&#xff09; 在日常的企业需求中&#xff0c;常常有这种情况。企业内部考勤打开都是通过钉钉或者企微的&#xff0c;那么希望我们…

Grasp Detection论文、代码汇总

文章目录2022End-to-end Trainable Deep Neural Network for Robotic Grasp Detection and Semantic Segmentation from RGB2019Antipodal Robotic Grasping using Generative Residual Convolutional Neural Network2022 End-to-end Trainable Deep Neural Network for Robot…

现代PCB生产工艺——加成法、减成法与半加成法

继续为朋友们分享关于PCB生产工艺的知识。 现代PCB生产工艺&#xff0c;目前主要分为&#xff1a;加成法、减成法与半加成法。 其具体定义如下&#xff1a; 加成法&#xff1a; 通过网印或曝光形成图形&#xff0c;经钻孔、沉铜、转移层压等工艺加工&#xff0c;直接将导电图形…

Pycharm开发环境下创建python运行的虚拟环境(自动执行安装依赖包)

问题&#xff1a;基于Django开发的后台程序涉及到很多依赖的开发包&#xff0c;将该项目迁移到其它电脑环境下运行需要搭建环境&#xff0c;由于项目中有requirement.txt&#xff0c;该文件内包含了运行该项目所需的依赖&#xff1b;最简便的方式是执行命令自动安装requirement…

postgresql11 主从配置详解

以下内容是针对pgsql11来做的。请看好版本再去考虑是否往下看 准备两台服务器&#xff0c;地址如下&#xff1a; 主&#xff1a;192.168.0.1pgsql11从:192.168.0.2pgsql11一、主库配置 1、创建具有复制权限的用户replica 密码为000000 CREATE ROLE replica login replicat…

Docker——数据卷命令

目录 一、数据卷 1.1 便于修改 1.2 数据共享 1.3 安全问题 1.4 数据卷的基本语法 二、 创建数据卷&#xff0c;并查看数据卷在宿主机的目录位置 2.1 创建数据卷 2.2 查看所有数据卷 2.3 查看数据卷详细信息卷 2.4 删除数据卷 三、挂载数据卷 3.1 创建容器并挂载数据卷…

智慧国土解决方案-最新全套文件

智慧国土解决方案-最新全套文件一、建设背景二、建设思路1、紧盯三大领域2、划分三个阶段3、面向三个维度三、建设方案轻应用微服务大平台应用设计四、获取 - 智慧国土全套最新解决方案合集一、建设背景 2019年5月9日&#xff0c;印发《关于建立国土空间规划体系并监督实施的若…

Pandas 数据中的loc与iloc含义以及操作

本节学习并记录pandas 的DataFrame类型的数据是怎么对列或者行进行操作的 1、df.loc: 语法格式是df.loc[<行表达式>, <列表达式>]&#xff0c;如果列不传将返回所有的行&#xff0c;loc操作通过索引和列的条件筛选出数据。 2、df.iloc: 语法格式是df.iloc[<行…

python初级学习

第一章 为什么要学习Python 那些最好的程序员不是为了得到更高的薪水或者得到公众的仰慕而编程,他们只是觉得这是-件有 趣的事情。 ——Linux 之父 Linus TorvaIds 作为-个实用主义的学习者,最关心的问题-定是「我为什么要选择学 Python, 学会之后我可以用来做什么&#xff1…

基于springboot电动车智能充电服务平台设计与实现的源码+文档

摘 要 在Internet高速发展的今天&#xff0c;我们生活的各个领域都涉及到计算机的应用&#xff0c;其中包括电动车智能充电服务平台的网络应用&#xff0c;在外国电动车智能充电服务平台已经是很普遍的方式&#xff0c;不过国内的电动车智能充电服务平台可能还处于起步阶段。电…

Briefings in bioinformatics2022 | 基于神经网络的分子性质预测通用优化策略

原文标题&#xff1a;A general optimization protocol for molecular property prediction using a deep learning network 代码&#xff1a;GitHub - titanda/Learn-it-all at ready_classification_feature 一、问题提出 虽然个别优化方法都成功地提高了模型的性能&#…

2022年IT服务行业研究报告

第一章 行业概况 IT服务是指在信息技术领域服务商为其用户提供信息咨询、软件升级、硬件维修等全方位的服务。IT服务产品包括&#xff1a;硬件集成、软件集成、通用解决方案、行业解决方案和IT综合服务。 服务过程是指IT需求得以满足的全过程&#xff0c;从IT服务商为用户提供…

小咖啡馆也能撬动大生意

在我们的传统观念里&#xff0c;无论什么行业&#xff0c;似乎店越大、生意也就越大&#xff0c;但事实真的如此吗&#xff1f;未必&#xff01;位于成都的原福咖啡馆整店大小仅10㎡&#xff0c;但在社区却人气非常高。从2018年开业至今&#xff0c;门店排队不断&#xff0c;而…

Pycharm初次创建项目时页面环境变量选择

Pycharm确实是一个非常不错的Python开发IDE&#xff0c;尤其对于初学者而言。 安装完Pycharm&#xff0c;并未创建任何工程项目时的界面&#xff0c;选择新建一个Pure Python项目&#xff0c; 基于上述界面解读 &#xff1a; 1是新建项目路径可以在Location处选择。 2是Proje…

MapReduce

4.1 MapReduce概述 2003年和2004年&#xff0c;Google公司在国际会议上分别发表了两篇关于Google分布式文件系统和MapReduce的论文&#xff0c;公布了Google的GFS和MapReduce的基本原理和主要设计思想。 4.1.1 MapReduce定义 MapReduce是一个分布式运算程序的编程框架&#…

基于GIS的人口统计数据空间化解决方案

​ 人口数据一般以各种级别的行政区域为统计单位&#xff0c;使用表格进行展示。常用的人口分布度量指标是人口密度&#xff0c;即行政区域内单位土地面积上的人口数量&#xff0c;这种以行政辖区为单位进行统计的方法&#xff0c;统计的结果是假定人口均匀分布在整个区域内&a…