文档章节

Elixir IO内幕(一)读操作

A
 Aetherus
发布于 2017/03/28 01:45
字数 1177
阅读 333
收藏 4

玩过Elixir的人也许注意有注意到,File.open(path)返回的不是什么文件描述子(file descriptor,简称fd),也不是什么文件句柄(handle),而是{:ok, pid}

为毛是pid?难道说打开文件的同时打开了一个新的进程?没错。之所以这样做,官方给出的解释是

By modelling IO devices with processes, the Erlang VM allows different nodes in the same network to exchange file processes in order to read/write files in between nodes.

看不懂英文的可以看下面我的翻译(非直译):

把IO设备建模成进程,可以使Erlang虚拟机在同一个网络内的不同节点(主机)上交换文件进程,从而实现节点之间的相互读写。

但这样一来,给我们自己实现IO设备带来了不小的麻烦。当执行读操作的时候,文件进程接收到的消息是什么?需要回复给主进程什么样的消息?碰上EOF了怎么处理?其他异常呢?写操作的时候又是什么样的消息机制呢?所有的这些都没有文档可查(我事先声明我不会Erlang,所以请别让我去查Erlang文档。前阵子查了erlsom的文档,差点没让我哭出来)。

没文档怎么办?Elixir又不像Ruby那样可以让我猴子补丁一把。忽然想到有个模块叫StringIO,貌似听说是用Elixir写的,于是去GitHub上啃了一下它的源代码,啃完才知道什么叫醍醐灌顶。今天比较晚了,所以就先说说读操作吧。

当主进程需要读取IO的内容时,它会向文件进程发送一条以下几种消息之一

{:io_request, sender_pid, reference, {:get_chars, prompt, chunk_size}}
{:io_request, sender_pid, reference, {:get_chars, encoding, prompt, chunk_size}}
{:io_request, sender_pid, reference, {:get_line, prompt}}
{:io_request, sender_pid, reference, {:get_line, encoding, prompt}}
{:io_request, sender_pid, reference, {:get_until, prompt, mod, func, args}}
{:io_request, sender_pid, reference, {:get_until, encoding, prompt, mod, func, args}}
  • 第1种消息对应IO.binread(device, n),其中n为大于0的整数。
  • 第2种消息对应IO.read(device, n)IO.getn(device, n),IO设备实现者自己决定输出什么字符编码的字符。
  • 第3种消息对应IO.binread(device, :line)
  • 第4种消息对应IO.read(device, :line)IO.gets(device)
  • 最后两种暂时不知道对应什么。

接下来说说消息的参数。上面列出的消息中,

  • sender_pid 是消息发送方的pid。
  • reference 是对消息发送方的一个引用(因为消息发送方可能不是一个进程,而是一个Port神马的)。
  • encoding 是消息发送方期望的字符编码,是一个atom,默认:latin1
  • prompt 是给消息接收方(文件进程)的提示信息,通常没用,但是如果消息接收方是到的标准输入流,则可以在控制台上把这个信息打印出来,提示操作者(人)可以开始输入文本了。
  • chunk_size 是一次应该读取多少字节。你自己实现的文件进程可以无视它,但通常都会尊重它。

无论哪种消息,文件进程都应向消息发送方回复下列三种消息之一(至少我在StringIO的源代码里没发现第四种):

{:io_reply, reference, chunk}
{:io_reply, reference, :eof}
{:io_reply, reference, {:error, reason}}

第一种是在成功读到数据时的回复。其中reference是发送方发过来的那个引用(就是上面提到的那个),而chunk就是获取到的数据片段。

第二种是在没有读到数据,碰到文件结尾时的回复。

第三种当然是读取出错时的回复了。其中reason可以是任何东西。

明白了这些,我们就可以实现自己的IO设备了(当然使用GenServer啦,除非你想自虐)。比如,Phoenix框架的基础Plug.Conn并没有实现IO的接口(也就是不能用IO.read这样的方法来读取HTTP请求内容),于是我们就可以给Conn来个包装,包装成IO的样子(仅对应IO.binread(device, n)):

defmodule ConnIOWrapper do
  use GenServer

  def wrap(conn) do
    GenServer.start_link(__MODULE__, %{conn: conn, done: false})
  end

  def init(state) do
    {:ok, state}
  end

  def handle_info({:io_request, from, reply_as, {:get_chars, _, chunk_size}}, %{conn: conn, done: false} = state) do
    state = case Plug.Conn.read_body(conn, length: chunk_size) do
      {status, data, conn} when status in [:ok, :more] ->
        send(from, {:io_reply, reply_as, to_string(data)})
        %{conn: conn, done: status == :ok}
      {:error, _} = reply ->
        send(from, {:io_reply, reply_as, reply})
        %{state | done: true}
    end
    {:noreply, state}
  end

  def handle_info({:io_request, from, reply_as, {:get_chars, _, _}}, %{conn: conn, done: true} = state) do
    send(from, {:io_reply, reply_as, :eof})
    GenServer.cast(self, :close)
    {:noreply, state}
  end

  def handle_cast(:close, state) do
    {:stop, :normal, state}
  end
end

好吧这期就写到这儿。欲知后事如何,且听下回分解。(话说这破Markdown编辑器能不能用monospace字体啊?害得我对缩进对了半天,还没有Stack Overflow上那种Ctrl + K)

© 著作权归作者所有

A
粉丝 0
博文 9
码字总数 7345
作品 0
私信 提问
Elixir: 编程语言的未来

这篇文章谈一谈最近火爆的 Elixir,同时说一下对编程语言选择的看法。同时作为 Erlang 发烧友,Elixir 不可不提。即使有了那么多编程语言 Elixir 也值得接触。 现在开始接触 Elixir 对编程语...

oschina
2015/08/02
6.8K
18
(译)循序渐进学习Elixir

——Chris Bell Learning Elixir at Made by Many 在今年奥兰多的Elixir大会上演讲之后,就经常有人问我:你们团队是如何学习Elixir和OTP的? 和学习其他语言一样,学习Elixir也需要付出时间...

ljzn
2016/09/29
278
0
【ELIXIR】for语句的N种用法

Elixir中的for语句有许多用法: 一般用法 [2, 4, 6, 8] 多个元素池 [3,4,6,8] 附带筛选 [2, 4] 在<-符号的左边可以加上模式匹配和筛选 ["JHON", "TOM"] 处理bitstring的时候,双箭头放在最外...

ljzn
2016/09/17
60
0
elixir官方入门教程 介绍

介绍 安装 交互模式 运行脚本 提出疑问 欢迎! 在本教程中我们将教给你Elixir的基础,语法,如何定义模块,如何操作常用数据结构的特性等等.本章将确保Elixir安装好了,并且你能够成功运行Elixir的...

ljzn
2016/08/06
118
0
elixir官方入门教程 模块

模块 编译 脚本模式 具名函数 函数捕获 默认参数 在Elixir中我们将一些函数集合到模块里。在之前的章节里我们已经使用了许多不同的模块,例如模块: 为了创造我们自己的模块,需要用到宏。我...

ljzn
2016/08/03
96
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Cloud 笔记之Spring cloud config client

观察者模式它的数据的变化是被动的。 观察者模式在java中的实现: package com.hxq.springcloud.springcloudconfigclient;import org.springframework.context.ApplicationListener;i...

xiaoxiao_go
昨天
4
0
CentOS7.6中安装使用fcitx框架

内容目录 一、为什么要使用fcitx?二、安装fcitx框架三、安装搜狗输入法 一、为什么要使用fcitx? Gnome3桌面自带的输入法框架为ibus,而在使用ibus时会时不时出现卡顿无法输入的现象。 搜狗和...

技术训练营
昨天
4
0
《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
昨天
7
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
昨天
7
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部