socat:シェルを使用して簡単な二重通信を実行するには?

socat:シェルを使用して簡単な二重通信を実行するには?

リモートサーバーへのTCP接続を作成し、socatクライアントが送信します。

  1. 画像ファイルの操作("convert""open"など)
  2. イメージ(バイナリコンテンツ)自体

その後、サーバーは与えられたタスクを使用して画像を処理します。

私の最初の考えは、バイナリコンテンツの前にこのテキスト操作タイプを渡すことでした。

# client
socat tcp:server:9999 -\
  < <(echo "open"; cat path/to/imgfile)

# server
socat tcp-listen:9999 - | {
  IFS= read -r action
  cat - > /tmp/imgfile
  # do process image file with action type ...
}

これは特定の状況では機能しているように見えますが、複数のパラメータを渡す必要がある場合など、柔軟ではありません。また、バイナリコンテンツの前にテキストを追加すると、画像ファイルが破損している可能性があります。

だから私は2つのパラメータを1つずつ渡すよりきれいな解決策を見つけようとしました(ここでは二重通信と呼ばれます)。

ここに画像の説明を入力してください。

これまで私が試したことは、次の分野の専門家ではないということですsocat

# client
socat\
  tcp:server:9999\
  system:'echo "open"; IFS= read -r ack; cat path/to/image.png'

# server
socat tcp-listen:9999\
  system:'IFS= read -r action; echo "ACK"; binary=$(cat -); echo "process image with action now..."'

今は許してください

壊れたパイプ

このタイプの二重通信は可能ですかsocat(新しいポートを開かない最良の場合)。

より簡単な選択肢を考えてみましたか?

おそらくこれはもはやフレームを使用せず、可能であればHTTPサーバーのオーバーヘッドなしで最も簡単で簡単な方法で行う必要があると言いたいと思います。シェルはBashであり、利用可能なZSHはありません。一般的なPythonを使用できます。

答え1

socatを使用してこのタイプの二重通信が可能ですか(新しいポートを開かない最良の場合)。

いいえ。しかし、それはそれとは何の関係もありませんsocat。 TCP接続が閉じられ、使用できなくなりました。しかし、特にcat -Stéphaneが指摘したように、あなたのメカニズムは接続を閉じることに依存しています。

シェルはBashであり、利用可能なZSHはありません。一般的なPythonを使用できます。

だから、普通のPythonです。

~から 含まれるモジュールのPythonドキュメント、実際にはわずかに修正されました。

from xmlrpc.server import SimpleXMLRPCServer
from xmlrpc.server import SimpleXMLRPCRequestHandler

PORT = 8000

class RequestHandler(SimpleXMLRPCRequestHandler):
    rpc_paths = ('/RPC2',)

with SimpleXMLRPCServer(('localhost', PORT),
                        requestHandler = RequestHandler,
                        use_builtin_types = True) as server:
    server.register_introspection_functions()

    # Register a function under function.__name__.
    @server.register_function
    def convert(target_format: str, img: bytes):
        # do something with img instead of just returning it,
        # but you get the idea.
        return img

    @server.register_function
    def length(img: bytes):
        return len(img)


    server.serve_forever()

クライアント側では

import xmlrpc.client

PORT = 8000

server_handle = xmlrpc.client.ServerProxy(f'http://localhost:{PORT:d}',
                                          use_builtin_types = True)

with open("inputimage.jpg", "rb") as infile:
    converted = server_handle.convert("image/png", infile.read())

with open("outputimage.png", "wb") as outfile:
    outfile.write(converted)

# Print list of available methods
print(server_handle.system.listMethods())

これは確かに一般的なPythonであることに注意してください。xmlrpc.server標準のPythonにはそれ以上もsocket含まれていませんmath


ここからバニラの概念を拡張できます。venvこれはPython標準ライブラリの一部でもあります。あなたは本当に良いと思います。持つ標準のPythonです。

python3 -m venv serverstuff
. serverstuff/bin/activate
pip install Flask

もう少し快適にしてください。その後、次のようなサーバーを作成できます。クイックスタートの例:

from flask import Flask
from flask import request
from flask import Response

app = Flask(__name__)


@app.route("/")
def say_hello():
    return "helloo"


@app.route("/convert/<target_fmt>"):
def convert(target_fmt):
    print(target_fmt)
    try:
        infile = request.files["file"]
    except Exception as e:
        print(e)
        return Response(response="", status=406)
    # convert it; infile is a Werkzeug.Filestorage
    # https://werkzeug.palletsprojects.com/en/2.3.x/datastructures/#werkzeug.datastructures.FileStorage
    data = infile.read()
    return Response(response=data, mimetype=f"image/{target_fmt}", status=200)

として保存server.pyして実行しますpython3 -m flask --app server run --port 8000

これにより、クライアント側のシェルスクリプトが非常に簡単になります。

$ myfiletoupload=/home/mosaic/catpicture.jpg
$ curl http://127.0.0.1:8000/convert/png -F "file=@${myfiletoupload}"

答え2

Socatのソリューション:

試みたときに発生する状況は次のとおりです。最初の成功した転送の後、シェルはファイルの内容を書き込んで終了します。 Socat はデータをサーバーに送信し、TCP halfclose を使用して EOF を送信します。サーバーはデータ(およびEOF)を受け取り、処理メッセージを送信します。クライアントは処理メッセージをシェルに書きたいと思っていましたが、終了したので「壊れたパイプ」が発生しました。

Socatに解決策があるかどうか疑問に思います。はい、1つあり、ほぼ百万のトリックがあります...簡単なテストに合格しますが、実際の要件のために失敗する可能性があります。

問題は、シェルがEOFを送信する必要がありますが、まだメッセージを受け取ることができるはずです。シェルに半閉じを実行させる方法がわからないので、2つのシェルを使用し、パイプを使用してSocatの外部でACK条件を渡すのがコツです。

顧客:

set +H; rm -f /tmp/pipe0; mkfifo /tmp/pipe0; 
socat -t 3600 \
  tcp:server:9999 \
  system:'echo "open"; cat /tmp/pipe0 >/dev/stderr; cat /path/to/imgfile'!!system:'IFS= read -r ack; echo $ack >/tmp/pipe0; cat >/dev/stderr'

仕える人:

socat -t 3600\
  tcp-listen:9999,reuseaddr \
  system:'IFS= read -r action; echo "ACK"; binary=$(cat -); echo "process image with action now..."'

説明する:

「set + H」を使用すると、bashで「!」を使用できます。

パイプはリーダーによって初めて開かれるため、すでに存在する必要があります。

"-t 3600"はハーフクローズタイムアウトを秒単位で設定し、最大処理時間より長くする必要があります。

クライアントは2つのシェルを開きます。あるデータソースは別のシェルから渡されたACKを待ち、ファイル転送後に終了します。 2番目のシェルはサーバーからすべてのデータ、特にACKを取得し、最初のシェルをトリガーし、最後にサーバー出力はstderrにパイプされます。

関連情報