Skip to content

Instantly share code, notes, and snippets.

@kohyama
Last active December 22, 2015 10:19
Show Gist options
  • Save kohyama/6458161 to your computer and use it in GitHub Desktop.
Save kohyama/6458161 to your computer and use it in GitHub Desktop.
An example client code to binary-communicate with a TCP server / TCP サーバとバイナリ通信するクライアントコード例

TCP-BIN

An example client code to binary-communicate with a TCP server.

TCP サーバとバイナリ通信するクライアントコードの例です.

Usage / 使い方

Give informations of a TCP server to the first argument as a map. :host is a string representing an IP address or a hostname which can be forward-looked up and :port is an integer represents a port. Give a sequence of integers from 0 upto 255 representing a command to be sent to a server as the 2nd argument cmd. The 3rd len is an expected number of bytes of a response from a server as an integer. Specifiy the time to timeout in milli second as an integer.

Though my code uses clojure.tools.logging, you can remove :require clause and replace warn, debug and trace to println instead or just comment them out.

最初の引数にマップで TCP サーバの情報を指定します. :host にホストの IP アドレスまたは正引き可能なホスト名を文字列で :port にポート番号を整数で渡します. 二番目の引数 cmd には, サーバに送りたいコマンドのバイト列を 0~255 の整数のシーケンスとして渡します. 三番目の引数 len に想定されるサーバからのレスポンスのバイト数を整数で指定します. 四番目の引数 timeout にタイムアウトするまでのミリ秒単位の待ち時間を整数で指定します.

例のコードでは clojure.tools.logging を使います. :require 節を消して, warn, debug, traceprintln で置き換えたりコメントアウトしても良いです.

Explanation / 説明

Only the sequence to read bytes is explained below. About other parts, code will explain itself enough.

読み込み手順のみ下に説明します. 他の部分はコードを読めばだいたい分かるでしょう.

; m: number of bytes already read
; t: number of times tried to read
; initialize both to 0
(loop [m 0 t 0]
  ; 1. If number of bytes already read is
  ;    smaller than expected number and
  ;    number of times is smaller than
  ;    timeout [ms] divided 10 [ms],
  ;    go to 2,
  ;    else go to 3
  (if (and (< m len) (< t u))
    ; 2. If the input stream has one or more bytes,
    (let [l (if (.available is)
                ; read them,
                (.read is ibuf m (- len m))
                ; else wait 10 [ms]
                (do (Thread/sleep 10) 0))]
    ;     update m and t and back to 1.
      (recur (+ m (long l)) (inc t)))
    ; 3. return number of bytes read
    m))
; m: 既に読み込んだバイト数
; t: 読み込みを試した回数
; をそれぞれ 0 に初期化します.
(loop [m 0 t 0]
  ; 1. 読み込んだバイト数が期待するより小さく
  ;    且つ, 読み込みを試した回数が, 指定され
  ;    たタイムアウト[ミリ秒] を 10 で割った
  ;    ものよりも小さいならば 2
  ;    そうでなければ 3
  (if (and (< m len) (< t u))
    ; 2. 入力ストリームに入力があれば
    (let [l (if (.available is)
                ; それを読み込み
                (.read is ibuf m (- len m))
                ; そうでなければ 10ミリ秒待って
                (do (Thread/sleep 10) 0))]
    ;     m と t を更新して 1. へ戻ります.
      (recur (+ m (long l)) (inc t)))
    ; 3. 読み込んだバイト数を返します.
    m))

^ Clojure Exercises / Clojure 演習

(ns tcp-bin
(:require [clojure.tools.logging :refer :all]))
(defn- i2b [i] (byte (if (< i 128) i (- i 256))))
(defn- b2i [b] (let [i (int b)] (if (< i 0) (+ i 256) i)))
(defn oi
"Send `cmd` bytes to `port` of `host` and receive `len` bytes response from it
with `timeout` [ms] timeout."
[{:keys [host port]} cmd len timeout]
(try
(with-open [s (java.net.Socket. ^String host ^int port)
^java.io.OutputStream os (.getOutputStream s)
^java.io.InputStream is (.getInputStream s)]
(let [^bytes obuf (byte-array (map i2b cmd))
_ (trace "tcp-bin:oi:send:" (seq obuf))
_ (.write os obuf 0 (count cmd))
_ (.flush os)
^bytes ibuf (byte-array len)
u (quot timeout 10)
n (loop [m 0 t 0]
(if (and (< m len) (< t u))
(let [l (if (.available is)
(.read is ibuf m (- len m))
(do (Thread/sleep 10) 0))]
(recur (+ m (long l)) (inc t)))
m))
]
(trace "tcp-bin:oi:recv: [" n "]" (seq ibuf))
(map b2i (take n (seq ibuf)))))
(catch Throwable t (warn t) (debug t t))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment