1.Linux系统编程 每周一深入 (二)高级文件IO
2.SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP消息play
3.TPRC-cpp 发送包流程剖析
Linux系统编程 每周一深入 (二)高级文件IO
在Linux系统中,源码一切操作都可以抽象为文件读写。源码因此,源码本系列文章的源码第二部分将深入探讨Linux中的文件IO。
常规的源码文件IO涉及的系统调用包括:open、read、源码目录自动收录源码write、源码close,源码分别对应打开、源码读取、源码写入和关闭文件。源码在执行读写操作时,源码内核会维护一个指向当前文件偏移量的源码指针。为了灵活控制偏移量,源码系统调用lseek提供了定位文件位置的源码能力。glibc提供的fopen、fread、熊崩搜索源码fwrite、close和fseek(ftell)等函数,则是上述系统调用的封装,其中包含缓存机制以提高读写效率。
通常,上述函数足以应对大部分应用场景。今天,我们将重点介绍几个更高级的系统调用:pread、pwrite、readv、writev、preadv和pwritev。它们的功能可以用基本读写函数实现,但提供更便捷的特性,可能在某些场景下成为提高效率的利器。
以多线程下载程序为例,家谱APP源码下载我们可以通过记录每个线程负责的文件部分位置和已写入字节数,实现数据合并。但这种方法可能因加锁和频繁的lseek操作而成为下载速度的瓶颈。为了避免这些问题,可以使用pread和pwrite系统调用,它们不会改变文件偏移量,从而简化程序逻辑。
在分散读和集中写的场景中,writev系统调用可以将分散数据集中在一次系统调用中发送。与之对应的readv系统调用则完成相反的操作,从文件中读取数据并填充到指定位置。nginx源码中就包含分散度和集中写的例子。
此外,Linux还提供了preadv和pwritev函数,支持多线程的分散读和集中写。这些函数结合了pread、源码修改Sam机架pwrite、readv和writev的功能,在特定应用场景下可以提高效率。
总结:Linux的文件IO功能丰富,除了基础操作外,还针对多线程和集中/分散读写提供了系列函数。掌握这些函数的用途和使用方法,将有助于解决特定应用场景下的效率问题。
SRS(simple-rtmp-server)流媒体服务器源码分析--RTMP消息play
本章内容梳理了SRS在接收到RTMP信息后如何进行转发的过程。在此过程中,首先进行代码梳理,作者也在源码熟悉阶段,可能尚未完全梳理完接受到RTMP后信息如何处理、缓存以及转发给直播用户等内容。
SRS源码中的Play流程如下:
1. 进入play流程:本章内容直接从SrsRtmpConn::stream_service_cycle()方法开始梳理。
2. 在接受流程中,小白站api源码客户类型为SrsRtmpConnFMLEPublish “fmle publish”,而在转发流程中,客户类型为SrsRtmpConnPlay。
3. 在http_hooks_on_play()方法中,回调on_play()方法通知vhost,xxx用户已经开始play。
4. 在http_hooks_on_stop()方法中,回调on_stop()方法通知vhost,xxx用户已经停止play。
5. 最重要的是进入该函数。
在函数中:
1.1 根据客户端创建消费者对象:create_consumer(this, consumer)
1.2 为该消费者开启一个独立协程:trd.start() //此处一直不太明白,在play流程中创建一个协程用来做什么?
1.3 进入play主流程:do_playing(source, consumer, &trd);
2. 进入主play循环:do_playing()函数内容众多且非常重要,因此将函数内容全部列出。
2.1 通知消费者准备play
2.2 从消费者列表中取出Rtmp信息(SrsMessageQueue)
2.3 进入play入口
3. 进入SRS发送接口(play):在int SrsProtocol::send_and_free_messages(SrsSharedPtrMessage** msgs, int nb_msgs, int stream_id)函数中,进入int SrsProtocol::do_send_messages(SrsSharedPtrMessage** msgs, int nb_msgs),该函数有一个#ifdef SRS_PERF_COMPLEX_SEND宏定义,一般rtmp协议都是要混合音视频数据,在做转发。在往后面看,
最后进入
在该函数中,最重要的一点是send message总出口writen()函数。它负责将转发给直播用户的流转发出去。
4. 最后:play总结
(1)通知client开始play
(2)从消费者列表中取出Rtmp数据
(3)从总出口writev()函数中转发出去
TPRC-cpp 发送包流程剖析
TRPC官网对客户端发送包的流程有简要描述,但细节不够清晰。以下将通过分析Hello World示例中的源代码,来详细解析发送流程,不做深入分析,主要目的是梳理流程。
一、官网描述
二、源代码追踪
客户端代码位于TRPC-CPP/examples/helloworld/test/ fiber_client.cc文件中,核心代码如下:
1、解析文件获取配置信息
对应函数为ParseClientConfig,配置信息主要分为三种:
2、RunInTrpcRuntime
主要完成的是日志初始化以及执行FiberRuntime程序,对应代码如下:
3、RunInFiberRuntime
主要做的工作就是初始化框架运行环境InitFrameworkRuntime以及开启协程进行插件初始化以及传入的Run函数执行。
InitFrameworkRuntime主要代码如下,主要完成的工作是设定运行环境类型(使用Fiber)、内存池创建、时间轮创建并开启、初始化Fiber环境(fiber调度组的初始化并启动协程模型)、配置调度组对应的Reactor。
RegisterPlugins主要代码如下,主要完成的就是Naming、Tracing、Loging、Codec等等插件的注册、初始化以及开启运行。
4、请求构建与发送
status = func();调用main函数中传入的Run函数,请求构建与发送,代码如下:
核心在于调用proxy->SayHello进行请求的发送,那么我们就再深入一层
原来是调用了UnaryInvoke函数,那么continue
这部分代码是在ServiceProxy中,也就对应的官网的流程,这部分主要将请求写入上下文对象中,并且运行过滤器(这里其实就是文档中所说的过滤器埋点,究其原理其实就是运行已注册再当前埋点的函数),然后调用了UnaryInvokeImp,继续追踪~~~
这部分代码将所使用的协议写入了上下文对象,然后又调用了ServiceProxy::UnaryInvoke,Come on !
同样此函数首先执行了过滤器(埋点是CLIENT_PRE_SEND_MSG),然后又调用了UnaryTransportInvoke,在已知传输数据、传输协议的情况下,进行下一步^_^
使用之前已经注册的codec编码器对请求内容进行编码与请求头封装,进入codec_->ZeroCopyEncode,编码完成后使用transport对象进行发送与接收transport_->SendRecv
通过目标IP地址以及端口寻找到对应的FiberConnectorGroup组,通过调用组的SendRecv,进行发送
获取connection对象(可深入追踪,分为短链接和长连接连接池复用),获取成功调用SendReqMsg进行发送
首先进行了用户过滤器判定,有的话进行用户过滤器调用,后面进行IoMessage信息封装,调用Send进行信息发送
状态判定居多,核心在于FlushWritingBuffer函数
同样进行了处理,核心在于FlushTo
其他复杂的处理暂时不关注,这里发送的核心函数是io->Writev
到此,调用系统调用将信息写入Fd,即发出完成。
整个过程确实比较长,一层有一层的嵌套封装,进而实现解耦,这里其实并不仅仅是发送,也有接收,最开始的UnaryInvoke>(context, request, response);函数已经将response以指针的形式传递进取,后续发送数据并收到对方发来的数据是,进行层层赋值,最终得到了我们接收到的返回信息。
发送信息层层函数递进,接收信息层层函数退出。
大概就是这样,下面去看下tcp连接池的设计~~~~。