包含go语言tls1.3通讯的词条

TLS过程(DH 非对称加密) TLS 的目的便是解决数据的
一、Record 记录协议(对称加解密)
二、HandShake 握手,挥手
验证通讯双方身份
交换加解密的安全套件
协商加密解密参数
当一个TLS会话建立好之后,会使用对称加密的密钥去通信,那么如何实现事先将对称加密的密钥传递给TLS会话的另一方呢 。利用的就是非对称加密 。分对称加密比对称加密慢数千倍,所以只是使用对称加密传递之后加密使用的对称加密的密钥 。之后的加密安全性靠的是对称加密来解决 。
非对称加密是有一把公钥和一把私钥,公钥可以公开,而私钥不能 。
用公钥加密成密文,再将密文用私钥解密就能实现加解密过程 。
而用私钥加密,公钥解密就是签名认证过程 。
常见的非对称加密方式分为两大类
RSA 没有向前安全性,也就是需要每次的对称加密密钥的传递都是基于 公钥加密,服务端私钥解密 。如果服务端的私钥丢失了,那几年前的通信数据都有可能被解密 。所以这是极度不安全的,私钥的地位太重了 , 如果每次的加解密都是临时生成的密码来解决安全性,才不会对私钥的安全性有如此强的依赖 。在2013年的棱镜门事件中 , 某个CA机构迫于美国政府压力向其提交了CA的私钥,这就是十分危险的 。如果向前不安全,那么之前利用该CA私钥都会全部遭殃 。所以这里说的是更安全的 DH类非对称加密 。
下图就是DH密钥交换的TLS握手过程
DH 密钥交换协议 , Diffile-Hellman key Exchange,简称 DH 或 DHE。它可以让双方在完全没有对方任何预先信息的条件下通过一个不安全的信道创建一个密钥 。
1、客户端浏览器随机生成一个值Ra,计算Pa(x,y) = Ra*Q(x,y), Q(x,y)为全世界公认的某个椭圆曲线算法的基点 。将Pa(x,y)发送至服务器 。
2、服务器随机生成一个值Rb,计算 Pb(x,y) = Rb * Q(x,y) 。将Pb(x,y)发送到客户端浏览器 。
3、客户端计算Sa(x,y) = Ra * Pb(x,y),服务器计算Sb(x,y)=Rb*Pa(x,y)
4、算法保证了Sa=Sb=S, 提取其中的S的x向量作为密钥 。
为了解决上述DH的问题,引入了ECC椭圆曲线,进而进化为 ECDHE 算法,称为 Elliptic Curve Diffie-Hellman Key Exchange 。ECC和RSA 在288字节的长度下,破解RSA需要煮沸一勺水的能量,而破解相同位数的ECC 就需要煮沸整个地球水的能量 。RSA 为了提高安全性,只能靠增大密钥位数 。尴尬的是现在的超算越来越厉害 。量子计算下秀尔算法可8h内轻松破解2048位的RSA 。RSA只能再增大密钥位数,但是再增大位数,移动端设备就惨了,你增大的密钥是运营商要收取流量费用的 , 而且加解密太费电 。
ECC 的数学原理是椭圆曲线和离散对数 。椭圆曲线很复杂 。为了提升性能,还需要选择一个椭圆曲线,但是它不是真正的椭圆形 , 下面有图可以看到 , 只是运算上用到了椭圆算法 。
但是ECC也有很多问题 , 1、ECC 可能有后门 , 如NSA(美国国家安全局发布的一套算法),这个算法就是被怀疑被植入后门了 。2、而且ECC很多的算法都被注册专利了,一不小心就要吃官司 , 其专利大部分都被黑莓注册 。
ECC 椭圆曲线的定义
ECC 的算法原理过于复杂,这里表示我也看不懂 。点到为止吧 。(以后看懂了再来补充)
这里的抓包结果就是用的EC DH E 算法来进行密钥交换的 。这里选择的曲线是 secp256r1, 在这个曲线中,基点和参数已经给出了,PubKey 也给出了 。
在 TLS1.3 中,一般使用的 X25519 曲线 (蒙哥马利曲线)
Ngrok有时很不稳定,有什么更好的内网穿透方法?以下内容来自公众号逆锋起笔,关注每日干货及时送达
原文:v0w.top/2020/08/11/IntranetProxy
前言
本文以渗透的视角,总结几种个人常用的内网穿透,内网代理工具 , 介绍其简单原理和使用方法 。
1、nps-npc1.1 简介
nps是一款轻量级、高性能、功能强大的内网穿透代理服务器 。目前支持tcp、udp流量转发 , 可支持任何tcp、udp上层协议(访问内网网站、本地支付接口调试、ssh访问、远程桌面,内网dns解析等等……),此外还支持内网http代理、内网socks5代理、p2p等,并带有功能强大的web管理端 。
一台有公网IP的服务器(VPS)运行服务端(NPS)
一个或多个运行在内网的服务器或者PC运行客户端(NPC)
1.2 特点
Go语言编写
支持跨平台
支持多种协议的代理
web管理端
1.3 使用方法
NPS
安装配置
找到自己服务器相应版本的server:
cd ~
wget
tar xzvf linux_amd64_server.tar.gz
cd ~/nps
在nps目录下面会有一个nps可执行文件、conf配置目录和web网页目录,go语言tls1.3通讯我们只需要修改conf/nps.conf即可:
vim conf/nps.conf
需要改一下#web下面的几个参数,
web_host= 服务器IP或者域名
web_username= admin(登录用户名)
web_password= 你的密码
web_port=8080(web管理端口)
修改#bridge可以更改 NPC的连接端口 。比如我们拿到一台权限受限的服务器,有防火墙,可能只有部分端口(80,443)可以出网,就需要修改成出网端口 。
##bridge
bridge_type=tcp
bridge_port=443 # 修改连接端口
bridge_ip=0.0.0.0
启动
#Mac/Linux
./nps test|start|stop|restart|status 测试配置文件|启动|停止|重启|状态
#Windows
nps.exe test|start|stop|restart|status 测试配置文件|启动|停止|重启|状态
NPC
./npc -server=你的IP:8024 -vkey=唯一验证密码 -type=tcp
新建好客户端后 , 也可以在 中看到,详细的客户端连接命令:
在客户端界面可以通过新增的方式添加客户端连接,每一个连接的vkey都是唯一区分的 。
每一个客户端,在建立连接后,都可以建立多个不同协议的隧道 , 这一个个隧道就是不同的代理了 。
通过不同的协议和端口就可以连接代理的内网机器 。
2、frp
2.1 简介
frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP、UDP、HTTP、HTTPS 等多种协议 。可以将内网服务以安全、便捷的方式通过具有公网 IP 节点的中转暴露到公网 。
2.2 特点
客户端服务端通信支持 TCP、KCP 以及 Websocket 等多种协议 。
端口复用,多个服务通过同一个服务端端口暴露 。
跨平台,但是支持的比nps少一点
多种插件,提供很多功能
2.3 使用方法
下载:
以下内容摘自:. 通过 rdp 访问家里的机器
1.修改 frps.ini 文件,为了安全起见,这里最好配置一下身份验证,服务端和客户端的 common 配置中的token参数一致则身份验证通过:
# frps.ini
[common]
bind_port = 7000
# 用于身份验证,请自行修改,要保证服务端与客户端一致
token = abcdefgh
2.启动 frps:
./frps -c ./frps.ini
3.修改 frpc.ini 文件,假设 frps 所在服务器的公网 IP 为 x.x.x.x:
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
# 用于身份验证,请自行修改,要保证服务端与客户端一致
token = abcdefgh
[rdp]
type = tcp
local_ip = 127.0.0.1
local_port = 3389
remote_port = 6000
4.启动 frpc:
./frpc -c ./frpc.ini
5.通过 rdp 访问远程的机器,地址为:
x.x.x.x:6000
开机自启
针对 Windows 系统 , 为了便于使用,可以配置一下开机的时候静默启动 。
1.在 frpc.exe 的同级目录创建一个 start_frpc.vbs:
'start_frpc.vbs
'请根据实际情况修改路径
CreateObject("WScript.Shell").Run """D:\Program Files\frp_windows_amd64\frpc.exe""""-c""""D:\Program Files\frp_windows_amd64\frpc.ini""",0
2.复制 start_frpc.vbs 文件,打开以下目录,注意将
改为你的用户名:
C:\Users\\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
3.鼠标右击,粘贴为快捷方式即可 。
2. 通过 SSH 访问公司内网机器
frps 的部署步骤同上 。
1.启动 frpc,配置如下:
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
# 用于身份验证,请自行修改 , 要保证服务端与客户端一致
token = abcdefgh
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
2.通过 SSH 访问内网机器,假设用户名为 test:
ssh -oPort=6000 test@x.x.x.x
3. 通过自定义域名访问部署于内网的 Web 服务
1.修改 frps.ini 文件,设置 http 访问端口为 8080:
# frps.ini
[common]
bind_port = 7000
vhost_http_port = 8080
# 用于身份验证,请自行修改 , 要保证服务端与客户端一致
token = abcdefgh
2.启动 frps:
./frps -c ./frps.ini
3.修改 frpc.ini 文件 , 假设 frps 所在的服务器的 IP 为 x.x.x.x,local_port 为本地机器上 Web 服务对应的端口, 绑定自定义域名:
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
# 用于身份验证,请自行修改,要保证服务端与客户端一致
token = abcdefgh
[web]
type = http
local_port = 80
custom_domains =
4.启动 frpc:
./frpc -c ./frpc.ini
5 .将的域名 A 记录解析到 IPx.x.x.x,如果服务器已经有对应的域名,也可以将 CNAME 记录解析到服务器原先的域名 。
6.通过浏览器访问即可访问到处于内网机器上的 Web 服务 。
4. 对外提供简单的文件访问服务
通过static_file插件可以对外提供一个简单的基于 HTTP 的文件访问服务 。
frps 的部署步骤同上 。
1.启动 frpc,启用static_file插件 , 配置如下:
# frpc.ini
[common]
server_addr = x.x.x.x
server_port = 7000
# 用于身份验证,请自行修改,要保证服务端与客户端一致
token = abcdefgh
[test_static_file]
type = tcp
remote_port = 6000
plugin = static_file
# 要对外暴露的文件目录
plugin_local_path = /tmp/file
# 访问 url 中会被去除的前缀 , 保留的内容即为要访问的文件路径
plugin_strip_prefix = static
plugin_http_user = abc
plugin_http_passwd = abc
2.通过浏览器访问来查看位于/tmp/file目录下的文件,会要求输入已设置好的用户名和密码 。
常用功能
统计面板(Dashboard)
通过浏览器查看 frp 的状态以及代理统计信息展示 。
注:Dashboard 尚未针对大量的 proxy 数据展示做优化,如果出现 Dashboard 访问较慢的情况,请不要启用此功能 。
需要在 frps.ini 中指定 dashboard 服务使用的端口,即可开启此功能:
[common]
dashboard_port = 7500
# dashboard 用户名密码,默认都为 admin
dashboard_user = admin
dashboard_pwd = admin
打开浏览器通过http://[server_addr]:7500访问 dashboard 界面 , 用户名密码默认为admin 。
加密与压缩
这两个功能默认是不开启的,需要在 frpc.ini 中通过配置来为指定的代理启用加密与压缩的功能,压缩算法使用 snappy:
# frpc.ini
[ssh]
type = tcp
local_port = 22
remote_port = 6000
use_encryption = true
use_compression = true
如果公司内网防火墙对外网访问进行了流量识别与屏蔽,例如禁止了 SSH 协议等 , 通过设置use_encryption = true,将 frpc 与 frps 之间的通信内容加密传输,将会有效防止流量被拦截 。
如果传输的报文长度较长 , 通过设置use_compression = true对传输内容进行压缩,可以有效减小 frpc 与 frps 之间的网络流量,加快流量转发速度,但是会额外消耗一些 CPU 资源 。
[TLS
从 v0.25.0 版本开始 frpc 和 frps 之间支持通过 TLS 协议加密传输 。通过在frpc.ini的common中配置tls_enable = true来启用此功能,安全性更高 。
为了端口复用,frp 建立 TLS 连接的第一个字节为 0x17 。
注意:启用此功能后除 xtcp 外,不需要再设置 use_encryption 。
代理限速
目前支持在客户端的代理配置中设置代理级别的限速 , 限制单个 proxy 可以占用的带宽 。
# frpc.ini
[ssh]
type = tcp
local_port = 22
remote_port = 6000
bandwidth_limit = 1MB
在代理配置中增加bandwidth_limit字段启用此功能,目前仅支持MB和KB单位 。
范围端口映射
在 frpc 的配置文件中可以指定映射多个端口 , 目前只支持 tcp 和 udp 的类型 。
这一功能通过range:段落标记来实现,客户端会解析这个标记中的配置,将其拆分成多个 proxy,每一个 proxy 以数字为后缀命名 。
例如要映射本地 6000-6005, 6007 这 6 个端口 , 主要配置如下:
# frpc.ini
[range:test_tcp]
type = tcp
local_ip = 127.0.0.1
local_port = 6000-6006,6007
remote_port = 6000-6006,6007
实际连接成功后会创建 8 个 proxy,命名为test_tcp_0, test_tcp_1 ... test_tcp_7 。
3、ew3.1 简介
EW 是一套便携式的网络穿透工具,具有 SOCKS v5服务架设和端口转发两大核心功能,可在复杂网络环境下完成网络穿透 。但是,现在工具已经不更新了 。。。
3.2 特点
轻量级,C语言编写
可以设置多级代理
跨平台
但是只支持Socks5代理
3.3 使用方法以下使用方法均摘自:
以下所有样例,如无特殊说明代理端口均为1080,服务均为SOCKSv5代理服务.
该工具共有 6 种命令格式(ssocksd、rcsocks、rssocks、lcx_slave、lcx_listen、lcx_tran) 。
1. 正向 SOCKS v5 服务器
$ ./ew -s ssocksd -l 1080
2. 反弹 SOCKS v5 服务器
这个操作具体分两步:
a) 先在一台具有公网 ip 的主机A上运行以下命令:
$ ./ew -s rcsocks -l 1080 -e 8888
b) 在目标主机B上启动 SOCKS v5 服务 并反弹到公网主机的 8888端口
$ ./ew -s rssocks -d 1.1.1.1 -e 8888
成功 。
3. 多级级联
工具中自带的三条端口转发指令 , 它们的参数格式分别为:
$ ./ew -s lcx_listen -l 1080 -e 8888
$ ./ew -s lcx_tran -l 1080 -f 2.2.2.3 -g 9999
$ ./ew -s lcx_slave -d 1.1.1.1 -e 8888 -f 2.2.2.3 -g 9999
通过这些端口转发指令可以将处于网络深层的基于TCP的服务转发至根前,比如 SOCKS v5 。首先提供两个“二级级联”本地SOCKS测试样例:
a)lcx_tran的用法
$ ./ew -s ssocksd -l 9999
$ ./ew -s lcx_tran -l 1080 -f 127.0.0.1 -g 9999
b)lcx_listen、lcx_slave的用法
$ ./ew -s lcx_listen -l 1080 -e 8888
$ ./ew -s ssocksd -l 9999
$ ./ew -s lcx_slave -d 127.0.0.1 -e 8888 -f 127.0.0.1 -g 9999
再提供一个“三级级联”的本地SOCKS测试用例以供参考
$ ./ew -s rcsocks -l 1080 -e 8888
$ ./ew -s lcx_slave -d 127.0.0.1 -e 8888 -f 127.0.0.1 -g 9999
$ ./ew -s lcx_listen -l 9999 -e 7777
$ ./ew -s rssocks -d 127.0.0.1 -e 7777
数据流向:SOCKS v5 - 1080 - 8888 - 9999 - 7777 - rssocks
4、ngrok4.1 简介
4.2 特点
官方维护,一般较为稳定
跨平台,闭源
有流量记录和重发功能
4.3 使用方法
进入ngrok官网() , 注册ngrok账号并下载ngrok;
根据官网给定的授权码,运行如下授权命令;
./ngrok authtoken 1hAotxhmORtzCYvUc3BsxDBPh1H_******************
./ngrok http 80即可将机器的80端口http服务暴露到公网,并且会提供一个公网域名 。
可以通过官网的UI界面查看数据包和流量等等(但是要付费==、)
还可以通过一些命令将内网的文件和其go语言tls1.3通讯他TCP服务 暴露到公网中 。
有授权的设置文件共享
ngrok http -auth="user:password"
无授权的设置文件共享
ngrok http ":\\Users\\alan\\Public Folder"
将主机的3389的TCP端口暴露到公网
ngrok tcp 3389
更多使用方法参考:
内网渗透之内网穿透
开源内网穿透工具 frp 简单使用教程
go语言语法(基础语法篇)import "workname/packetfolder"
导入多个包
方法调用 包名.函数//不是函数或结构体所处文件或文件夹名
packagename.Func()
前面加个点表示省略调用go语言tls1.3通讯 , 那么调用该模块里面的函数 , 可以不用写模块名称了:
当导入一个包时,该包下的文件里所有init()函数都会被执行,然而 , 有些时候go语言tls1.3通讯我们并不需要把整个包都导入进来,仅仅是是希望它执行init()函数而已 。下划线的作用仅仅是为了调用init()函数,所以无法通过包名来调用包中的其他函数
import _ package
变量声明必须要使用否则会报错 。
全局变量运行声明但不使用 。
func 函数名 (参数1,参数2,...) (返回值a 类型a, 返回值b 类型b,...)
func 函数名 (参数1 , 参数2,...) (返回值类型1, 返回值类型2,...)
func (this *结构体名) 函数名(参数 string) (返回值类型1, 返回值类型2){}
使用大小来区分函数可见性
大写是public类型
小写是private类型
func prifunc int{}
func pubfunc int{}
声明静态变量
const value int
定义变量
var value int
声明一般类型、接口和结构体
声明函数
func function () int{}
go里面所有的空值对应如下
通道类型
内建函数 new 用来分配内存 , 它的第一个参数是一个类型 , 不是一个值,它的返回值是一个指向新分配类型零值的指针
func new(Type) *Type
[这位博主有非常详细的分析]
Go 语言支持并发 , 我们只需要通过 go 关键字来开启 goroutine 即可 。
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的 。
同一个程序中的所有 goroutine 共享同一个地址空间 。
语法格式如下:
通道(channel)是用来传递数据的一个数据结构 。
通道的声明
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯 。操作符 - 用于指定通道的方向,发送或接收 。如果未指定方向,则为双向通道 。
[这里有比较详细的用例]
go里面的空接口可以指代任何类型(无论是变量还是函数)
声明空接口
go里面的的强制类型转换语法为:
int(data)
如果是接口类型的强制转成其他类型的语法为:
go里面的强制转换是将值复制过去,所以在数据量的时候有比较高的运行代价
go语言做串口通信,我应该从什么地方入手,IO是什//创建一个串口通讯 SerialPort CurrentPort = null; CurrentPort = new SerialPort(); CurrentPortReadBufferSize = 128; CurrentPortPortName = comName; //端口号 CurrentPortBaudRate = bandRate; //比特率 CurrentPortParity =parity;/go语言做串口通信,我应该从什么地方入手,IO是什
我们应该使用 TLS1.3 吗 SSL(Socket Layer Security)和 TLS(Transport Layer Security) 都是属于安全协议,主要作用是保证客户端和服务端之间能安全通讯 。SSL是较早的协议,TLS 是 SSL的替代者 。
SSL 版本 1.0、2.0 和 3.0,TLS 版本 1.0、1.2 和 1.3 。SSL协议和TLS1.0 由于已过时被禁用,目前TLS 1.3 是互联网上部署最多的安全协议 , 它是TLS最新版本 ,它增强了过时的安全性,并增加了更多的触控性 。通过下面几点可以有个简单认识:
现代浏览器支持 TLS 1.2 和 TLS 1.3 协议,但 1.3 版本要好得多 。TLS 1.3 对早期版本进行了多项改进,最明显的是简化了TLS握手操作,使得握手时间变短、网站性能得以提升、改善了用户体验,另外支持的密码套件更安全和简单 。
密码套件
TLS/SSL 使用一种或多种密码套件 。密码套件是身份验证、加密和消息身份验证的算法组合 。TLS 1.2 版使用的算法存在一些弱点和安全漏洞 。在 TLS 1.3 中删除了这些算法:
另外一个很重要的更新是TLS1.3 支持perfect-forward-secrecy(PFS)算法 。
向前保密 (PFS)是特定密钥协商协议的一项功能 , 如果一个长周期的会话密钥被泄露,黑客就会截获大量数据,我们可以为每个会话生成唯一的会话密钥,单个会话密钥的泄露不会影响该会话之外的任何数据 。
TLS在早期版本的握手期间可以使用两种机制之一交换密钥:静态 RSA密钥和 Diffie-Hellman 密钥 。在 TLS1.3 中 , RSA 以及所有静态(非 PFS)密钥交换已被删除,只保留了DHE、ECDHE
可以查看网站的安全详情来确认它是否使用"ECDHE"或"DHE" 。
AES(Advanced Encryption Standard) 对称加密 , 它是 高级加密 标准 。早期的加密标准DES(Data Encryption Standard) 已被弃用 。
AES选择合适的 加密模式 很重要,应用比较多的两种模式 CBC 和 GCM 。
CBC 密码分组链接模式
明文分块,第一个块使用初始化向量,后面的每个明文块在加密前与前一个密文块进行异或运算 。
这种模式存在的问题:
CTR 计数模式
明文分块按顺序编号,通过加密"计数器"的连续值来生成下一个密钥流块 。CTR 模式非常适合在多核处理器上运行 , 明文块可以并行加密 。
GCM 伽罗瓦/计数器模式
GCM = CTRAuthentication 。其加密过程 , 明文块是按顺序编号的,然后这个块号与初始向量 组合并使用块密码E加密,然后将此加密的结果与明文进行异或以生成密文 。
简单来说,GCM 是 CTR 身份验证的组合,它更快、更安全 。它将接受流水线和并行化实现,并具有最小的计算延迟,所以它的应用更加广泛 。
客户端最低版本
客户端最低版本
一般建议同时兼容1.2和1.3
测试是否支持 TLS 1.2
测试是否支持 TLS 1.3
驳狗屎文 "我为什么放弃Go语言此篇文章流传甚广, 其实里面没啥干货,而且里面很多观点是有问题的. 这个文章在 golang-china 很早就讨论过了.
最近因为 Rust 1.0 和 1.1 的发布, 导致这个文章又出来毒害读者.
所以写了这篇反驳文章, 指出其中的问题.
有好几次,当我想起来的时候,总是会问自己:我为什么要放弃Go语言?这个决定是正确的吗?是明智和理性的吗?其实我一直在认真思考这个问题 。
开门见山地说,我当初放弃Go语言(golang),就是因为两个“不爽”:第一 , 对Go语言本身不爽;第二,对Go语言社区里的某些人不爽 。毫无疑问 , 这是非常主观的结论 。但是我有足够详实的客观的论据,用以支撑这个看似主观的结论 。
文末附有本文更新日志 。
确实是非常主观的结论, 因为里面有不少有问题的观点(用来忽悠Go小白还行).
第0节:我的Go语言经历
先说说我的经历吧 , 以避免被无缘无故地当作Go语言的低级黑 。
2009年底,Go语言(golang)第一个公开版本发布 , 笼罩着“Google公司制造”的光环,吸引了许多慕名而来的尝鲜者 , 我(Liigo)也身居其中,笼统的看了一些Go语言的资料,学习了基础的教程,因对其语法中的分号和花括号不满,很快就遗忘掉了 , 没拿它当一回事 。
在2009年Go刚发布时, 确实是因为“Google公司制造”的光环而吸引了(包括文章作者和诸多IT采访人员)很多低级的尝鲜者.
还好, 经过5年的发展, 这些纯粹因为光环来的投机者所剩已经不多了(Google趋势).
目前, 真正的Go用户早就将Go用于实际的生产了.
说到 其语法中的分号和花括号不满, 我想说这只是你的 个人主观感受, 还有很多人对Go的分号和花括号很满意,
包括水果公司的的 Swift 的语言设计者也很满意这种风格(Swift中的分号和花括号和Go基本相同).
如果只谈 个人主观感受, 我也可以说 Rust 的 fn 缩写也很蛋疼!
两年之后,2011年底,Go语言发布1.0的计划被提上日程,相关的报道又多起来 , 我再次关注它,重新评估之后决定深入参与Go语言 。我订阅了其users、nuts、dev、commits等官方邮件组,坚持每天阅读其中的电子邮件,以及开发者提交的每一次源代码更新,给Go提交了许多改进意见,甚至包括修改Go语言编译器源代码直接参与开发任务 。如此持续了数月时间 。
这个到是事实, 在 golang-china 有不少吵架的帖子, 感兴趣的可以去挖下, 我就不展开说了.
到2012年初,Go 1.0发布 , 语言和标准库都已经基本定型,不可能再有大幅改进,我对Go语言未能在1.0定型之前更上一个台阶、实现自我突破 , 甚至带着诸多明显缺陷走向1.0,感到非常失望 , 因而逐渐疏远了它(所以Go 1.0之后的事情我很少关心) 。后来看到即将发布的Go 1.1的Release Note,发现语言层面没有太大改变,只是在库和工具层面有所修补和改进,感到它尚在幼年就失去成长的动力,越发失望 。外加Go语言社区里的某些人 , 其中也包括Google公司负责开发Go语言的某些人,其态度、言行,让我极度厌恶 , 促使我决绝地离弃Go语言 。
真的不清楚楼主说的可以在 Go1.0 之前短时间内能实现的 重大改进和诸多明显缺陷 是什么.
如果是楼主说前面的 其语法中的分号和花括号不满 之类的重大改进, 我只能说这只是你的 个人主观感受 而已,
你的很多想法只能说服你自己, 没办法说服其他绝大部分人(不要以为像C或Rust那样什么特性都有就NB了, 各种NB特性加到一起只能是 要你命3000, 而绝对不会是什么 银弹).
Go 1.1的Release Note,发现语言层面没有太大改变. 语言层没有改变是是因为 Go1 作出的向后兼容的承诺. 对于工业级的语言来说, Go1 这个只能是优点. 如果连语言层在每个版本都会出现诸多大幅改进, 那谁还敢用Go语言来做生产开发呢(我承认Rust的改动很大胆, 但也说明了Rust还处于比较幼稚和任性的阶段)?
说 Go语言社区里的某些人固执 的观点我是同意的. 但是这些 固执 的人是可以讲道理的, 但是他们对很多东西的要求很高(特别是关于Go的设计哲学部分).
只要你给的建议有依据(语言的设计哲学是另外一回事情), 他们绝对不会盲目的拒绝(只是讨论的周期会比较长).
关于楼主提交的给Go文件添加BOM的文章, 需要补充说明下.
在Go1.0发布的时候, Go语言的源文件(.go)明确要求必须是UTF8编码的, 而且是无BOM的UTF8编码的.
注意: 这个 无BOM的UTF8编码 的限制仅仅是 针对 Go语言的源文件(.go).
这个限制并不是说不允许用户处理带BOM的UTF8的txt文件!
我觉得对于写Go程序来说, 这个限制是没有任何问题的, 到目前为止, 我还从来没有使用过带BOM的.go文件.
不仅是因为带BOM的.go文件没有太多的意义, 而且有很多的缺陷.
BOM的原意是用来表示编码是大端还是小端的, 主要用于UTF16和UTF32. 对于 UTF8 来说, BOM 没有任何存在的意义(正是Go的2个作者发明了UTF8, 彻底解决了全球的编码问题).
但是, 在现实中, 因为MS的txt记事本, 对于中文环境会将txt(甚至是C/C源文件)当作GBK编码(GBK是个烂编码),
为了区别到底是GBK还是UTF8, MS的记事本在前面加了BOM这个垃圾(被GBK占了茅坑), 这里的bom已经不是表示字节序本意了. 不知道有没有人用ms的记事本写网页, 然后生成一个带bom的utf8网页肯定很有意思.
这是MS的记事本的BUG: 它不支持生成无BOM的UTF8编码的文本文件!
这些是现实存在的带BOM的UTF8编码的文本文件, 但是它们肯定都不是Go语言源文件!
所以说, Go语言的源文件即使强制限制了无BOM的UTF8编码要求, 也是没有任何问题的(而且我还希望有这个限制).
虽然后来Go源文件接受带BOM的UTF8了, 但是运行 go fmt 之后, 还是会删除掉BOM的(因为BOM就是然并卵). 也就是说 带 BOM 的 Go 源文件是不符合 Go语言的编码风格的, go fmt 会强制删除 BOM 头.
前面说了BOM是MS带来的垃圾, 但是BOM的UTF8除了然并卵之外还有很多问题, 因为BOM在string的开头嵌入了垃圾,
导致正则表达式, string的链接运算等操作都被会被BOM这个垃圾所污染. 对于.go语言, 即使代码完全一样, 有BOM和无BOM会导致文件的MD5之类的校验码不同.
所以, 我觉得Go用户不用纠结BOM这个无关紧要的东西.
在上一个10年 , 我(Liigo)在我所属的公司里,深度参与了两个编程语言项目的开发 。我想,对于如何判断某个编程语言的优劣 , 或者说至少对于如何判断某个编程语言是否适合于我自己,我应该还是有一点发言权的 。
第1节:我为什么对Go语言不爽?
Go语言有很多让我不爽之处,这里列出我现在还能记起的其中一部分,排名基本上不分先后 。读者们耐心地看完之后,还能淡定地说一句“我不在乎”吗?
1.1 不允许左花括号另起一行
关于对花括号的摆放,在C语言、C、Java、C#等社区中,十余年来存在持续争议,从未形成一致意见 。在我看来 , 这本来就是主观倾向很重的抉择 , 不违反原则不涉及是非的情况下,不应该搞一刀切 , 让程序员或团队自己选择就足够了 。编程语言本身强行限制,把自己的喜好强加给别人,得不偿失 。无论倾向于其中任意一种,必然得罪与其对立的一群人 。虽然我现在已经习惯了把左花括号放在行尾,但一想到被禁止其他选择,就感到十分不爽 。Go语言这这个问题上,没有做到“团结一切可以团结的力量”不说,还有意给自己树敌 , 太失败了 。
我觉得Go最伟大的发明是 go fmt, 从此Go用户不会再有花括弧的位置这种无聊争论了(当然也少了不少灌水和上tiobe排名的机会).
是这优点, Swift 语言也使用和 Go 类似的风格(当然楼主也可能鄙视swift的作者).
1.2 编译器莫名其妙地给行尾加上分号
对Go语言本身而言,行尾的分号是可以省略的 。但是在其编译器(gc)的实现中,为了方便编译器开发者,却在词法分析阶段强行添加了行尾的分号,反过来又影响到语言规范,对“怎样添加分号”做出特殊规定 。这种变态做法前无古人 。在左花括号被意外放到下一行行首的情况下 , 它自动在上一行行尾添加的分号,会导致莫名其妙的编译错误(Go 1.0之前),连它自己都解释不明白 。如果实在处理不好分号,干脆不要省略分号得了;或者,Scala和JavaScript的编译器是开源的,跟它们学学怎么处理省略行尾分号可以吗?
又是楼主的 个人主观感受, 不过我很喜欢这个特性. Swift 语言也是类似.
1.3 极度强调编译速度 , 不惜放弃本应提供的功能
程序员是人不是神 , 编码过程中免不了因为大意或疏忽犯一些错 。其中有一些,是大家集体性的很容易就中招的错误(Go语言里的例子我暂时想不起来,C里的例子有“基类析构函数不是虚函数”) 。这时候编译器应该站出来,多做一些检查、约束、核对性工作,尽量阻止常规错误的发生 , 尽量不让有潜在错误的代码编译通过 , 必要时给出一些警告或提示,让程序员留意 。编译器不就是机器么,不就是应该多做脏活累活杂活、减少人的心智负担么?编译器多做一项检查,可能会避免数十万程序员今后多年内无数次犯同样的错误,节省的时间不计其数 , 这是功德无量的好事 。但是Go编译器的作者们可不这么想 , 他们不愿意自己多花几个小时给编译器增加新功能,觉得那是亏本,反而减慢了编译速度 。他们以影响编译速度为由,拒绝了很多对编译器改进的要求 。典型的因噎废食 。强调编译速度固然值得赞赏,但如果因此放弃应有的功能,我不赞成 。
编译速度是很重要的, 如果编译速度够慢, 语言再好也不会有人使用的.
比如C/C的增量编译/预编译头文件/并发编译都是为了提高编译速度.
Rust1.1 也号称 比 1.0 的编译时间减少了32% (注意: 不是运行速度).
当然, Go刚面世的时候, 编译速度是其中的一个设计目标.
不过我想楼主, 可能想说的是因为编译器自己添加分号而导致的编译错误的问题.
我觉得Go中 { 不能另起一行是语言特性, 如果修复这个就是引入了新的错误.
其他的我真想不起来还有哪些 调编译速度,不惜放弃本应提供的功能 (不要提泛型, 那是因为还没有好的设计).
1.4 错误处理机制太原始
在Go语言中处理错误的基本模式是:函数通常返回多个值 , 其中最后一个值是error类型,用于表示错误类型极其描述;调用者每次调用完一个函数,都需要检查这个error并进行相应的错误处理:if err != nil { /*这种代码写多了不想吐么*/ } 。此模式跟C语言那种很原始的错误处理相比如出一辙,并无实质性改进 。实际应用中很容易形成多层嵌套的if else语句,可以想一想这个编码场景:先判断文件是否存在,如果存在则打开文件,如果打开成功则读取文件,如果读取成功再写入一段数据,最后关闭文件,别忘了还要处理每一步骤中出现错误的情况,这代码写出来得有多变态、多丑陋?实践中普遍的做法是,判断操作出错后提前return,以避免多层花括号嵌套,但这么做的后果是,许多错误处理代码被放在前面突出的位置,常规的处理逻辑反而被掩埋到后面去了,代码可读性极差 。而且,error对象的标准接口只能返回一个错误文本,有时候调用者为了区分不同的错误类型,甚至需要解析该文本 。除此之外,你只能手工强制转换error类型到特定子类型(静态类型的优势没了) 。至于panic - recover机制,致命的缺陷是不能跨越库的边界使用,注定是一个半成品,最多只能在自己的pkg里面玩一玩 。Java的异常处理虽然也有自身的问题(比如Checked Exceptions),但总体上还是比Go的错误处理高明很多 。
话说, 软件开发都发展了半个世纪, 还是无实质性改进. 不要以为弄一个异常的语法糖就是革命了.
我只能说错误和异常是2个不同的东西, 将所有错误当作异常那是SB行为.
正因为有异常这个所谓的银弹, 导致很多等着别人帮忙擦屁股的行为(注意 shit 函数抛出的绝对不会是一种类型的 shit, 而被其间接调用的各种 xxx_shit 也可能抛出各种类型的异常, 这就导致 catch 失控了):
int main() {
try {
shit();
} catch( /* 到底有几千种 shit ? */) {
...
}
}
Go的建议是 panic - recover 不跨越边界, 也就是要求正常的错误要由pkg的处理掉.
这是负责任的行为.
再说Go是面向并发的编程语言, 在海量的 goroutine 中使用 try/catch 是不是有一种不伦不类的感觉呢?
1.5 垃圾回收器(GC)不完善、有重大缺陷
在Go 1.0前夕,其垃圾回收器在32位环境下有内存泄漏,一直拖着不肯改进,这且不说 。Go语言垃圾回收器真正致命的缺陷是,会导致整个进程不可预知的间歇性停顿 。像某些大型后台服务程序,如游戏服务器、APP容器等,由于占用内存巨大,其内存对象数量极多,GC完成一次回收周期,可能需要数秒甚至更长时间 , 这段时间内 , 整个服务进程是阻塞的、停顿的,在外界看来就是服务中断、无响应,再牛逼的并发机制到了这里统统失效 。垃圾回收器定期启动,每次启动就导致短暂的服务中断,这样下去,还有人敢用吗?这可是后台服务器进程,是Go语言的重点应用领域 。以上现象可不是我假设出来的,而是事实存在的现实问题 , 受其严重困扰的也不是一家两家了(2013年底ECUG Con 2013,京东的刘奇提到了Go语言的GC、defer、标准库实现是性能杀手,最大的痛苦是GC;美团的沈锋也提到Go语言的GC导致后台服务间隔性停顿是最大的问题 。更早的网络游戏仙侠道开发团队也曾受Go垃圾回收的沉重打击) 。在实践中,你必须努力减少进程中的对象数量,以便把GC导致的间歇性停顿控制在可接受范围内 。除此之外你别无选择(难道你还想自己更换GC算法、甚至砍掉GC?那还是Go语言吗?) 。跳出圈外,我近期一直在思考,一定需要垃圾回收器吗?没有垃圾回收器就一定是历史的倒退吗?(可能会新写一篇博客文章专题探讨 。)
这是说的是32位系统, 这绝对不是Go语言的重点应用领域!! 我可以说Go出生就是面向64位系统和多核心CPU环境设计的. (再说 Rust 目前好像还不支持 XP 吧, 这可不可以算是影响巨大?)
32位当时是有问题, 但是对实际生产影响并不大(请问楼主还是在用32位系统吗, 还只安装4GB的内存吗). 如果是8位单片机环境, 建议就不要用Go语言了, 直接C语言好了.
而且这个问题早就不存在了(大家可以去看Go的发布日志).
Go的出生也就5年时间, GC的完善和改进是一个持续的工作, 2015年8月将发布的 Go1.5将采用并行GC.
关于GC的被人诟病的地方是会导致卡顿, 但是我以为这个主要是因为GC的实现还不够完美而导致的.
如果是完美的并发和增量的GC, 那应该不会出现大的卡顿问题的.
当然, 如果非要实时性, 那用C好了(实时并不表示性能高, 只是响应时间可控).
对于Rust之类没有GC的语言来说, 想很方便的开发并发的后台程序那几乎是不可能的.
不要总是吹Rust能代替底层/中层/上层的开发, 我们要看有谁用Rust真的做了什么.
1.6 禁止未使用变量和多余import
Go编译器不允许存在被未被使用的变量和多余的import,如果存在,必然导致编译错误 。但是现实情况是,在代码编写、重构、调试过程中,例如,临时性的注释掉一行代码,很容易就会导致同时出现未使用的变量和多余的import,直接编译错误了,你必须相应的把变量定义注释掉,再翻页回到文件首部把多余的import也注释掉,……等事情办完了 , 想把刚才注释的代码找回来,又要好几个麻烦的步骤 。还有一个让人蛋疼的问题,编写数据库相关的代码时,如果你import某数据库驱动的pkg,它编译给你报错,说不需要import这个未被使用的pkg;但如果你听信编译器的话删掉该import,编译是通过了 , 运行时必然报错,说找不到数据库驱动;你看看程序员被折腾的两边不是人,最后不得不请出大神:import _ 。对待这种问题 , 一个比较好的解决方案是,视其为编译警告而非编译错误 。但是Go语言开发者很固执,不容许这种折中方案 。
这个问题我只能说楼主的吐槽真的是没水平.
为何不使用的是错误而不是警告? 这是为了将低级的bug消灭在编译阶段(大家可以想下C/C的那么多警告有什么卵用).
而且, import 即使没有使用的话, 也是用副作用的, 因为 import 会导致 init 和全局变量的初始化.
如果某些代码没有使用, 为何要执行 init 这些初始化呢?
如果是因为调试而添加的变量, 那么调试完删除不是很正常的要求吗?
如果是因为调试而要导入fmt或log之类的包, 删除调试代码后又导致 import 错误的花,
楼主难道不知道在一个独立的文件包装下类似的辅助调试的函数吗?
import (
"fmt"
"log"
)
func logf(format string, a ...interface{}) {
file, line := callerFileLine()
fmt.Fprintf(os.Stderr, "%s:%d: ", file, line)
fmt.Fprintf(os.Stderr, format, a...)
}
func fatalf(format string, a ...interface{}) {
file, line := callerFileLine()
fmt.Fprintf(os.Stderr, "%s:%d: ", file, line)
fmt.Fprintf(os.Stderr, format, a...)
os.Exit(1)
}
import _ 是有明确行为的用法, 就是为了执行包中的 init 等函数(可以做某些注册操作).
将警告当作错误是Go的一个哲学, 当然在楼主看来这是白痴做法.
1.7 创建对象的方式太多令人纠结
创建对象的方式 , 调用new函数、调用make函数、调用New方法、使用花括号语法直接初始化结构体,你选哪一种?不好选择,因为没有一个固定的模式 。从实践中看 , 如果要创建一个语言内置类型(如channel、map)的对象,通常用make函数创建;如果要创建标准库或第三方库定义的类型的对象,首先要去文档里找一下有没有New方法 , 如果有就最好调用New方法创建对象,如果没有New方法,则退而求其次 , 用初始化结构体的方式创建其对象 。这个过程颇为周折,不像C、Java、C#那样直接new就行了 。
C的new是狗屎. new导致的问题是构造函数和普通函数的行为不一致, 这个补丁特性真的没啥优越的.
我还是喜欢C语言的 fopen 和 malloc 之类构造函数, 构造函数就是普通函数, Go语言中也是这样.
C中, 除了构造不兼容普通函数, 析构函数也是不兼容普通函数. 这个而引入的坑有很多吧.
1.8 对象没有构造函数和析构函数
没有构造函数还好说,毕竟还有自定义的New方法 , 大致也算是构造函数了 。没有析构函数就比较难受了,没法实现RAII 。额外的人工处理资源清理工作,无疑加重了程序员的心智负担 。没人性?。瓜游颐浅绦蛟奔影嗷股俾穑緾里有析构函数,Java里虽然没有析构函数但是有人家finally语句啊,Go呢,什么都没有 。没错,你有个defer,可是那个defer问题更大,详见下文吧 。
defer 可以覆盖析构函数的行为, 当然 defer 还有其他的任务. Swift2.0 也引入了一个简化版的 defer 特性.
1.9 defer语句的语义设定不甚合理
Go语言设计defer语句的出发点是好的 , 把释放资源的“代码”放在靠近创建资源的地方,但把释放资源的“动作”推迟(defer)到函数返回前执行 。遗憾的是其执行时机的设置似乎有些不甚合理 。设想有一个需要长期运行的函数,其中有无限循环语句 , 在循环体内不断的创建资源(或分配内存),并用defer语句确保释放 。由于函数一直运行没有返回 , 所有defer语句都得不到执行,循环过程中创建的大量短暂性资源一直积累着,得不到回收 。而且,系统为了存储defer列表还要额外占用资源,也是持续增加的 。这样下去,过不了多久,整个系统就要因为资源耗尽而崩溃 。像这类长期运行的函数 , http.ListenAndServe()就是典型的例子 。在Go语言重点应用领域,可以说几乎每一个后台服务程序都必然有这么一类函数,往往还都是程序的核心部分 。如果程序员不小心在这些函数中使用了defer语句,可以说后患无穷 。如果语言设计者把defer的语义设定为在所属代码块结束时(而非函数返回时)执行,是不是更好一点呢?可是Go 1.0早已发布定型,为了保持向后兼容性,已经不可能改变了 。小心使用defer语句!一不小心就中招 。
前面说到 defer 还有其他的任务, 也就是 defer 中执行的 recover 可以捕获 panic 抛出的异常.
还有 defer 可以在 return 之后修改命名的返回值.
上面2个工作要求 defer 只能在函数退出时来执行.
楼主说的 defer 是类似 Swift2.0 中 defer 的行为, 但是 Swift2.0 中 defer 是没有前面2个特性的.
Go中的defer是以函数作用域作为触发的条件的, 是会导致楼主说的在 for 中执行的错误用法(哪个语言没有坑呢?).
不过 for 中 局部 defer 也是有办法的 (Go中的defer是以函数作用域):
for {
func(){
f, err := os.Open(...)
defer f.Close()
}()
}
在 for 中做一个闭包函数就可以了. 自己不会用不要怪别人没告诉你.
1.10 许多语言内置设施不支持用户定义的类型
for in、make、range、channel、map等都仅支持语言内置类型,不支持用户定义的类型(?) 。用户定义的类型没法支持for in循环,用户不能编写像make、range那样“参数类型和个数”甚至“返回值类型和个数”都可变的函数,不能编写像channel、map那样类似泛型的数据类型 。语言内置的那些东西,处处充斥着斧凿的痕迹 。这体现了语言设计的局限性、封闭性、不完善 , 可扩展性差,像是新手作品——且不论其设计者和实现者如何权威 。延伸阅读:Go语言是30年前的陈旧设计思想,用户定义的东西几乎都是二等公民(Tikhon Jelvis) 。
说到底, 这个是因为对泛型支持的不完备导致的.
Go语言是没啥NB的特性, 但是Go的特性和工具组合在一起就是好用.
这就是Go语言NB的地方.
1.11 没有泛型支持,常见数据类型接口丑陋
没有泛型的话,List、Set、Tree这些常见的基础性数据类型的接口就只能很丑陋:放进去的对象是一个具体的类型,取出来之后成了无类型的interface{}(可以视为所有类型的基础类型) , 还得强制类型转换之后才能继续使用 , 令人无语 。Go语言缺少min、max这类函数,求数值绝对值的函数abs只接收/返回双精度小数类型 , 排序接口只能借助sort.Interface无奈的回避了被比较对象的类型,等等等等,都是没有泛型导致的结果 。没有泛型,接口很难优雅起来 。Go开发者没有明确拒绝泛型,只是说还没有找到很好的方法实现泛型(能不能学学已经开源的语言呀) 。现实是 , Go 1.0已经定型,泛型还没有,那些丑陋的接口为了保持向后兼容必须长期存在着 。
Go有自己的哲学, 如果能有和目前哲学不冲突的泛型实现, 他们是不会反对的.
如果只是简单学学(或者叫抄袭)已经开源的语言的语法, 那是C的设计风格(或者说C从来都是这样设计的, 有什么特性就抄什么), 导致了各种脑裂的编程风格.
编译时泛型和运行时泛型可能是无法完全兼容的, 看这个例子:
type AdderT interface {
Add(a, b T) T
}
【包含go语言tls1.3通讯的词条】go语言tls1.3通讯的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于、go语言tls1.3通讯的信息别忘了在本站进行查找喔 。

    推荐阅读