WCF进阶:将编码后的字节流压缩传输

  在前面两篇文章WCF进阶:将消息正文Base64编码和WCF进阶:为每个操作附加身份信息中讲述了如何通过拦截消息的方式来记录或者修改消息,这种方式在特定条件下可以改变消息编码格式,但实现方式并不规范,而且使用范围也有限制。 WCF缺省提供了三种编码器(MessageEncoder):TextMessageEncoder,BinaryMessageEncoder,MtomMessageEncoder。事实上也是基于XML可以有三种格式:Text,Binary,MTOM,而XmlDictionaryWriter也提供了三种创建Writer的方法,CreateTextWriter,CreateBinaryWriter,CreateMtomWriter他们分别用于将XML以文本,二进制,MTOM保存。三种保存形式各有利弊,Text便于理解,更通用,Binary体积小,MTOM是优化之后的二进制,适用于较大的二进制传输。但无论使用哪种,最终在网络上传输的都是字节流或者叫字节数组。在Binding中处于最后一个的总是TransportBindingElement,也就是说当要传递的数据到达TransportBindingElement之后,其实已经是字节数组了。在TransportBindingElement之上,可以有事务处理器,会话处理器,消息安全处理器,编码器,传输安全处理器等,在编码器之前,主要的处理对象是Message,而之后,主要的处理对象就是Stream(Byte[]),从这点我们也就清楚了之前在学习安全体系的时候,总是习惯将安全划分为消息级别的安全和传输级别安全两种了。我们这次要实现的其实是将已经经过编码器编码好的字节流压缩后传输,而不是传统意义上的消息编码。这点也需要大家深层次的理解。

  虽然我们实现的传输层面上的压缩编码,但实现机理和自定义MessageEncoder是一样的,MessageEncoder是所有编码器的基类,它有几个非常重要的方法和属性

public abstract Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType);
public ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager);
// Properties
public abstract string ContentType { get; }
public abstract string MediaType { get; }
public abstract MessageVersion MessageVersion { get; }

ReadMessage和WriteMessage是完成消息 <->字节数组转换的,这两个方法(还有几个重载),在实现自定义编码器的时候是最为重要的,我们主要是通过重写它们来完成自定义转换。上面我们也说过了,我们要实现的是传输层的压缩编码,那么需要有一个缺省编码,为此我们设计了名为CompressEncoder的自定义编码类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.IO;namespace RobinLib
{public class CompressEncoder : MessageEncoder{CompressEncoderFactory factory;MessageEncoder innserEncoder;private CompressAlgorithm algorithm;public CompressEncoder(CompressEncoderFactory encoderFactory, CompressAlgorithm algorithm){factory = encoderFactory;this.algorithm = algorithm;innserEncoder = factory.InnerMessageEncodingBindingElement.CreateMessageEncoderFactory().Encoder;}public override string ContentType{get { return innserEncoder.ContentType; }}public override string MediaType{get { return innserEncoder.MediaType; }}public override MessageVersion MessageVersion{get { return innserEncoder.MessageVersion; }}public override bool IsContentTypeSupported(string contentType){return innserEncoder.IsContentTypeSupported(contentType);}public override T GetProperty<T>(){return innserEncoder.GetProperty<T>();}public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType){ArraySegment<byte> bytes = new Compressor(algorithm).DeCompress(buffer);int totalLength = bytes.Count;byte[] totalBytes = bufferManager.TakeBuffer(totalLength);Array.Copy(bytes.Array, 0, totalBytes, 0, bytes.Count);ArraySegment<byte> byteArray = new ArraySegment<byte>(totalBytes, 0, bytes.Count);bufferManager.ReturnBuffer(byteArray.Array); Message msg = innserEncoder.ReadMessage(byteArray, bufferManager, contentType);return msg;}public override Message ReadMessage(System.IO.Stream stream, int maxSizeOfHeaders, string contentType){//读取消息的时候,二进制流为加密的,需要解压Stream ms = new Compressor(algorithm).DeCompress(stream); Message msg = innserEncoder.ReadMessage(ms, maxSizeOfHeaders, contentType);return msg;}public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset){ ArraySegment<byte> bytes = innserEncoder.WriteMessage(message, maxMessageSize, bufferManager);ArraySegment<byte> buffer = new Compressor(algorithm).Compress(bytes);int totalLength = buffer.Count + messageOffset;byte[] totalBytes = bufferManager.TakeBuffer(totalLength);Array.Copy(buffer.Array, 0, totalBytes, messageOffset, buffer.Count);ArraySegment<byte> byteArray = new ArraySegment<byte>(totalBytes, messageOffset, buffer.Count);Console.WriteLine("算法:"+algorithm+",原来字节流大小:"+bytes.Count+",压缩后字节流大小:"+byteArray.Count);return byteArray;}public override void WriteMessage(Message message, System.IO.Stream stream){System.IO.MemoryStream ms = new System.IO.MemoryStream();innserEncoder.WriteMessage(message, ms);stream = new Compressor(algorithm).Compress(ms);}}
}

在这个类中需要知道上层编码器是什么,我们用MessageEncoder innserEncoder来指定,在WriteMessage时候,将消息用内置编码器转换为字节数组,然后用压缩算法压缩这个数组,形成压缩后字节数组传递给到下一层,而在读取Message的时候,首先将收到的字节数组解压缩,最后将解压缩后字节数组用内置编码器转换为Message对象。其中Compressor是一个功能类,用于将字节数组压缩或者解压缩,代码为:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Compression;
namespace RobinLib
{public class Compressor{private CompressAlgorithm algorithm;public Compressor(CompressAlgorithm algorithm){this.algorithm = algorithm;}//压缩数组public ArraySegment<byte> Compress(ArraySegment<byte> data){MemoryStream ms = new MemoryStream();if (algorithm == CompressAlgorithm.GZip){Stream compressStream = new GZipStream(ms, CompressionMode.Compress, true);compressStream.Write(data.Array, 0, data.Count);compressStream.Close();}else{Stream compressStream = new DeflateStream(ms, CompressionMode.Compress, true);compressStream.Write(data.Array, 0, data.Count);compressStream.Close();}byte[] newByteArray = new byte[ms.Length];ms.Seek(0, SeekOrigin.Begin);ms.Read(newByteArray, 0, newByteArray.Length);ArraySegment<byte> bytes = new ArraySegment<byte>(newByteArray);return bytes;}//压缩流public Stream Compress(Stream stream){MemoryStream ms = new MemoryStream();if (algorithm == CompressAlgorithm.GZip){Stream compressStream = new GZipStream(ms, CompressionMode.Compress, true);byte[] buffer = new byte[stream.Length];stream.Read(buffer, 0, buffer.Length);compressStream.Write(buffer, 0, buffer.Length);compressStream.Close();}else{Stream compressStream = new DeflateStream(ms, CompressionMode.Compress, true);byte[] buffer = new byte[stream.Length];stream.Read(buffer, 0, buffer.Length);compressStream.Write(buffer, 0, buffer.Length);compressStream.Close();}return ms;}//解压缩数组public ArraySegment<byte> DeCompress(ArraySegment<byte> data){MemoryStream ms = new MemoryStream();ms.Write(data.Array, 0, data.Count);ms.Seek(0, SeekOrigin.Begin);if (algorithm == CompressAlgorithm.GZip){Stream compressStream = new GZipStream(ms, CompressionMode.Decompress, false);byte[] newByteArray = RetrieveBytesFromStream(compressStream, 1);compressStream.Close();return new ArraySegment<byte>(newByteArray);}else{Stream compressStream = new DeflateStream(ms, CompressionMode.Decompress, false);byte[] newByteArray = RetrieveBytesFromStream(compressStream, 1);compressStream.Close();return new ArraySegment<byte>(newByteArray);}}//解压缩数组public Stream DeCompress(Stream stream){stream.Seek(0, SeekOrigin.Begin);if (algorithm == CompressAlgorithm.GZip){Stream compressStream = new GZipStream(stream, CompressionMode.Decompress, false);byte[] newByteArray = RetrieveBytesFromStream(compressStream, 1);compressStream.Close();return new MemoryStream(newByteArray);}else{Stream compressStream = new DeflateStream(stream, CompressionMode.Decompress, false);byte[] newByteArray = RetrieveBytesFromStream(compressStream, 1);compressStream.Close();return new MemoryStream(newByteArray);}}public static byte[] RetrieveBytesFromStream(Stream stream, int bytesblock){List<byte> lst = new List<byte>();byte[] data = new byte[1024];int totalCount = 0;while (true){int bytesRead = stream.Read(data, 0, data.Length);if (bytesRead == 0){break;}byte[] buffers = new byte[bytesRead];Array.Copy(data, buffers, bytesRead);lst.AddRange(buffers);totalCount += bytesRead;}return lst.ToArray();}}
}

到此,其实我们的自定义编码器应该编写好了,接下来如何使用它成为我们最为关心的事情。每一个MessageEncoder都对应一个MessageEncoderFactory,在MessageEncodingBindingElement中能返回这个MessageEncoderFactory,然后通过在自定义BindingElement创建监听通道(BuildChannelListener)和通道工厂(BuildChannelFactory)的时候,将BindingElement添加到BindingContext,这样就能最终消费我们上面实现的CompressEncoder。

CompressEncoderFactory的代码实现为:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;namespace RobinLib
{public class CompressEncoderFactory:MessageEncoderFactory{private MessageEncodingBindingElement innerMessageEncodingBindingElement;CompressEncoder messageEncoder;private CompressAlgorithm algorithm;public CompressEncoderFactory(MessageEncodingBindingElement innerMessageEncodingBindingElement, CompressAlgorithm algorithm){this.innerMessageEncodingBindingElement = innerMessageEncodingBindingElement;this.algorithm = algorithm;messageEncoder = new CompressEncoder(this,algorithm);}public override MessageEncoder CreateSessionEncoder(){return base.CreateSessionEncoder();}public override MessageEncoder Encoder{get { return messageEncoder; }}public override MessageVersion MessageVersion{get { return innerMessageEncodingBindingElement.MessageVersion; }}public MessageEncodingBindingElement InnerMessageEncodingBindingElement{get{return innerMessageEncodingBindingElement;}}}
}

自定义的MessageEncoderBindingElement代码为:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel.Channels;
using System.ServiceModel;
using System.Xml;namespace RobinLib
{public sealed class CompressEncodingBindingElement : MessageEncodingBindingElement{ private XmlDictionaryReaderQuotas readerQuotas;private MessageEncodingBindingElement innerMessageEncodingBindingElement;private CompressAlgorithm algorithm;public MessageEncodingBindingElement InnerMessageEncodingBindingElement{get{return innerMessageEncodingBindingElement;}}public CompressAlgorithm CompressAlgorithm{get{return algorithm;}}public CompressEncodingBindingElement(MessageEncodingBindingElement innerMessageEncodingBindingElement, CompressAlgorithm algorithm){this.readerQuotas = new XmlDictionaryReaderQuotas();this.algorithm = algorithm;this.innerMessageEncodingBindingElement = innerMessageEncodingBindingElement;}public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context){context.BindingParameters.Add(this);return context.BuildInnerChannelFactory<TChannel>();}public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context){context.BindingParameters.Add(this);return context.BuildInnerChannelListener<TChannel>();}public override bool CanBuildChannelFactory<TChannel>(BindingContext context){context.BindingParameters.Add(this);return context.CanBuildInnerChannelFactory<TChannel>();}public override bool CanBuildChannelListener<TChannel>(BindingContext context){context.BindingParameters.Add(this);return context.CanBuildInnerChannelListener<TChannel>();}public override MessageEncoderFactory CreateMessageEncoderFactory(){return new CompressEncoderFactory(innerMessageEncodingBindingElement,algorithm);}public override T GetProperty<T>(BindingContext context)  {if (typeof(T) == typeof(XmlDictionaryReaderQuotas)){return this.readerQuotas as T;}return base.GetProperty<T>(context);}public override MessageVersion MessageVersion{get{return innerMessageEncodingBindingElement.MessageVersion;}set{innerMessageEncodingBindingElement.MessageVersion = value;}}public override BindingElement Clone(){return new CompressEncodingBindingElement(innerMessageEncodingBindingElement,algorithm);} }
}

最终,我们可以使用CustomeBinding创建宿主和客户端。

服务端:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using Robin_Wcf_CustomMessageEncoder_SvcLib;
using System.ServiceModel.Channels;
using RobinLib;namespace Robin_Wcf_CustomMessageEncoder_Host
{class Program{static void Main(string[] args){//服务地址Uri baseAddress = new Uri("http://127.0.0.1:8081/Robin_Wcf_Formatter");ServiceHost host = new ServiceHost(typeof(Service1), new Uri[] { baseAddress });//服务绑定ICollection<BindingElement> bindingElements = new List<BindingElement>();HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement();CompressEncodingBindingElement textBindingElement = new CompressEncodingBindingElement(new TextMessageEncodingBindingElement(),CompressAlgorithm.Deflate);bindingElements.Add(textBindingElement);bindingElements.Add(httpBindingElement);CustomBinding bind = new CustomBinding(bindingElements);  host.AddServiceEndpoint(typeof(IService1), bind, "");if (host.Description.Behaviors.Find<System.ServiceModel.Description.ServiceMetadataBehavior>() == null){System.ServiceModel.Description.ServiceMetadataBehavior svcMetaBehavior = new System.ServiceModel.Description.ServiceMetadataBehavior();svcMetaBehavior.HttpGetEnabled = true;svcMetaBehavior.HttpGetUrl = new Uri("http://127.0.0.1:8001/Mex");host.Description.Behaviors.Add(svcMetaBehavior);}host.Opened += new EventHandler(delegate(object obj, EventArgs e){Console.WriteLine("服务已经启动!");}); host.Open();Console.Read();}}
}

 

客户端:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RobinLib;
using System.ServiceModel.Channels;
using Robin_Wcf_CustomMessageEncoder_ClientApp.ServiceReference1;namespace Robin_Wcf_CustomMessageEncoder_ClientApp
{class Program{static void Main(string[] args){System.Threading.Thread.Sleep(5300);ICollection<BindingElement> bindingElements = new List<BindingElement>();HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement();CompressEncodingBindingElement textBindingElement = new CompressEncodingBindingElement(new BinaryMessageEncodingBindingElement(), CompressAlgorithm.GZip);bindingElements.Add(textBindingElement);bindingElements.Add(httpBindingElement); CustomBinding bind = new CustomBinding(bindingElements);  ServiceReference1.IService1 svc = new ServiceReference1.Service1Client(bind, new System.ServiceModel.EndpointAddress("http://127.0.0.1:8081/Robin_Wcf_Formatter"));string pres = svc.GetData(10);Console.WriteLine(pres);CompositeType ct = svc.GetDataUsingDataContract(new CompositeType());System.IO.MemoryStream ms = new System.IO.MemoryStream();for (int i = 0; i < 1000000; i++){byte[] buffer = BitConverter.GetBytes(i);ms.Write(buffer, 0, buffer.Length);}System.IO.Stream stream = svc.GetStream(ms);Console.Read();}}
}

我们可以更改CompressEncodingBindingElement textBindingElement = new CompressEncodingBindingElement(new BinaryMessageEncodingBindingElement(), CompressAlgorithm.GZip);,指定内置MessageEncoder和压缩算法。

最后附件一句,不是所有的数组压缩后体积都变小的,只有文本类型的压缩后,效果比较明显。运行程序,当内置TextMessageEncodingBindingElement的时候,我们得到的效果为:

image

此时说明压缩效果非常明显,

而当内置BinaryMessageEncodingBindingElement的时候,压缩效果不再突出,甚至起到反作用。

image

如果有朋友需要使用压缩传输,可以直接下载项目,引用其中的RobinLib.dll,然后使用自定义Binding。

项目文件:/jillzhang/Robin_Wcf_CustomMessageEncoder.rar

下文我们将演示实现对称加密传输。

转载于:https://www.cnblogs.com/jillzhang/archive/2010/04/13/1711079.html

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

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

相关文章

物理学四大神兽,除了“薛定谔的猫”, 你还知道哪几个?

来源 &#xff1a; 逗逼的500T硬盘物理学是一门研究物质运动最一般规律和物质基本结构的学科。分为纯物理学和多学科物理学&#xff0c;其中的纯物理学又分为&#xff1a;1.经典力学&#xff1b;2.热力学和统计力学&#xff1b;3.电磁学&#xff1b;4.相对论&#xff1b;5.量子…

JDBC-01-快速入门

文章目录01 JDBC快速入门02 JDBC各个类详解03 JDBC之CRUD练习04 ResultSet类详解05 JDBC登录案例练习抽取JDBC工具类 &#xff1a; JDBCUtils练习06 PreparedStatement类详解07 JDBC事务管理目标 1. JDBC基本概念 2. 快速入门 3. 对JDBC中各个接口和类详解 01 JDBC快速入门 1…

研究速递:预测学习——神经元高效运作的最佳策略

来源&#xff1a;集智俱乐部作者&#xff1a;袁郭玲、梁金编辑&#xff1a;邓一雪摘要了解大脑如何学习有助于制造具有与人类类似智力水平的机器。之前有理论提出&#xff0c;大脑可能是根据预测编码的原理运行。然而&#xff0c;对于预测系统如何在大脑中实现还没有很好的解释…

破解人工智能系统的四种攻击方法!

来源&#xff1a;未来科技前沿没有人喜欢早上起床&#xff0c;但现在人工智能驱动的算法可以设置我们的闹钟、管理我们家中的温度设置以及选择适合我们心情的播放列表&#xff0c;贪睡按钮的使用越来越少。人工智能安全辅助系统使我们的车辆更安全&#xff0c;人工智能算法优化…

PowerDesigner-快速入门(极简教程)

文章目录3. PowerDesigner3.1 PowerDesigner介绍3.2 PowerDesigner使用3.2.1 创建物理数据模型3.2.2 从PDM导出SQL脚本3.2.3 逆向工程3.2.4 生成数据库报表文件3. PowerDesigner 3.1 PowerDesigner介绍 PowerDesigner是Sybase公司的一款软件&#xff0c;使用它可以方便地对系…

关于dev无法更新、调试的问题

转载于:https://www.cnblogs.com/IcefishBingqing/p/5109876.html

MIT发布白皮书:美国欲重返世界半导体霸主!

来源&#xff1a;新智元编辑&#xff1a;时光 David近年来&#xff0c;全球芯片的持续性短缺已引发了一连串的产能瓶颈问题。各种消费品的价格都随着「缺芯」而上升&#xff0c;从CPU到显卡&#xff0c;从智能冰箱到SUV&#xff0c;这凸显出半导体在日常生活种所扮演的重要作用…

dubbo-快速入门-分布式RPC框架Apache Dubbo

文章目录分布式RPC框架Apache Dubbo1. 软件架构的演进过程1.1 单体架构1.2 垂直架构1.3 SOA架构1.4 微服务架构2. Apache Dubbo概述2.1 Dubbo简介2.2 Dubbo架构3. 服务注册中心Zookeeper3.1 Zookeeper介绍3.2 安装Zookeeper3.3 启动、停止Zookeeper4. Dubbo快速入门4.1 服务提供…

可构建AI的“AI”诞生:几分之一秒内,就能预测新网络的参数

来源&#xff1a;学术头条 作者&#xff1a;Anil Ananthaswamy译者&#xff1a;刘媛媛原文出处&#xff1a;quantamagazine.org人工智能在很大程度上是一场数字游戏。当深度神经网络在 10 年前开始超越传统算法&#xff0c;是因为我们终于有了足够的数据和处理能力来充分利用它…

linux-01-概述

文章目录入门概述走近Linux系统入门概述 我们为什么要学习Linux linux诞生了这么多年&#xff0c;以前还喊着如何能取代windows系统&#xff0c;现在这个口号已经小多了&#xff0c;任何事物发展都有其局限性都有其天花板。就如同在国内再搞一个社交软件取代腾讯一样&#xff0…

MySQL-Front的安装简介

本博文在作者的个人网站、博客园和CSDN同步发表&#xff0c;如要转载&#xff0c;请标明原作者和出处。 最近在学习MySQL数据库&#xff0c;开始的时候使用的Windows的命令行进行最基本的代码的输入&#xff0c;可是后来就觉得比较麻烦了&#xff0c;于是想找一款图形化数…

生命是什么?生物化学、物理学、哲学对生命本源的共同探索

来源&#xff1a; 集智俱乐部作者&#xff1a;Mark A. Bedua译者&#xff1a;宋词、范星辰 审校&#xff1a;周理乾、梁金编辑&#xff1a;邓一雪导语地球上充盈着生命&#xff0c;通常我们很容易分辨哪些是生命&#xff0c;哪些不是生命。可是&#xff0c;关于生命是什么&…

linux-02-常用的命令-必须掌握

文章目录目录管理基本属性文件内容查看目录管理 绝对路径和相对路径 我们知道Linux的目录结构为树状结构&#xff0c;最顶级的目录为根目录 /。 其他目录通过挂载可以将它们添加到树中&#xff0c;通过解除挂载可以移除它们。 在开始本教程前我们需要先知道什么是绝对路径与相…

上交大许志钦:神经网络中的奥卡姆剃刀——简单有效原理

来源&#xff1a; 智源社区作者&#xff1a;许志钦整理&#xff1a;熊宇轩编辑&#xff1a;李梦佳本文整理自青源Talk第十期&#xff0c;视频回看地址&#xff1a;https://event.baai.ac.cn/activities/217【专栏&#xff1a;研究思路】奥卡姆剃刀是由14世纪方济会修士奥卡姆的…

linux-03-Vim使用+账号用户管理

什么是Vim编辑器 Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富&#xff0c;在程序员中被广泛使用。 简单的来说&#xff0c; vi 是老式的字处理器&#xff0c;不过功能已经很齐全了&#xff0c;但是还是有可以进步的地方。 vim 则…

孙正义:未来30年投资趋势【附PPT】

来源&#xff1a;投资家、蓝血研究&#xff08;lanxueyanjiu)作者&#xff1a;孙正义对于今后30年来讲&#xff0c;我认为现在是个很关键的时刻&#xff0c;尤其是在各位的人生当中。而且现在是一整个概念的转变&#xff0c;我们要包容这个概念的转变。我想先给大家看两张照片。…

linux-04-磁盘命令+进程命令

磁盘管理 概述 Linux磁盘管理好坏直接关系到整个系统的性能问题。 Linux磁盘管理常用命令为 df、du。 df &#xff1a;列出文件系统的整体磁盘使用量du&#xff1a;检查磁盘空间使用量 df df df命令参数功能&#xff1a;检查文件系统的磁盘空间占用情况。可以利用该命令来获…

华人一作统一「视觉-语言」理解与生成:一键生成图像标注,完成视觉问答,Demo可玩...

来源&#xff1a;机器学习研究组订阅这个 BLIP 模型可以「看图说话」&#xff0c;提取图像的主要内容&#xff0c;不仅如此&#xff0c;它还能回答你提出的关于图像的问题。视觉 - 语言预训练 (Vision-Language Pre-training&#xff0c;VLP) 提高了许多视觉 - 语言任务的性能。…

2020年9月25日-01-项目启动(团队分工)+带宽,网络速度的计算

此博客用于记录2020年9月25日每日分享&#xff0c; 大概讲讲团队里的分工合作那些事儿。 关于带宽啊&#xff0c;网速啊之类的一些事儿 日期&#xff1a;2020年9月25日 主题&#xff1a; 团队合作怎么合作&#xff1f;有什么人&#xff1f;一般用什么工具&#xff1f;诸如此类…

群体决策是如何误入歧途的

1986年&#xff0c;刚刚升空不久就发生爆炸的挑战者号航天飞机。© Boing Boing来源&#xff1a; 利维坦文&#xff1a;Joshua Holden译&#xff1a;以实马利校对&#xff1a;兔子的凌波微步原文&#xff1a;nautil.us/what-makes-group-decisions-go-wrong-and-right-1340…