シリアル通信アプリ開発の落とし穴
まず結論
シリアル通信アプリで本当に難しいのは、送受信 API そのものではありません。難しいのは境界、タイムアウト、状態遷移、再接続、観測可能性です。
押さえたいポイント:
- シリアル通信は順序付き byte streamであって、メッセージ境界は勝手には付かない
Read(100)したからといって 100 byte ぴったり返るとは限らない- .NET の
DataReceivedは受信 byte ごとに発火するとは限らず、UI スレッドでもない - タイムアウトは1個では足りない。open、inter-byte、response、reconnect で分ける
- 送信はどこからでも
Writeできるようにするより、single writer に寄せる
シリアル通信は「メッセージ」ではなく「順序付き byte stream」
アプリ側からは「コマンドを1つ送り、応答を1つ受ける」ように見えても、下の層では byte 列が流れているだけです。1回 Write した内容が相手側で2回に分かれて届いたり、他のデータと連結して届いたりします。
| よくある思い込み | 実際 |
|---|---|
| Read(16) なら 16 byte 返る | 到着状況次第で途中までしか取れない |
| DataReceived = 1メッセージ到着 | byte ごと保証されず、UI スレッドでもない |
| Write が返った = 相手が処理完了 | 送信側バッファに積めただけ |
| COM 一覧 = 現在の接続状態 | 列挙順は不定、結果が古いことも |
このため、メッセージ境界をプロトコルとして自分で定義する必要があります(固定長、区切り文字、長さ+payload+checksum など)。
最初に決めるべきこと
- フレーム境界 - どの byte 列を1メッセージと見なすか
- テキストかバイナリか - 両方混ざる場合は境界ルールを明示
- タイムアウトの意味 - open, inter-byte, response, reconnect で分ける
- フロー制御とライン状態 - BaudRate, DataBits, Parity, StopBits, Handshake, DTR/RTS
- 責務分離 - 読む人、書く人、パースする人、業務状態へ反映する人を分ける
- 状態遷移 - Closed, Opening, Ready, WaitingResponse, Fault, Reconnecting
- ログと調査性 - open/close 時刻、ポート設定、送受信 hex dump、エラー情報
よくある落とし穴
1. 「1回の Read = 1メッセージ」だと思う
最も多いミス。受信はまずバッファに蓄積し、そこから parser がフレームを切り出す形に分ける。
2. DataReceived をそのまま業務イベントにする
DataReceived は「何か来たらしい」の通知と割り切り、ハンドラ内では重い処理をしない。UI 更新は必ず UI スレッドに戻す。
3. どこからでも Write してよいと思う
UI ボタン、監視タイマー、再接続処理がそれぞれ直接 Write する構成は崩れやすい。single writer に寄せる。
4. ReadLine()/WriteLine() で全部通す
行ベースのテキストプロトコルのときだけ便利。NewLine 不一致、ペイロード中の改行、バイナリ混在があると境界が壊れる。
5. タイムアウトを設計せず既定のままにする
同期 read の無限待ち、1個の timeout ですべてを表現しようとする、UI スレッドで同期 read する、は詰まりやすい。
6. RTS/CTS、DTR/RTS を軽く見る
ハンドシェイクや制御線の設定不一致で、送信がたまに止まる、一定量を超えると取りこぼす、といった症状が出る。
7. Open() のやり直しだけで再接続した気になる
特に USB-シリアルでは、session 無効化、pending request fail、reader/writer 停止、backoff 後の reopen、装置初期化の再実行まで含めて扱う。
8. COM ポート列挙を真実だと思う
一覧に出たことと open できることは別。前回の COM7 を盲信したり、先頭を自動選択したりしない。
ベストプラクティス
責務を分けるのが最も効きます:
- reader: port から byte 列を読むだけ
- writer: outbound queue から順番に書くだけ
- parser: byte 列から frame を切り出すだけ
- protocol: request と response の対応、checksum を扱う
- app state: 業務状態を更新するだけ
受信は buffer に蓄積してから parser が frame を切り出す。送信は single writer に集約する。再接続は単なる reopen ではなく session 再生成として扱う。生ログ(raw hex dump)と要約ログの両方を持つ。
チェックリスト
- メッセージ境界は明文化されているか
- 受信は byte 蓄積 → frame 切り出しになっているか
- DataReceived をメッセージ到着扱いしていないか
- UI スレッドで同期 I/O していないか
- 送信は single writer か
- timeout は意味ごとに分かれているか
- Handshake / DTR / RTS が明示されているか
- reconnect で session を作り直しているか
- raw hex dump を残しているか
- 実機の抜き差しや途中切断を試験しているか
まとめ
- シリアル通信はメッセージではなく byte stream
- Read 単位とメッセージ単位は一致しない
- 境界はプロトコルとして自分で定義する
- 送受信は責務を分離し、送信は single writer に寄せる
- timeout は意味ごとに分割、再接続は session 単位で設計
- raw hex dump を含むログが後の調査をかなり楽にする
シリアル通信アプリでは、ポートを開けることより、byte 列をどう解釈し、時間と状態をどう制御するかのほうがずっと大事です。
参考資料
- Microsoft Learn,
SerialPort.DataReceivedEvent - Microsoft Learn,
SerialPort.ReadMethod - Microsoft Learn,
SerialPort.ReadTimeoutProperty - Microsoft Learn,
SerialPort.BaseStreamProperty - Microsoft Learn,
SerialPort.NewLineProperty - Microsoft Learn,
HandshakeEnum - Microsoft Learn,
SerialPort.DtrEnableProperty - Microsoft Learn,
SerialPort.RtsEnableProperty - Microsoft Learn,
SerialPort.GetPortNamesMethod - Microsoft Learn,
SerialPortClass - Microsoft Learn,
COMMTIMEOUTSstructure - Microsoft Learn,
DCBstructure - Microsoft Learn,
CreateFilefunction - pySerial API, Serial API Reference
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
.NET Framework→.NET移行の事前チェックリスト
.NET Framework から .NET へ移行する前に、何を整え、何を切り離し、何を捨てるかを判断するための実務チェックリストです。アプリ種別の難易度、移行不能な技術、共有ライブラリの切り出し方まで整理します。
C#をNative AOTでDLL化しC/C++から呼び出す方法
C# のクラスライブラリを Native AOT でネイティブ DLL として発行し、UnmanagedCallersOnly で C/C++ から呼び出す構成を、設計指針・最小実装・はまりどころまでまとめて整理した実務向け解説記事です。
デスクトップアプリにGeneric Hostを導入する
WPF や WinForms の常駐アプリで、起動・停止・例外処理・定期処理が UI に染み出してきたら読みたい記事。Generic Host と BackgroundService を導入して責務を整理し、graceful shutdown まで設計に組み込む考え方を、最...
FileSystemWatcherの安全な使い方
FileSystemWatcher のイベントは完了通知ではないという前提に立ち、取りこぼし、重複通知、完了判定の落とし穴を整理し、再スキャン要求への畳み込み、原子的 claim、idempotency までの設計指針をまとめます。
ClickOnce 入門:配布・更新・選定基準
ClickOnce の仕組みと向き不向きを実務目線で整理します。マニフェスト、自動更新、キャッシュ分離、署名、配布経路の注意点に加え、社内業務アプリで強い理由と MSI/MSIX が適するケースの見分け方が分かります。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。