使用go语言编写一个播放器,直接看效果
创新互联坚持“要么做到,要么别承诺”的工作理念,服务领域包括:成都网站设计、成都网站建设、外贸网站建设、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的沅陵网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
整体思路是使用ffmpeg解码,然后使用xui进行渲染,音频播放暂时还没调通,以下是源码
三次握手:
1. 主动发起连接请求端(客户端),发送 SYN 标志位,携带数据包、包号
2. 被动接收连接请求端(服务器),接收 SYN,回复 ACK,携带应答序列号。同时,发送SYN标志位,携带数据包、包号
3. 主动发起连接请求端(客户端),接收SYN 标志位,回复 ACK。
被动端(服务器)接收 ACK —— 标志着 三次握手建立完成( Accept()/Dial() 返回 )
四次挥手:
1. 主动请求断开连接端(客户端), 发送 FIN标志,携带数据包
2. 被动接受断开连接端(服务器), 发送 ACK标志,携带应答序列号。 —— 半关闭完成。
3. 被动接受断开连接端(服务器), 发送 FIN标志,携带数据包
4. 主动请求断开连接端(客户端), 发送 最后一个 ACK标志,携带应答序列号。—— 发送完成,客户端不会直接退出,等 2MSL时长。
等 2MSL待目的:确保服务器 收到最后一个ACK
滑动窗口:
通知对端本地存储数据的 缓冲区容量。—— write 函数在对端 缓冲区满时,有可能阻塞。
TCP状态转换:
1. 主动发起连接请求端:
CLOSED —— 发送SYN —— SYN_SENT(了解) —— 接收ACK、SYN,回发 ACK —— ESTABLISHED (数据通信)
2. 主动关闭连接请求端:
ESTABLISHED —— 发送FIN —— FIN_WAIT_1 —— 接收ACK —— FIN_WAIT_2 (半关闭、主动端)
—— 接收FIN、回复ACK —— TIME_WAIT (主动端) —— 等 2MSL 时长 —— CLOSED
3. 被动建立连接请求端:
CLOSED —— LISTEN —— 接收SYN、发送ACK、SYN —— SYN_RCVD —— 接收 ACK —— ESTABLISHED (数据通信)
4. 被动断开连接请求端:
ESTABLISHED —— 接收 FIN、发送 ACK —— CLOSE_WAIT —— 发送 FIN —— LAST_ACK —— 接收ACK —— CLOSED
windows下查看TCP状态转换:
netstat -an | findstr 端口号
Linux下查看TCP状态转换:
netstat -an | grep 端口号
TCP和UDP对比:
TCP: 面向连接的可靠的数据包传递。 针对不稳定的 网络层,完全弥补。ACK
UDP:无连接不可靠的报文传输。 针对不稳定的 网络层,完全不弥补。还原网络真实状态。
优点 缺点
TCP: 可靠、顺序、稳定 系统资源消耗大,程序实现繁复、速度慢
UDP:系统资源消耗小,程序实现简单、速度快 不可靠、无序、不稳定
使用场景:
TCP:大文件、可靠数据传输。 对数据的 稳定性、准确性、一致性要求较高的场合。
UDP:应用于对数据时效性要求较高的场合。 网络直播、电话会议、视频直播、网络游戏。
UDP-CS-Server实现流程:
1. 创建 udp地址结构 ResolveUDPAddr(“协议”, “IP:port”) —— udpAddr 本质 struct{IP、port}
2. 创建用于 数据通信的 socket ListenUDP(“协议”, udpAddr ) —— udpConn (socket)
3. 从客户端读取数据,获取对端的地址 udpConn.ReadFromUDP() —— 返回:n,clientAddr, err
4. 发送数据包给 客户端 udpConn.WriteToUDP("数据", clientAddr)
UDP-CS-Client实现流程:
1. 创建用于通信的 socket。 net.Dial("udp", "服务器IP:port") —— udpConn (socket)
2. 以后流程参见 TCP客户端实现源码。
UDPserver默认就支持并发!
------------------------------------
命令行参数: 在main函数启动时,向整个程序传参。 【重点】
语法: go run xxx.go argv1 argv2 argv3 argv4 。。。
xxx.exe: 第 0 个参数。
argv1 :第 1 个参数。
argv2 :第 2 个参数。
argv3 :第 3 个参数。
argv4 :第 4 个参数。
使用: list := os.Args 提取所有命令行参数。
获取文件属性函数:
os.stat(文件访问绝对路径) —— fileInfo 接口
fileInfo 包含 两个接口。
Name() 获取文件名。 不带访问路径
Size() 获取文件大小。
网络文件传输 —— 发送端(客户端)
1. 获取命令行参数,得到文件名(带路径)filePath list := os.Args
2. 使用 os.stat() 获取 文件名(不带路径)fileName
3. 创建 用于数据传输的 socket net.Dial("tcp", “服务器IP+port”) —— conn
4. 发送文件名(不带路径) 给接收端, conn.write()
5. 读取 接收端回发“ok”,判断无误。封装函数 sendFile(filePath, conn) 发送文件内容
6. 实现 sendFile(filePath, conn)
1) 只读打开文件 os.Open(filePath)
for {
2) 从文件中读数据 f.Read(buf)
3) 将读到的数据写到socket中 conn.write(buf[:n])
4)判断读取文件的 结尾。 io.EOF. 跳出循环
}
网络文件传输 —— 接收端(服务器)
1. 创建用于监听的 socket net.Listen() —— listener
2. 借助listener 创建用于 通信的 socket listener.Accpet() —— conn
3. 读取 conn.read() 发送端的 文件名, 保存至本地。
4. 回发 “ok”应答 发送端。
5. 封装函数,接收文件内容 recvFile(文件路径)
1) f = os.Create(带有路径的文件名)
for {
2)从 socket中读取发送端发送的 文件内容 。 conn.read(buf)
3) 将读到的数据 保存至本地文件 f.Write(buf[:n])
4) 判断 读取conn 结束, 代表文件传输完成。 n == 0 break
}
用户数据报协议(User Datagram Protocol,缩写为UDP),又称用户数据报文协议,是一个简单的面向数据报(package-oriented)的传输层协议,正式规范为RFC 768。
UDP只提供数据的不可靠传递,它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份(所以UDP有时候也被认为是不可靠的数据报协议)。
UDP在IP数据报的头部仅仅加入了复用和数据校验。
由于缺乏可靠性且属于非连接导向协议,UDP应用一般必须允许一定量的丢包、出错和复制粘贴。
1 在接收udp包时,如果接收包时给定的buffer太小的话,就要自己解决粘包问题。
2 udp包的发送和接收不保证一定成功,不保证按正确顺序抵达。
3 如果不允许丢包的情况出现的话,要有重发机制来保证,如:反馈机制确认。
服务端
客户端
参考:
Goroutine并发调度模型深度解析手撸一个协程池
Golang 的 goroutine 是如何实现的?
Golang - 调度剖析【第二部分】
OS线程初始栈为2MB。Go语言中,每个goroutine采用动态扩容方式,初始2KB,按需增长,最大1G。此外GC会收缩栈空间。
BTW,增长扩容都是有代价的,需要copy数据到新的stack,所以初始2KB可能有些性能问题。
更多关于stack的内容,可以参见大佬的文章。 聊一聊goroutine stack
用户线程的调度以及生命周期管理都是用户层面,Go语言自己实现的,不借助OS系统调用,减少系统资源消耗。
Go语言采用两级线程模型,即用户线程与内核线程KSE(kernel scheduling entity)是M:N的。最终goroutine还是会交给OS线程执行,但是需要一个中介,提供上下文。这就是G-M-P模型
Go调度器有两个不同的运行队列:
go1.10\src\runtime\runtime2.go
Go调度器根据事件进行上下文切换。
调度的目的就是防止M堵塞,空闲,系统进程切换。
详见 Golang - 调度剖析【第二部分】
Linux可以通过epoll实现网络调用,统称网络轮询器N(Net Poller)。
文件IO操作
上面都是防止M堵塞,任务窃取是防止M空闲
每个M都有一个特殊的G,g0。用于执行调度,gc,栈管理等任务,所以g0的栈称为调度栈。g0的栈不会自动增长,不会被gc,来自os线程的栈。
go1.10\src\runtime\proc.go
G没办法自己运行,必须通过M运行
M通过通过调度,执行G
从M挂载P的runq中找到G,执行G
1. 介绍
最近在研究一些消息中间件,常用的MQ如RabbitMQ,ActiveMQ,Kafka等。NSQ是一个基于Go语言的分布式实时消息平台,它基于MIT开源协议发布,由bitly公司开源出来的一款简单易用的消息中间件。
官方和第三方还为NSQ开发了众多客户端功能库,如官方提供的基于HTTP的nsqd、Go客户端go-nsq、Python客户端pynsq、基于Node.js的JavaScript客户端nsqjs、异步C客户端libnsq、Java客户端nsq-java以及基于各种语言的众多第三方客户端功能库。
1.1 Features
1). Distributed
NSQ提供了分布式的,去中心化,且没有单点故障的拓扑结构,稳定的消息传输发布保障,能够具有高容错和HA(高可用)特性。
2). Scalable易于扩展
NSQ支持水平扩展,没有中心化的brokers。内置的发现服务简化了在集群中增加节点。同时支持pub-sub和load-balanced 的消息分发。
3). Ops Friendly
NSQ非常容易配置和部署,生来就绑定了一个管理界面。二进制包没有运行时依赖。官方有Docker image。
4.Integrated高度集成
官方的 Go 和 Python库都有提供。而且为大多数语言提供了库。
1.2 组件
1.3 拓扑结构
NSQ推荐通过他们相应的nsqd实例使用协同定位发布者,这意味着即使面对网络分区,消息也会被保存在本地,直到它们被一个消费者读取。更重要的是,发布者不必去发现其他的nsqd节点,他们总是可以向本地实例发布消息。
NSQ
首先,一个发布者向它的本地nsqd发送消息,要做到这点,首先要先打开一个连接,然后发送一个包含topic和消息主体的发布命令,在这种情况下,我们将消息发布到事件topic上以分散到我们不同的worker中。
事件topic会复制这些消息并且在每一个连接topic的channel上进行排队,在我们的案例中,有三个channel,它们其中之一作为档案channel。消费者会获取这些消息并且上传到S3。
nsqd
每个channel的消息都会进行排队,直到一个worker把他们消费,如果此队列超出了内存限制,消息将会被写入到磁盘中。Nsqd节点首先会向nsqlookup广播他们的位置信息,一旦它们注册成功,worker将会从nsqlookup服务器节点上发现所有包含事件topic的nsqd节点。
nsqlookupd
2. Internals
2.1 消息传递担保
1)客户表示已经准备好接收消息
2)NSQ 发送一条消息,并暂时将数据存储在本地(在 re-queue 或 timeout)
3)客户端回复 FIN(结束)或 REQ(重新排队)分别指示成功或失败。如果客户端没有回复, NSQ 会在设定的时间超时,自动重新排队消息
这确保了消息丢失唯一可能的情况是不正常结束 nsqd 进程。在这种情况下,这是在内存中的任何信息(或任何缓冲未刷新到磁盘)都将丢失。
如何防止消息丢失是最重要的,即使是这个意外情况可以得到缓解。一种解决方案是构成冗余 nsqd对(在不同的主机上)接收消息的相同部分的副本。因为你实现的消费者是幂等的,以两倍时间处理这些消息不会对下游造成影响,并使得系统能够承受任何单一节点故障而不会丢失信息。
2.2 简化配置和管理
单个 nsqd 实例被设计成可以同时处理多个数据流。流被称为“话题”和话题有 1 个或多个“通道”。每个通道都接收到一个话题中所有消息的拷贝。在实践中,一个通道映射到下行服务消费一个话题。
在更底的层面,每个 nsqd 有一个与 nsqlookupd 的长期 TCP 连接,定期推动其状态。这个数据被 nsqlookupd 用于给消费者通知 nsqd 地址。对于消费者来说,一个暴露的 HTTP /lookup 接口用于轮询。为话题引入一个新的消费者,只需启动一个配置了 nsqlookup 实例地址的 NSQ 客户端。无需为添加任何新的消费者或生产者更改配置,大大降低了开销和复杂性。
2.3 消除单点故障
NSQ被设计以分布的方式被使用。nsqd 客户端(通过 TCP )连接到指定话题的所有生产者实例。没有中间人,没有消息代理,也没有单点故障。
这种拓扑结构消除单链,聚合,反馈。相反,你的消费者直接访问所有生产者。从技术上讲,哪个客户端连接到哪个 NSQ 不重要,只要有足够的消费者连接到所有生产者,以满足大量的消息,保证所有东西最终将被处理。对于 nsqlookupd,高可用性是通过运行多个实例来实现。他们不直接相互通信和数据被认为是最终一致。消费者轮询所有的配置的 nsqlookupd 实例和合并 response。失败的,无法访问的,或以其他方式故障的节点不会让系统陷于停顿。
2.4 效率
对于数据的协议,通过推送数据到客户端最大限度地提高性能和吞吐量的,而不是等待客户端拉数据。这个概念,称之为 RDY 状态,基本上是客户端流量控制的一种形式。
efficiency
2.5 心跳和超时
组合应用级别的心跳和 RDY 状态,避免头阻塞现象,也可能使心跳无用(即,如果消费者是在后面的处理消息流的接收缓冲区中,操作系统将被填满,堵心跳)为了保证进度,所有的网络 IO 时间上限势必与配置的心跳间隔相关联。这意味着,你可以从字面上拔掉之间的网络连接 nsqd 和消费者,它会检测并正确处理错误。当检测到一个致命错误,客户端连接被强制关闭。在传输中的消息会超时而重新排队等待传递到另一个消费者。最后,错误会被记录并累计到各种内部指标。
2.6 分布式
因为NSQ没有在守护程序之间共享信息,所以它从一开始就是为了分布式操作而生。个别的机器可以随便宕机随便启动而不会影响到系统的其余部分,消息发布者可以在本地发布,即使面对网络分区。
这种“分布式优先”的设计理念意味着NSQ基本上可以永远不断地扩展,需要更高的吞吐量?那就添加更多的nsqd吧。唯一的共享状态就是保存在lookup节点上,甚至它们不需要全局视图,配置某些nsqd注册到某些lookup节点上这是很简单的配置,唯一关键的地方就是消费者可以通过lookup节点获取所有完整的节点集。清晰的故障事件——NSQ在组件内建立了一套明确关于可能导致故障的的故障权衡机制,这对消息传递和恢复都有意义。虽然它们可能不像Kafka系统那样提供严格的保证级别,但NSQ简单的操作使故障情况非常明显。
2.7 no replication
不像其他的队列组件,NSQ并没有提供任何形式的复制和集群,也正是这点让它能够如此简单地运行,但它确实对于一些高保证性高可靠性的消息发布没有足够的保证。我们可以通过降低文件同步的时间来部分避免,只需通过一个标志配置,通过EBS支持我们的队列。但是这样仍然存在一个消息被发布后马上死亡,丢失了有效的写入的情况。
2.8 没有严格的顺序
虽然Kafka由一个有序的日志构成,但NSQ不是。消息可以在任何时间以任何顺序进入队列。在我们使用的案例中,这通常没有关系,因为所有的数据都被加上了时间戳,但它并不适合需要严格顺序的情况。
2.9 无数据重复删除功能
NSQ对于超时系统,它使用了心跳检测机制去测试消费者是否存活还是死亡。很多原因会导致我们的consumer无法完成心跳检测,所以在consumer中必须有一个单独的步骤确保幂等性。
3. 实践安装过程
本文将nsq集群具体的安装过程略去,大家可以自行参考官网,比较简单。这部分介绍下笔者实验的拓扑,以及nsqadmin的相关信息。
3.1 拓扑结构
topology
实验采用3台NSQD服务,2台LOOKUPD服务。
采用官方推荐的拓扑,消息发布的服务和NSQD在一台主机。一共5台机器。
NSQ基本没有配置文件,配置通过命令行指定参数。
主要命令如下:
LOOKUPD命令
NSQD命令
工具类,消费后存储到本地文件。
发布一条消息
3.2 nsqadmin
对Streams的详细信息进行查看,包括NSQD节点,具体的channel,队列中的消息数,连接数等信息。
nsqadmin
channel
列出所有的NSQD节点:
nodes
消息的统计:
msgs
lookup主机的列表:
hosts
4. 总结
NSQ基本核心就是简单性,是一个简单的队列,这意味着它很容易进行故障推理和很容易发现bug。消费者可以自行处理故障事件而不会影响系统剩下的其余部分。
事实上,简单性是我们决定使用NSQ的首要因素,这方便与我们的许多其他软件一起维护,通过引入队列使我们得到了堪称完美的表现,通过队列甚至让我们增加了几个数量级的吞吐量。越来越多的consumer需要一套严格可靠性和顺序性保障,这已经超过了NSQ提供的简单功能。
结合我们的业务系统来看,对于我们所需要传输的发票消息,相对比较敏感,无法容忍某个nsqd宕机,或者磁盘无法使用的情况,该节点堆积的消息无法找回。这是我们没有选择该消息中间件的主要原因。简单性和可靠性似乎并不能完全满足。相比Kafka,ops肩负起更多负责的运营。另一方面,它拥有一个可复制的、有序的日志可以提供给我们更好的服务。但对于其他适合NSQ的consumer,它为我们服务的相当好,我们期待着继续巩固它的坚实的基础。