Q語言——I/O操作

前言

Q的I/O操作是非常簡潔和強(qiáng)大的,每個I/O操作的函數(shù)的信息熵都很高。通過句柄的操作方式,我們可以把硬盤中的數(shù)據(jù)導(dǎo)入到內(nèi)存中,或者把內(nèi)存中的數(shù)據(jù)存儲到硬盤里。同樣我們也要養(yǎng)成用時打開句柄,用完之后關(guān)閉的習(xí)慣。

一、二進(jìn)制數(shù)據(jù)

在Q中,文件的存儲和導(dǎo)入我們可以分為兩類:文本和二進(jìn)制數(shù)據(jù)。處理文本數(shù)據(jù),我們會用到參數(shù)“0”,二進(jìn)制數(shù)據(jù)我們會用到參數(shù)“1”。文本文件我們可以理解為是一個字符串的列表,而二進(jìn)制文件我們可以理解是一個二進(jìn)制字節(jié)的列表。雖然所有文本文件我們可以作為二進(jìn)制數(shù)據(jù)來處理,但是不是所有二進(jìn)制數(shù)據(jù)文件我們可以用文本數(shù)據(jù)的處理方式來處理。

1. 文件句柄

文件句柄:存儲在硬盤中的數(shù)據(jù)的一個路徑+名稱。具體如下:

`:[ 文件路徑 ] 文件名稱

通??赡芪覀冊谑褂寐窂胶兔Q的時候我們使用字符串更容易,以便可以輕松處理空白和其他特殊字符。因此Q提供了內(nèi)置函數(shù)hsym,我們可以通過hsym將字符串的句柄轉(zhuǎn)換為標(biāo)準(zhǔn)的Q可以識別的標(biāo)準(zhǔn)讀寫句柄。

q)hsym `$"/data/file name.csv" /使用hsym轉(zhuǎn)換將字符串的句柄轉(zhuǎn)換為標(biāo)準(zhǔn)的句柄方式
`:/data/file name.csv

2. 文件大小的查看(hcount)與文件的刪除(hdel)

我們可以通過hcount來查看要導(dǎo)入的一個文件的大小,同時我們要刪除某個文件的時候我們可以通過hdel來刪除。這里一定要謹(jǐn)慎使用hdel,因為它是直接就永久刪除了(回收站也沒有),刪除之前一定要確認(rèn)是否可以刪除。

對于文件的讀寫,我們還有一個重要的事情,就是先確認(rèn)一下我們Q語言運行的環(huán)境目錄,我們可以通過\cd命令來查看我們當(dāng)前的環(huán)境目錄,一般情況下的環(huán)境目錄是在你的安裝目錄,這里我修改了q.q的文件,初始化的環(huán)境目錄就是我的E盤。同時我們也可以使用\cd命令進(jìn)入到我們想要的目錄中。

q)\cd /查看當(dāng)前的環(huán)境目錄
"E:\\"
q)\cd c: /進(jìn)入我們想要的環(huán)境目錄
q)\cd
"C:\\"
q)\cd c:/q /進(jìn)入我們想要的環(huán)境目錄
q)\cd
"c:\\q"

下面我們就開始嘗試使用hcount與hdel命令。這里我創(chuàng)建了一個test.txt的文件,大小為29K。并且我在桌面和我的E盤的data文件夾下分別保存了一份。

q)hcount `E:/data/test.txt /如果我們要查看的文件不在當(dāng)前環(huán)境目錄下的查看方式
29575 /返回文件大小,單位為字節(jié)
q)hcount `C:/Users/caoxi/Desktop/test.txt /如果我們要查看的文件不在當(dāng)前環(huán)境目錄下的查看方式
29575
q)hcount `:test.txt /所要查看的文件就在當(dāng)前環(huán)境目錄下
29575
q)hdel `C:/Users/caoxi/Desktop/test.txt /但是我發(fā)現(xiàn)hdel刪除命令就不能
刪除不在當(dāng)前環(huán)境目錄下的文件,刪除不在當(dāng)前環(huán)境目錄下的文件就會報錯
'nyi
q)hdel `:test.txt /成功刪除當(dāng)前環(huán)境目錄下的文件,返回刪除的文件名稱,但是
一定要確認(rèn)有備份?。?!這種刪除是直接永久刪除,在回收站中都不存在的
`:test.txt

3. 文件的序列化(寫入到本地)和反序列化(讀入到內(nèi)存)

每個Q實體都可以序列化并持久存儲。而且非常簡單方便,不像傳統(tǒng)文件那樣,文件的讀和寫比較復(fù)雜,通常我們使用set命令將實體序列化到本地,使用get命令將本地文件導(dǎo)入到內(nèi)存中。具體讀寫方式如下:

:[ 文件路徑 ]/實體名稱 set 文件名稱 get:[ 文件路徑 ]/實體名稱

下面我們看一些具體案例

q)\cd /首先可以查看一下我們所在的環(huán)境目錄
"E:\\"
q)`:/data/variable_a set 2019 /序列化一個變量到對應(yīng)的路徑下,
如果對應(yīng)的路徑下文件夾不存在,則會自動生成一個對應(yīng)的文件夾
`:/data/variable_a /返回對應(yīng)的路徑+序列化實體對象表示成功
q)`:/data/list_L set 2019 2020 2021 /序列化一個列表到對應(yīng)的路徑下
`:/data/list_L
q)`:/data/table_T set ([] c1:`a`b`c; c2:10 20 30) /序列化一個表到對應(yīng)的路徑下
`:/data/table_T

這個時候我們可以打開我們序列化使用的路徑的文件夾下,我們可以看到所序列化的對應(yīng)的二進(jìn)制文件。

序列化后的二進(jìn)制文件

我們可以通過get命令將序列化的文件來反序列化(就是讀入到內(nèi)存)到我們的內(nèi)存中。

q)get `:/data/variable_a /反序列化一個變量,返回變量的具體內(nèi)容
2019 
q)get `:/data/list_L /反序列化一個列表,返回列表的具體內(nèi)容
2019 2020 2021
q)get `:/data/table_T /反序列化一個表,返回表的具體內(nèi)容
c1 c2
-----
a 10
b 20
c 30
q)value `:/data/table_T /當(dāng)然我們也可以使用value命令來進(jìn)行反序列化
c1 c2
-----
a 10
b 20
c 30

但我們使用get或者value命令將本地的文件反序列話到我們的內(nèi)存中,那是否內(nèi)存中就有了這一個變量呢?其實不是的,我們可以理解為只是將本地文件的內(nèi)容反序列化后在我們內(nèi)存中進(jìn)行顯示,但是并沒有保存到我們的內(nèi)存當(dāng)中。如果我們要將本地的文件導(dǎo)入到內(nèi)存中并保存,那我們要使用\l命令。前面我們也曾經(jīng)提到過\l命令的使用。具體操作方式如下:

\l /[ 路徑 ]/實體名稱

q)table_T  /上面我們通過get命令和value命令將本地文件反序列化后,
我們直接查看對應(yīng)的實體內(nèi)容,發(fā)現(xiàn)內(nèi)存中并沒有,然后報錯。 
q)\l /data/table_T /這時我們就要通過\l命令來將本地文件導(dǎo)入到內(nèi)存中,
這時對應(yīng)的實體才保存到了我們的內(nèi)存中
`table_T /導(dǎo)入成功返回對應(yīng)的實體名稱
q)table_T /查看實體內(nèi)容
c1 c2
-----
a 10
b 20
c 30

4. 二進(jìn)制數(shù)據(jù)文件

我們知道,對于一個文件,我們可能會在后面繼續(xù)追加文件內(nèi)容的操作,用set序列化后我們?nèi)绾卧谛蛄谢蟮奈募欣^續(xù)添加內(nèi)容呢。這就與傳統(tǒng)編程語言就非常類似了,我們需要先打開一個句柄(對應(yīng)的文件路徑),然后寫入內(nèi)容,最后關(guān)閉文件句柄。通常我們使用hopen命令和hclose命令來打開和關(guān)閉句柄。

q)`:/data/L set 10 20 30 /序列化一個列表
`:/data/L
q)h:hopen `:/data/L /使用hopen打開
q)h 
876i /返回的一個編號(個人覺得這個編號是一個對應(yīng)的通道編號)
q)h[42] /添加42這條數(shù)據(jù)
876i
q)h[1] /添加1這條數(shù)據(jù)
876i
q)h 100 200 300 /同時添加一個列表
876i
q)hclose h /關(guān)閉通道
q)get `:/data/L /可以看到添加數(shù)據(jù)成功
10 20 30 42 1 100 200 300

5. 二進(jìn)制數(shù)據(jù)文件的讀與寫

通常我們使用read1將序列化的文件以二進(jìn)制的方式讀入到內(nèi)存中。

q)read1 `:/data/table_T
0xff016200630b00020000006331006332000000020000000b000300000061006200630007000..
q)read1 `:/data/list_L
0xfe200700000000000300000000000000e307000000000000e407000000000000e5070000000..
q)read1 `:/data/variable_a
0xff01f9e307000000000000

當(dāng)然我們也可以直接將二進(jìn)制數(shù)據(jù)序列化到我們的本地

q)`:/data/answer.bin 1: 0x06072a /將二進(jìn)制文件序列化到本地
`:/data/answer.bin
q)read1 `:/data/answer.bin /從本地直接將二進(jìn)制文件讀入到內(nèi)存中
0x06072a

6. :號的使用

我們有另外一種方式來序列化數(shù)據(jù),非常類似于前面我們介紹的函數(shù)形式,具體如下:

.[ 路徑;(); :;數(shù)據(jù)] /第一次序列化
.[ 路徑;(); ,;數(shù)據(jù)] /對已經(jīng)存在的文件中附加數(shù)據(jù)

q).[`:/data/raw; (); :; 10 20 30] /注意[ ]前面有個點號“.”
`:/data/raw
q)get `:/data/raw
10 20 30
q).[`:/data/raw; (); ,; 100 200 300]
`:/data/raw
q)get `:/data/raw
10 20 30 100 200 300

二、表的保存與載入

前面我們已經(jīng)介紹過表的序列化(保存)與反序列化(載入),對于序列化好的文件,我們要使用的時候,一定要使用load或者\l命令將文件載入到內(nèi)存中才可以使用,get命令只是將序列化好的文件內(nèi)讀取到內(nèi)存中,兩者是有區(qū)別的。其中l(wèi)oad命令和\l命令在路徑參數(shù)上有一些差別,具體如下:

q)load `C:/Users/caoxi/Desktop/t  /load的路徑參數(shù)前面有一個`符號
`t
q)\l C:/Users/caoxi/Desktop/t1
`t1
q)t
c1 c2 c3
---------
a  10 1.1
b 20 2.2
c 30 3.3
q)t1
c1 c2 c3
---------
a 10 1.1
b 20 2.2
c 30 3.3

我們還可以將表以txt格式、csv格式、xml格式或者xls格式序列化到本地磁盤上。

q)save `:/data/t.txt
`:/data/t.txt
q)save `:/data/t.csv
`:/data/t.csv
q)save `:/data/t.xml
`:/data/t.xml
q)save `:/data/t.xls
`:/data/t.xls

但是這四種文件只能通過read0的形式讀取到我們的內(nèi)存中,使用get、load或者\l命令都無法讀取這些文件。

q)read0 `:/data/t.txt
"c1\tc2\tc3"
"a\t10\t1.1"
"b\t20\t2.2"
"c\t30\t3.3"
q)read0 `:/data/t.csv
"c1,c2,c3"
"a,10,1.1"
"b,20,2.2"
"c,30,3.3"
q)read0 `:/data/t.xml
"<R>"
"<r><c1>a</c1><c2>10</c2><c3>1.1</c3></r>"
"<r><c1>b</c1><c2>20</c2><c3>2.2</c3></r>"
"<r><c1>c</c1><c2>30</c2><c3>3.3</c3></r>"
"</R>"
q)read0 `:/data/t.xls
"<?xml version=\"1.0\"?><?mso-application progid=\"Excel.Sheet\"?>"
"<Workbook xmlns=\"urn:schemas-microsoft-com:office:spreadsheet\" xmlns:ss=\"..

幾種文件的具體內(nèi)容如下:

xml文件內(nèi)容

txt文件內(nèi)容

csv文件內(nèi)容

xls文件內(nèi)容

磁盤中的文件

三、擴(kuò)展表

有時候可能會這種情況,那就是我們一張表有很多個字段,但是我們有時候可能只會臨時使用到其中的一個或者兩個字段,然后我們這張表可能還有個好幾百萬行,那我們將這種表導(dǎo)入到內(nèi)存中就會造成很大的內(nèi)存浪費,畢竟內(nèi)存是非常寶貴的,這種情況下Q就采取了一種非常高效的方式,那就是按照字段存儲。

對一個大型表不適合直接載入到內(nèi)存中的表,通過Q序列化每個字段到指定路徑下,對表的這種操作我們稱為擴(kuò)展表(splayed table)。

比如我們有這樣一個表t

t:([] c1:10 20 30; c2:1.1 2.2 3.3; c3: 2001 2002 2003)

我們就可以分為三個字段序列化到我們的本地磁盤上。具體操作如下:

`:[ 路徑 ]/table_name/ set table_name

注意這里的/是必須的,這也是表普通序列化和擴(kuò)展序列化的區(qū)別。

這里我們對表t進(jìn)行擴(kuò)展序列化:

`:/data/tsplay/ set t

這時我們看到我們的data文件夾下多了一個tsplay的文件夾,而不是一個文件,在tsplay文件夾下有4個文件,一個是“.d”的文件,該文件包含了字段信息,其他的文件就是序列化的字段數(shù)據(jù)。

擴(kuò)展表的序列化文件

這樣我們在導(dǎo)入的時候就可以直接導(dǎo)入某一個字段了,我們可以直接查看.d文件來看這個表的字段信息。

q)get `:/data/tsplay/.d /查看表tsplay的字段信息
`c1`c2`c3
q)get `:/data/tsplay/c2 /查看表tsplay的c2字段的內(nèi)容
1.1 2.2 3.3
q)c2 /這時我們還沒有導(dǎo)入c2字段的內(nèi)容,因此會報錯
'c2
q)load `:/data/tsplay/c2 /導(dǎo)入表tsplay的c2字段的內(nèi)容
`c2
q)c2
1.1 2.2 3.3

Q語言對表的擴(kuò)展也有一定的限制:

1) 所有的字段必須是簡單列表或者符合列表
2) 如果一個字段全是symbol類型的話就必須使用枚舉

q)`:/data/tsplay1/ set ([] c1:2000.01.01+til 3; c2:1 2 3) 
`:/data/tsplay1/
q)`:/data/tsplay2/ set ([] c1:1 2 3; c2:(1.1 2.2; enlist 3.3; 4.4 5.5))
`:/data/tsplay2/
q)`:/data/tsplay3/ set ([] c1:1 2 3; c2:(1;`1;"a"))
`:/data/tsplay3/
q)`:/data/tsplay4/ set ([] c1:`a`b`c; c2:10 20 30) /這時c1字段就是全symbol類型的數(shù)據(jù),
進(jìn)行擴(kuò)展序列化時就失敗了
'type
q)`:/db/tsplay4/ set ([] `sym?c1:`a`b`c; c2:10 20 30) /對于包含全symbol類型的字段,
我們就需要對這些特殊字段枚舉處理后擴(kuò)展序列化
`:/db/tsplay4/
Q官方也給了一個使用的工具,那就是 .Q.en[`路徑; table_name]來進(jìn)行擴(kuò)展序列化,
這時對于字段數(shù)據(jù)類型就沒有限制了。
q)`:/db/tsplay5/ set .Q.en[`:/db; ([] c1:`a`b`c; c2:10 20 30)]
`:/db/tsplay5/

四、文本數(shù)據(jù)處理

前面我們介紹的都是二進(jìn)制數(shù)據(jù)文件的處理,現(xiàn)在我們介紹一下文本文件的數(shù)據(jù)處理。通常一個文本文件會被認(rèn)為是char類型的列表,也就是字符串。因此讀取文本文件會生成一個字符串列表,同時也可以將一個字符串列表寫入到文本文件中。

1. 文本文件的讀寫

前面我們二進(jìn)制文件通常使用的參數(shù)是1:,對于文本文件,通常我們使用的參數(shù)都是0:。

下面我們在data文件夾下創(chuàng)建一個文本文件test.txt。我們使用read0就可以將文本文件讀入到我們的內(nèi)存中。

q)read0 `:/data/test.txt
"So long"
"and thanks"
"for all the fish"

我們可以看到read0會將文本文件的每一行轉(zhuǎn)換為一個字串符的列表。當(dāng)然我們也可以使用read1讀取文本文件,只不過返回的結(jié)果是一個二進(jìn)制的數(shù)據(jù)。

q)read1 `:/data/test.txt
0x536f206c6f6e670d0a616e64207468616e6b730d0a666f7220616c6c207468652066697368
q)"x"$ read0 `:/data/test.txt /上面的可能看起來比較混亂,我們可以把read0讀取的
文件通過類型轉(zhuǎn)換的形式轉(zhuǎn)換成二進(jìn)制數(shù)據(jù)
0x536f206c6f6e67
0x616e64207468616e6b73
0x666f7220616c6c207468652066697368
q)"c"$ read1 `:/data/test.txt /我們也可以將read1讀取的二進(jìn)制文件轉(zhuǎn)換成字符轉(zhuǎn)的類型
"So long\r\nand thanks\r\nfor all the fish"

下面就是介紹如何將數(shù)據(jù)序列化到文本文件,與方式一樣,只是將set換成了0:參數(shù)。

q)`:/data/test1.txt 0: ("Life"; "The Universe"; "And Everything") /序列化到文本文件
`:/data/test1.txt
q)read0 `:/data/test1.txt
"Life"
"The Universe"
"And Everything"
序列化文本文件的內(nèi)容

2. hopen與hclose的使用

跟前面介紹的hopen和hclose操作方式相似。只是對于文本文件我們要用到neg函數(shù)來寫入數(shù)據(jù)到文本文件中,而不是像前面一樣可以直接寫入。

q)h:hopen `:/data/new.txt
q)neg[h] enlist "this" /通過neg[h]來寫入數(shù)據(jù)到文本文件
-960i
q)neg[h] ("and"; "that") /每個字符串就會占一行
-960i
q)hclose h /關(guān)閉通道的時候直接用h,不是neg[h]
q)read0 `:/data/new.txt /讀取文本文件
"this"
"and"
"that"
q)get `:/data/new.txt /這時我們發(fā)現(xiàn)使用get已經(jīng)不能得到文本文件內(nèi)容了,
開始報錯了,只能用read0的形式
'/data/new.txt
q)h:hopen `:/data/new.txt
q)neg[h] ("and"; "more")
-1012i
q)hclose h
q)read0 `:/data/new.txt /讀取文本文件內(nèi)容
"this"
"and"
"that"
"and"
"more"

五、數(shù)據(jù)解析

我們也可以將文本類文件或者二進(jìn)制文件通過特定的方式解析為特定的字段,和前面介紹的一樣,文本文件我們用參數(shù)0:,二進(jìn)制文件我們用參數(shù)1:。具體在解析為什么數(shù)據(jù)類型的時候我們可以參考下面的圖。

數(shù)據(jù)解析類型參考

這里0這列是在解析文本類文件的時候選擇的字段類型簡寫(注意是大寫);1這列是解析二進(jìn)制文件的時候選擇的字段類型簡寫(注意是小寫);type這列表示對應(yīng)的數(shù)據(jù)類型;width表示字節(jié)寬度;后面的format列表示可以接受的格式類型。

1. 固定寬度解析

對于固定寬度解析,就是我們按照預(yù)先給定的一個寬度來裁剪一樣,然后將每段內(nèi)容放到相應(yīng)的字段。具體模式如下:

(“指定字段類型”;指定每個字段類型的寬度) 0:read0 :[文件路徑] (“指定字段類型”;指定每個字段類型的寬度) 1:read1:[文件路徑]

我們的所有指定寬度的總和要小于等于我們文本文件中的每行的字?jǐn)?shù)。

現(xiàn)在假設(shè)我們有一個如下記錄的文本文件(注意這里1001和98.000之間有兩個空格):

1001 98.000ABCDEF1234Garbage2015.01.01
1002 42.001GHUJKL0123Garbage2015.01.02
1003 44.123nopqrs9876Garbage2015.01.03

然后我要解析成下面的形式(其中我們省略了一部分信息):

c1 c2 c3 c4
---------------------------------
1001 98 ABCDEF1234 2015.01.01
1002 42.001 GHUJKL0123 2015.01.02
1003 44.123 nopqrs9876 2015.01.03

那我們首先就可以通過固定寬度解析,再通過翻轉(zhuǎn)得到(對于要省略的信息,在給定指定解析字段類型的時候我們使用空格,比如這里("JFS D";4 8 10 7 10),在S的10個寬度解析完之后就會跳過7個空格開始解析D的10個寬度):

q)("JFS D";4 8 10 7 10) 0: `:/data/text.txt 
1001 1002 1003
98 42.001 44.123
ABCDEF1234 GHUJKL0123 nopqrs9876
2015.01.01 2015.01.02 2015.01.03

這里的4+8+10+7+10=39,寬度要和text.txt中的每一行寬度是一樣的,這就是定寬度解析。我們發(fā)現(xiàn)解析出來的結(jié)果是列向的,不是橫向的,因此我們要反轉(zhuǎn)一下就得到了下面的結(jié)果形式,同時我們可以給每個字段添加一個名稱。

q)flip `c1`c2`c3`c4!("JFS D";4 8 10 7 10) 0: `:/data/text.txt
c1 c2 c3 c4
---------------------------------
1001 98 ABCDEF1234 2015.01.01
1002 42.001 GHUJKL0123 2015.01.02
1003 44.123 nopqrs9876 2015.01.03

2. 變長記錄解析

有時候?qū)τ诿恳恍械拈L度可能不清楚,同時每個字段數(shù)據(jù)也不一樣,那我們就不能使用固定寬度來解析了,我們得使用邊長記錄來解析,或者說是分隔標(biāo)識來解析,解析模式如下:

(“指定字段類型”;“分隔標(biāo)識符”) 0: :[文件路徑] (“指定字段類型”;“分隔標(biāo)識符”) 1::[文件路徑]

假設(shè)我們有一個下面的csv文件記錄(我們知道csv文件基本上是以,號分隔的):

1001,DBT12345678,98.6
1002,EQT98765432,24.75
1004,CCR00000001,121.23

最終我們可以解析到如下形式:

c1 c2 c3
-----------------------
1001 DBT12345678 98.6
1002 EQT98765432 24.75
1004 CCR00000001 121.23

跟上面固定寬度解析流程一樣,我們先通過標(biāo)識符分隔后,然后再翻轉(zhuǎn)。

q)("JSF"; ",") 0: `:/data/simple.csv
1001 1002 1004
DBT12345678 EQT98765432 CCR00000001
98.6 24.75 121.23
q)flip `c1`c2`c3!("JSF"; ",") 0: `:/data/simple.csv
c1 c2 c3
-----------------------
1001 DBT12345678 98.6
1002 EQT98765432 24.75
1004 CCR00000001 121.23
q)flip `c1`c2`c3!("J*F"; ",") 0: `:/data/simple.csv
c1 c2 c3
-------------------------
1001 "DBT12345678" 98.6
1002 "EQT98765432" 24.75
1004 "CCR00000001" 121.23

對于我們有些有字段名稱的csv文件,我們在導(dǎo)入解析的時候就可以直接解析成表的形式。如我們有下面內(nèi)容的文件(titles.csv):

id,ticker,price
1001,DBT12345678,98.6
1002,EQT98765432,24.7
1004,CCR00000001,121.23

第一行為字段名稱,這時我們就可以解析成Q的表。

id ticker price
-----------------------
1001 DBT12345678 98.6
1002 EQT98765432 24.7
1004 CCR00000001 121.23

具體操作如下:

q)("JSF"; enlist ",") 0: `:/data/titles.csv
id ticker price
-----------------------
1001 DBT12345678 98.6
1002 EQT98765432 24.7
1004 CCR00000001 121.23

3. 類正則表達(dá)式

下面的操作就非常類似我們Python里面的正則表達(dá)式了。大家可以慢慢領(lǐng)會一下。

q)"S=;" 0: "one=1;two=2;three=3"
one two three
,"1" ,"2" ,"3"
q)"S:/" 0: "one:1/two:2/three:3"
one two three
,"1" ,"2" ,"3"
q)"I=;" 0: "1=one;2=two;3=three"
1 2 3
"one" "two" "three"
q)flip `k`v!"I=;" 0: "1=one;2=two;3=three"
k v
---------
1 "one"
2 "two"
3 "three"

六、進(jìn)程通信

Q語言的進(jìn)程間的通信還是比較簡單的,啟動通信的一端稱為客戶端,而接收和處理請求的一端稱為服務(wù)器。只要可以訪問,服務(wù)器進(jìn)程可以位于同一臺計算機(jī),同一網(wǎng)絡(luò),不同網(wǎng)絡(luò)或Internet上。通信可以是同步的(等待返回結(jié)果)或異步(不等待,也不返回結(jié)果)。

這里我們使用同一臺機(jī)器來測試進(jìn)程間的通信,我們首先需要打開一個會話,打開的同時我們需要打開端口,其命令如下:

Server端: q -p 5042(端口號) 或者如果已經(jīng)打開q會話,則輸入\p 5042
Client端: hopen `:[Server]:port(端口號)

1. 通信句柄

根據(jù)服務(wù)器端,大概有四種方式可以來訪問我們的服務(wù)器。通信句柄為:

`:[服務(wù)器]:端口號

1) 本地訪問

本地訪問我們常見的兩種方式為:

::5042或者:localhost:5042

這里的端口號可以根據(jù)自己的實際情況來設(shè)置。

2) 同一網(wǎng)絡(luò)訪問

同一網(wǎng)絡(luò)下我們還可以通過PC名稱來訪問,比如你還有一臺電腦的名稱叫zhagnsan,則可以通過如下方式訪問:

`:zhagnsan:5042

3) IP地址訪問

我們也可以通過服務(wù)器端的IP地址來訪問,其訪問形式相似:

`:192.168.0.1:5042

4) URL訪問

URL訪問方式比較獨特,自己可以測試一下:

`:http://www.myrrl.com:5042

下面我們進(jìn)行一個簡單的通信測試(為了方便,我就在同一臺機(jī)器上進(jìn)行通信測試)。首先我們需要打開一個Q會話作為服務(wù)器端,在cmd中,我們輸入q -p 5042。同時再打開一個普通的Q會話我們作為客戶端。在客戶端,我們通過hopen命令來與我們的服務(wù)端進(jìn)行通信。

簡單的進(jìn)程通信測試

圖中兩個Q會話窗口中,黑色背景的就是服務(wù)器端,白色背景的就是客戶端。作為服務(wù)器端的會話,打開的時候我們需要加-p 5042的參數(shù)。打開后我們在服務(wù)器端輸入一個a,發(fā)現(xiàn)沒有這個變量,因此報錯。這時我們在我們的客戶端通過hopen命令來與我們服務(wù)器端取得通信聯(lián)系。我們通過客戶端我們執(zhí)行一條語句a:6*7(這里需要用引號引起來,作為一個字符串的形式來發(fā)送處理,否則會報錯),然后會自動的發(fā)送到我們的服務(wù)器端,我們這時再到服務(wù)器端輸入一個a,你就會發(fā)現(xiàn)此時服務(wù)器端就有了a變量的信息。這就完成了一次簡單的進(jìn)程通信。

2. 遠(yuǎn)程執(zhí)行

上面我介紹的簡單的進(jìn)程通信方式是非常不安全的,因為執(zhí)行a:6*7是在我們的服務(wù)器端執(zhí)行,而不是在我們的客戶端執(zhí)行,那說明我們服務(wù)器端沒有對客戶端做一個限制,那很容易被攻擊的。

下面我們介紹一種遠(yuǎn)程的執(zhí)行方式,我們用一種列表的方式從我們的客戶端給我們的服務(wù)器端來發(fā)送請求。該列表形式為(function; arg1; arg2; ……),這里的function可以是客戶端的,也可以是服務(wù)器端的。

遠(yuǎn)程執(zhí)行

我們首先在我們的服務(wù)器端定義一個func的函數(shù),然后在我們的客戶端調(diào)用這個函數(shù)并傳遞相應(yīng)的參數(shù)(這里函數(shù)調(diào)用的時候要加symbol符號)。同時我還在我們的服務(wù)器端定義一個表t和定義一個表的查詢函數(shù)f,然后再到我們的客戶端通過調(diào)用查詢函數(shù)f來查詢服務(wù)器端表t的內(nèi)容。

3. 同步和異步消息

上面我們介紹的都是同步消息,同步消息就意味著在通信進(jìn)程打開的時候,當(dāng)客戶端發(fā)送信息到我們的服務(wù)端,客戶端進(jìn)程就會等待服務(wù)端返回相應(yīng)的結(jié)果。具體的流程如下:

同步執(zhí)行流程

當(dāng)客戶端在同步消息傳遞中向服務(wù)器發(fā)送多條消息時,在收到上一條消息的結(jié)果之前,不會發(fā)送下一條消息。因此,消息總是按照發(fā)送順序到達(dá)服務(wù)器。此外,服務(wù)器端的結(jié)果按原始消息的發(fā)送順序返回客戶端。

也可以在Q中執(zhí)行異步IPC。在這種情況下,消息被發(fā)送到服務(wù)端后客戶端可以立即繼續(xù)執(zhí)行下一條語句,不用等待客戶端的返回結(jié)果。特別是,服務(wù)端沒有返回值。當(dāng)不關(guān)心結(jié)果時,這對于在服務(wù)端上啟動任務(wù)很有用。

異步執(zhí)行

圖中案例:我們在服務(wù)端定義一個sq函數(shù)(這里的0N!xx表示顯示xx的執(zhí)行結(jié)果),然后我們在我們的客戶端使用neg函數(shù)來調(diào)用服務(wù)端的sq函數(shù),當(dāng)我們執(zhí)行

neg[h] (sq; 5)的時候我們發(fā)現(xiàn)服務(wù)端會立即執(zhí)行sq函數(shù),可能這看不出來異步通信,所以重新在客戶端定義一個函數(shù){neg[h] (sq; 8); 88},該函數(shù)中第一條語句為向服務(wù)端發(fā)送信息,第二條為在客戶端執(zhí)行的一條簡單語句,我們會發(fā)現(xiàn)客戶端執(zhí)行{neg[h] (`sq; 8); 88}[ ]該函數(shù)的時候,立即顯示了88的結(jié)果,沒有等待我們服務(wù)端返回的64的結(jié)果。

由于Q會話默認(rèn)是單線程的,因此服務(wù)器將按接收順序處理消息。但是,在異步消息傳遞中,無法保證消息按照發(fā)送順序到達(dá)服務(wù)器。

4. 消息處理

假設(shè)從服務(wù)器端向服務(wù)端調(diào)用客戶端函數(shù)或服務(wù)器端的函數(shù)名稱,則在服務(wù)端上執(zhí)行相應(yīng)的函數(shù)。在服務(wù)端執(zhí)行期間,客戶端進(jìn)程的通信句柄在系統(tǒng)變量.z.w(“who”被調(diào)用)中也是可用。

當(dāng)客戶端和服務(wù)端之間的連接打開時,它們都具有連接句柄,這些句柄是獨立分配的,它們的int值通常不同的。

下面我們通過一個簡單的案例來展示如何使用.z.w將消息發(fā)送到客戶端上。首先我們在服務(wù)端定義一個函數(shù)f:{show "Received ",string x; .z.w (mycallback; x+1)},該函數(shù)第一條語句為顯示收到的參數(shù),第二條語句為回調(diào)與服務(wù)端通信的客戶端函數(shù),并將從客戶端傳遞過來的參數(shù)+1后作為新的回調(diào)客戶端函數(shù)的參數(shù)。這里執(zhí)行異步消息傳遞時,始終使用.z.w`以確保所有消息都是異步的。否則,當(dāng)每個進(jìn)程等待另一個進(jìn)程時,將遇到死鎖。

.z.w系統(tǒng)變量測試

我們可以通過將自己的處理程序分配給適當(dāng)?shù)南到y(tǒng)變量來覆蓋Q中消息處理的默認(rèn)行為。如將自己的函數(shù)分配給變量.z.pg以捕獲和處理同步消息,將自己的函數(shù)分配給變量.z.ps以捕獲和處理異步消息。名稱以'g'和's'結(jié)尾,因為同步處理具有“get”語義,異步處理具有“set”語義。

.z.ps系統(tǒng)變量測試

圖中我們在服務(wù)端定義了兩個函數(shù),函數(shù)sq為一個普通函數(shù),我們對系統(tǒng)變量.z.ps進(jìn)行了重新定義(簡單的打印ignore到會話窗口中)。當(dāng)我們在客戶端向服務(wù)端發(fā)送信息的時候,我們的服務(wù)端立即執(zhí)行了系統(tǒng)變量.z.ps,將ignore打印出來。

現(xiàn)在我們將同步處理程序設(shè)置為只接受按函數(shù)名稱“安全”遠(yuǎn)程調(diào)用。然后通過對傳遞的參數(shù)進(jìn)行判斷來保護(hù)客戶端的執(zhí)行,從而確保失敗的應(yīng)用程序不會掛起服務(wù)器。

.z.pg系統(tǒng)變量測試

圖中我們在服務(wù)端重新定義了系統(tǒng)變量.z.pg。通過條件語句對傳遞進(jìn)來的參數(shù)進(jìn)行了判斷。首先判斷是否為symbol類型,如果是說明調(diào)用的是服務(wù)端的函數(shù),這時開始執(zhí)行.[value first x; 1_x; ::]這條語句,同時開始判斷傳遞的第二個參數(shù),不是函數(shù)需要的數(shù)據(jù)類型,則返回一個type錯誤;如果不是symbol類型,則返回一個不支持的類型錯誤。

Q還有很多可以重新定義的系統(tǒng)變量。大家可以研究一下官方網(wǎng)站提供的所有系統(tǒng)變量的用法。https://code.kx.com/v2/ref/dotz/#zpg-get

接下來在介紹一下系統(tǒng)變量.z.po和.z.pc。當(dāng)進(jìn)程通信打開的時候,.z.po就會自動處理打開的進(jìn)程信息,當(dāng)進(jìn)程通信關(guān)閉的時候,.z.pc就會自動處理關(guān)閉的進(jìn)程信息。我們通過重新定義這兩個系統(tǒng)變量來進(jìn)行測試一下。首先在服務(wù)端定義一個表叫Registry,來保存客戶端發(fā)給服務(wù)端的進(jìn)程通信信息,同時定義一個register函數(shù),該函數(shù)將客戶端發(fā)送給服務(wù)端的信息上傳到Registry的表中。然后我們重新定義系統(tǒng)變量.z.po,當(dāng)進(jìn)程通信打開的時候會將通信通道的編號和`unregistered上傳傳到Registry表中;同時.z.pc在進(jìn)程通信關(guān)閉的時候?qū)egistry表中的信息進(jìn)行刪除。

.z.po和.z.pc系統(tǒng)變量測試

圖中我們可以看到,當(dāng)我們在客戶端打開進(jìn)程通信的時候,我們的服務(wù)端的系統(tǒng)變量.z.po執(zhí)行了相應(yīng)的語句;然后當(dāng)我們在客戶端執(zhí)行相應(yīng)的服務(wù)端函數(shù)調(diào)用的時候,這個時候我們可以看出表Registry中有信息更新;最后當(dāng)我們在客戶端關(guān)閉進(jìn)程通信的時候,我們服務(wù)端的系統(tǒng)變量.z.pc執(zhí)行了相應(yīng)的語句,也就是刪除了表Registry中的對應(yīng)信息。

5. 遠(yuǎn)程查詢案例

下面我介紹一個通過客戶端來遠(yuǎn)程查詢服務(wù)端中表的信息。

遠(yuǎn)程查詢案例

首先我們在服務(wù)端導(dǎo)入一個trades的表(前期我通過.Q.en序列化到本地的擴(kuò)展表)。打開進(jìn)程通信之后,我們在服務(wù)端和客戶端都可以查詢,但是我們在客戶端的查詢是通過最原始的方式,這樣的方式非常不安全,因此我們可以在我們的服務(wù)端定義好查詢函數(shù),然后在客戶端進(jìn)行調(diào)用和傳遞相應(yīng)參數(shù)即可。

七、HTTP與WEB的Sockets

(這部分還沒完全弄明白,等弄明白了補上)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容