地方エンジニアの学習日記

興味ある技術の雑なメモだったりを書いてくブログ。たまに日記とガジェット紹介。

【Rust】TCPのエコーサーバを書く

仕事で使うわけでは今のところないけど触ってみてる。コンパイルエラーが凄くてそれ通りに書いていくと動くというのが良かった。cargoコマンドも少ししか触っていないけど便利。マスコットキャラクターのカニが可愛い。

Linuxにも入っていてるらしいのでとても楽しみ。

ソースコード

AIが優秀というのもあるけど出てくるコード自体は理解がしやすくて書きやすかった。ただ所有権とか出てくる手前で多分ここから難しくなっていくんだろうなと思ったりした。 Firecrackerのコードを読めるくらいにはRustに慣れておきたいので読んで書いてをやっていく。

use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
use std::thread;

fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 512];

    loop {
        match stream.read(&mut buffer) {
            Ok(0) => {
                println!("Client disconnected");
                break;
            }
            Ok(n) => {
                println!("Received: {}", String::from_utf8_lossy(&buffer[..n]));
                if let Err(e) = stream.write_all(&buffer[..n]) {
                    eprintln!("Failed to send data: {}", e);
                    break;
                }
            }
            Err(e) => {
                eprintln!("Error reading from stream: {}", e);
                break;
            }
        }
    }
}

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8080")?;

    println!("Server running on 127.0.0.1:8080");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(move || handle_client(stream));
            }
            Err(e) => {
                eprintln!("Connection failed: {}", e);
            }
        }
    }

    Ok(())
}

【Linux】シグナルセーフとは

docs.oracle.com

Linuxにおけるシグナルセーフとは、シグナルハンドラ内で安全に使用できる関数の特性を指します。シグナルは、特定のイベントが発生した際にプロセスに通知を送るための仕組みであり、その性質上、シグナルハンドラはプログラムの任意の時点で実行される可能性があります。そのため、シグナルハンドラ内では、予期しない動作を防ぐために特定の制約が課されます。

一般的な関数の多くは、シグナルが発生する可能性のある状況で使用すると予測不可能な動作を引き起こすことがあります。たとえば、グローバル変数や静的データを操作する関数、内部でメモリ確保やロックを行う関数は、シグナルによる中断が発生するとデータ競合やデッドロックの原因となります。こうした問題を避けるため、Linuxでは「シグナルセーフ」と呼ばれる関数群が明確に定義されています。

qiita.com

シグナルセーフな関数とは、シグナルハンドラ内で実行されたとしても、不整合や予測不可能な動作を起こさないように設計された関数です。これらの関数は、非同期に実行される可能性がある状況でも問題なく動作するよう配慮されています。たとえば、標準出力への低レベル書き込みを行うwrite関数や、プロセスの終了を即座に行う_exit関数などがこれに該当します。一方で、mallocやprintfなどの関数は、内部で状態を保持するため、シグナルハンドラ内で使用するとスタック破壊やデータ競合を引き起こす可能性があるため避けるべきです。

例えばこんなコードはシグナルセーフではない。

#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void signal_handler(int signum) {
    // シグナルハンドラ内でprintfを使用(シグナルセーフではない)
    printf("Caught signal %d\n", signum);
}

int main() {
    signal(SIGINT, signal_handler);

    while (1) {
        printf("Running...\n");
        sleep(1);
    }

    return 0;
}

シグナルセーフな設計の重要性は、信頼性が求められるシステムプログラミングにおいて特に顕著です。たとえば、サーバープログラムやリアルタイムシステムでは、シグナル処理中の不整合がシステム全体の動作に重大な影響を及ぼす可能性があります。そのため、シグナルハンドラを実装する際には、シグナルセーフな関数のみを利用し、必要最小限の処理にとどめることが推奨されています。これにより、システムの安定性を確保しつつ、シグナルの非同期性によるリスクを最小限に抑えることが可能になります。

最近だとsshdが話題になっていたりしました。

www.trendmicro.com

【JavaScript】EventEmitter

EventEmitter とは、Node.js が提供する イベント駆動モデルを実現するためのクラスです。このクラスを使うことで、カスタムイベントを定義し、そのイベントのリスナー(処理)を登録して、イベント発生時にそれを呼び出す仕組みを簡単に実装できます。

主な特徴

  • イベントの登録
    • イベントに対してリスナー関数を登録できます。
  • イベントの発火(emit)
    • イベントを発生させ、登録されているリスナーを呼び出します。
  • 非同期処理
    • イベント駆動モデルに基づき、非同期処理を効率的に扱うことができます。

基本的な使い方

  1. EventEmitterのインポート
const EventEmitter = require('events');
  1. イベントの登録と発火
const EventEmitter = require('events');
const myEmitter = new EventEmitter();

myEmitter.on('greet', (name) => {
  console.log(`hi! ${name}さん!`);
});

myEmitter.emit('greet', 'hoge');

イベント駆動モデルの利点

  • 非同期処理の簡略化
    • イベントをリスナーとして登録するだけで非同期処理を簡潔に記述できる。
  • 柔軟な設計
    • イベントに応じた動的な処理を追加しやすい。
  • 高いスケーラビリティ
    • 大量のリクエストやデータを効率的に処理できる。

【TypeScript】TCPサーバーでEchoを返す

最近はTypeScriptを書いています。JavaScriptも対して書いたことないので独特な書き味に色々戸惑っています。特にコールバック、無名関数、Promiseあたりは読めるようにはなったがまだ空では書けないですし複雑になると読めないですwコールバック地獄とか言葉は知ってるけどなんでああなるの?がわかっていなかったのですがわかってよかったです。本業でも少し使っているし今後は手伝ってる会社の方で書くことが増えそうなのでやっていきます。

import * as net from 'net';

const HOST = '127.0.0.1';
const PORT = 3000;

const server = net.createServer((socket) => {
  console.log('クライアントが接続しました:', socket.remoteAddress, socket.remotePort);

  socket.on('data', (data) => {
    console.log('受信したデータ:', data.toString());
    socket.write(`Echo: ${data}`);
  });

  socket.on('end', () => {
    console.log('クライアントが切断しました');
  });

  socket.on('error', (err) => {
    console.error('ソケットエラー:', err.message);
  });
});


server.listen(PORT, HOST, () => {
  console.log(`サーバーが起動しました: ${HOST}:${PORT}`);
});

server.on('error', (err) => {
  console.error('サーバーエラー:', err.message);
});

特に慣れないのがこの辺。イベント駆動モデルでアプリケーションを書き慣れていないので慣れてないだけかもですが...

server.on('error', (err) => {
  console.error('サーバーエラー:', err.message);
});