C#でTCPソケットを使った画像処理ワーカーを作る

コンピュータ

GIMPのPython-Fuから、外部のワーカープロセスで画像のフィルター処理を行う仕組みを試しました。

ワーカープロセスが起動している必要がありますが、

起動処理もクライアントコードに組み込むことが出来ないか検証してみました。

ソースコード

サーバー(WPF)
ファイル名:TcpWpfWorker.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

ファイル名:MainWindow.xaml.cs

using System.Windows;

namespace TcpWpfWorker;

public partial class MainWindow : Window
{
    private readonly TcpServer _tcpServer;

    public MainWindow()
    {
        InitializeComponent();

        _tcpServer = new TcpServer(5001); // 任意のポート
        _tcpServer.LogReceived += OnLogReceived;
        _tcpServer.Start();
    }

    private void OnLogReceived(string message)
    {
        Dispatcher.Invoke(() =>
        {
            LogTextBox.AppendText(message + "\r\n");
            LogTextBox.ScrollToEnd();
        });
    }

    protected override void OnClosed(EventArgs e)
    {
        base.OnClosed(e);
        _tcpServer.Stop();
    }
}

ファイル名:TcpServer.cs

using System.Net;
using System.Net.Sockets;
using System.Text;

namespace TcpWpfWorker;

public class TcpServer
{
    private readonly int _port;
    private TcpListener? _listener;
    private bool _isRunning;

    public event Action<string>? LogReceived;

    public TcpServer(int port)
    {
        _port = port;
    }

    public void Start()
    {
        _listener = new TcpListener(IPAddress.Any, _port);
        _listener.Start();
        _isRunning = true;

        Log($"Server started on port {_port}");

        Thread listenerThread = new Thread(ListenLoop);
        listenerThread.IsBackground = true;
        listenerThread.Start();
    }

    public void Stop()
    {
        _isRunning = false;
        _listener?.Stop();
        Log("Server stopped.");
    }

    private void ListenLoop()
    {
        try
        {
            while (_isRunning)
            {
                var client = _listener?.AcceptTcpClient();
                if (client == null) continue;

                Thread worker = new Thread(() => HandleClient(client));
                worker.IsBackground = true;
                worker.Start();
            }
        }
        catch (SocketException ex)
        {
            Log($"Socket exception: {ex.Message}");
        }
    }

    private void HandleClient(TcpClient client)
    {
        var endPoint = client.Client.RemoteEndPoint?.ToString();
        Log($"Connected: {endPoint}");

        using NetworkStream stream = client.GetStream();
        byte[] buffer = new byte[4096];
        int length;

        try
        {
            while ((length = stream.Read(buffer, 0, buffer.Length)) > 0)
            {
                string message = Encoding.UTF8.GetString(buffer, 0, length).Trim();
                Log($"Received: {message}");

                // 必要なら返信もできる
                byte[] replyBytes = Encoding.UTF8.GetBytes("ACK");
                stream.Write(replyBytes, 0, replyBytes.Length);
            }
        }
        catch (Exception ex)
        {
            Log($"Client error: {ex.Message}");
        }

        Log($"Disconnected: {endPoint}");
        client.Close();
    }

    private void Log(string msg)
    {
        LogReceived?.Invoke($"[{DateTime.Now:HH:mm:ss}] {msg}");
    }
}

ファイル名:MainWindow.xaml

<Window x:Class="TcpWpfWorker.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="TCP Worker" Height="450" Width="800">
    <Grid>
        <TextBox Name="LogTextBox"
                 VerticalScrollBarVisibility="Auto"
                 HorizontalScrollBarVisibility="Auto"
                 TextWrapping="Wrap"
                 AcceptsReturn="True"
                 IsReadOnly="True"
                 FontFamily="Consolas"
                 FontSize="12"
                 Margin="10"/>
    </Grid>
</Window>

クライアント(python)

tcp_client.py

import socket
import sys

def main():
    if len(sys.argv) < 2:
        print("usage:")
        print("  python tcp_client.py \"send message\"")
        return

    message = sys.argv[1]

    HOST = "127.0.0.1"
    PORT = 5001   # WPF 側と合わせる

    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.connect((HOST, PORT))

            print(f"connected to {HOST}:{PORT}")
            print(f"sending: {message}")

            # 送信
            sock.sendall(message.encode("utf-8"))

            # 応答受信(任意)
            try:
                data = sock.recv(4096)
                if data:
                    print("reply:", data.decode("utf-8", errors="ignore"))
            except:
                pass

    except Exception as e:
        print("error:", e)


if __name__ == "__main__":
    main()

実行例

サーバー側のスクリーンショット

クライアント側スクリーンショット

ワーカープロセス起動機能付きクライアント

ワーカーからの応答がない場合、ワーカープロセスを起動する用にするコードを、
クラアンとスクリプトに組み込みました。

tcp_client_with_bootsvr.py

import time
import subprocess
import socket
import sys

HOST = "127.0.0.1"
PORT=5001
WORKER_EXE="J:/csharp/WpfCB2/TcpWpfWorker/bin/Release/net8.0-windows/TcpWpfWorker.exe"


def is_worker_alive(port, timeout=3.0):
    end = time.time() + timeout
    while time.time() < end:
        try:
            with socket.create_connection(("127.0.0.1", port), 0.2):
                return True
        except OSError:
            time.sleep(0.1)
    return False

def main():
    if len(sys.argv) < 2:
        print("usage:")
        print("  python tcp_client.py \"send message\"")
        return

    message = sys.argv[1]

    if not is_worker_alive(PORT):
        subprocess.Popen([WORKER_EXE])

    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
            sock.connect((HOST, PORT))

            print(f"connected to {HOST}:{PORT}")
            print(f"sending: {message}")

            # 送信
            sock.sendall(message.encode("utf-8"))

            # 応答受信(任意)
            try:
                data = sock.recv(4096)
                if data:
                    print("reply:", data.decode("utf-8", errors="ignore"))
            except:
                pass

    except Exception as e:
        print("error:", e)


if __name__ == "__main__":
    main()

コメント