TCP/IP 通信は確実にデータの送受信ができるので通信品質が高く、現在でも使用されている通信手段の1つとして使用されています。
C# で TCP/IP 通信を非同期処理でプログラムを実装する方法をこの記事にまとめていますので、参考にしてみてください。
環境
項目 | 内容 |
---|---|
OS | Windows10 |
IDE | Visual Studio 2019 |
フレームワーク | .NET Core3.1 |
UIフレームワーク | WPFアプリ |
オススメの参考書
C#の使い方が丁寧に解説しており、「基礎からしっかりと学びたい」という初心者の方にオススメの一冊です。サンプルコードも記載してあり、各章の最後に復習問題があるので理解度を確認しながら読み進めることができます。新しい C# のバージョンにも対応している書籍です。
TCP/IPの通信イメージ
まず TCP/IP 通信は、サービスや機能を提供するサーバーとサービスや機能を利用するクライアントが相互に接続しあってデータの送受信を行います。
C# ではサーバーとクライアントのプログラミングを簡単に実装できるように、Socket クラスをベースとした、TcpListener クラスと TcpClient クラスが用意されています。
この2つのクラスを利用した場合の通信イメージを理解しておくことで、プログラミングがスムーズに出来ると思いますので、下図の通信手順をみてみましょう。
- サーバー側はTcpListenerクラスで接続を開始して、クライアントからの要求を待ち受けします。
- クライアント側はTcpClientクラスで接続要求をし、サーバーに接続します。
- サーバーはクライアント側からの接続要求を受け付けると、TcpClientオブジェクトを作成して通信を確立します。
- 通信確立後、クライアントからリクエストを送信し、サーバーはリクエストを受信します。
- サーバーからレスポンスを送信し、クライアントはレスポンスを受信します。
- 最後にサーバーとクライアントのTcpClientの接続を閉じます。
今回のサンプルコードではサーバーとクライアント間で接続をして、クライアントから「Hello」を受信したら、「OK」を送信するプログラムを作成します。通信には非同期処理を使用します。
サーバー側の処理
サーバー側の処理を行う為に2つの関数を用意します。上図のフローチャート(通信処理の流れ)と比較しながら関数の役割を確認してみてください。
関数 | 役割 |
---|---|
StartListening | 指定されたIPアドレスとポートを、TCP/IP通信のソケットのエンドポイントとしてTcpListenerを作成する。クライアントの接続要求を受け入れ、通信を確立させる。 |
ReceiveAsync | クライアントからのレスポンスデータを受信する。受信がすべて完了したら、クライアントへ受信が完了したことを知らせる為にレスポンスを送信する。 |
サンプルソースコード
「tcpserver.cs」という新しい項目(ファイル)を作成して、プロジェクトに追加します。
ソケット関連で使用する名前空間System.Net.Sockets
とSystem.Net
を参照するために、usingディレクティブを用いてコードの先頭に記述します。
using System;
using System.Net.Sockets;
using System.Net;
StartListening関数
先ほど作成したtcpserver.csに記述するコードは以下になります。
public async Task>bool> StartListening(int port)
{
// 接続を待つエンドポイントを設定
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
// TcpListenerを作成
var tcpServer = new TcpListener(localEndPoint);
try
{
// 待ち受け開始
tcpServer.Start();
while (true)
{
Debug.WriteLine("接続待機中...");
// 非同期で接続要求を待機
using (var tcpClient = await tcpServer.AcceptTcpClientAsync())
{
Debug.WriteLine("クライアントからの接続要求を受け入れ");
// クライアントからデータを受信
var request = await ReceiveAsync(tcpClient);
}
Debug.WriteLine("接続終了");
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
return false;
}
IP アドレスとポート番号を指定して、エンドポイントを設定します。IP アドレスは、IPAddress.Any
として、どんなIPアドレスも受け付けるようにします。ポート番号は引数で渡すことで変更できるようにしました。
指定したローカルエンドポイントを使用してTcpListener
のインスタンスを初期化します。
非同期処理のAcceptTcpClientAsync
でクライアントからの接続要求があるまで待機します。接続要求があるまで次の処理は実行されることがありません。
今回はwhile
で何度もクライアントと接続をし続けるようにしています。
ReceiveAsync関数
次に以下のコードをtcpserver.csに記述します。
public async Task<string> ReceiveAsync(TcpClient tcpClient)
{
byte[] buffer = new byte[1024];
string request = "";
try
{
using (NetworkStream stream = tcpClient.GetStream())
{
// クライアントからリクエストを受信する
do
{
int byteSize = await stream.ReadAsync(buffer, 0, buffer.Length);
request += Encoding.UTF8.GetString(buffer, 0, byteSize);
}
while (stream.DataAvailable);
Debug.WriteLine($"クライアントから「{request}」を受信");
// クライアントへレスポンスを送信する
var response = "OK";
buffer = Encoding.ASCII.GetBytes(response);
await stream.WriteAsync(buffer, 0, buffer.Length);
Debug.WriteLine($"クライアントへ「{response}」を送信");
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
return request;
}
TcpClient でデータの送受信をする際はNetworkStream
クラスを使います。このクラスはネットワークを読み書きの対象とするストリーム(ストリームとは順番に流れてくるデータのこと)です。
サンプル使用例
上記で説明したサンプルソースコードを使用して、WPF でサーバーのアプリケーションを作成してみます。
ポート番号を入力して、受信開始ボタンをクリックすればクライアントと接続されるまで待機します。画面はマテリアルデザインを適用させてオシャレな画面に仕上げました。
マテリアルデザインの適用方法については、以下の記事で詳しく記載しています。
XAMLのソースは以下の記述になります。
<Window
x:Class="TcpServer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="TCP/IP (Server)"
Width="500"
Height="320"
WindowStyle="None"
mc:Ignorable="d">
<Grid>
<!-- ヘッダー -->
<materialDesign:ColorZone
Height="50"
Padding="12"
Mode="PrimaryMid">
<DockPanel>
<materialDesign:PopupBox DockPanel.Dock="Right" PlacementMode="BottomAndAlignRightEdges">
<ListBox>
<ListBoxItem Content="Close" />
</ListBox>
</materialDesign:PopupBox>
<StackPanel Orientation="Horizontal">
<materialDesign:PackIcon
Width="26"
Height="26"
Kind="AccessPoint" />
<TextBlock
Margin="16,0,0,0"
VerticalAlignment="Center"
Text="TCP/IP SAMPLE" />
</StackPanel>
</DockPanel>
</materialDesign:ColorZone>
<Label
Margin="15,61,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="TCP/IP通信のサーバー側のサンプル画面です。" />
<!-- ポート番号入力欄 -->
<Label
x:Name="lblPort"
Margin="15,98,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="ポート番号:" />
<TextBox
x:Name="txtPort"
Width="185"
Margin="90,85,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
materialDesign:HintAssist.Hint="ポート番号を入力してください。"
Style="{StaticResource MaterialDesignFloatingHintTextBox}" />
<!-- 受信開始ボタン -->
<Button
x:Name="btnStart"
Width="90"
Height="30"
Margin="0,92,10,0"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Click="BtnStart_Click"
Content="受信開始"
Foreground="Black"
Style="{StaticResource MaterialDesignOutlinedButton}" />
<!-- ログ出力欄 -->
<TextBox
x:Name="txtLog"
Margin="10,140,10,10"
AcceptsReturn="True"
IsEnabled="{Binding ElementName=MaterialDesignOutlinedTextBoxEnabledComboBox}"
Style="{StaticResource MaterialDesignOutlinedTextBox}"
TextWrapping="Wrap"
VerticalScrollBarVisibility="Auto" />
</Grid>
</Window>
コードは以下の記述になります。
using System.Windows;
namespace TcpServer
{
public partial class MainWindow : Window
{
tcpserver tcp = new tcpserver();
public MainWindow()
{
InitializeComponent();
}
private async void BtnStart_Click(object sender, RoutedEventArgs e)
{
btnStart.IsEnabled = false;
if (!await tcp.StartListening(int.Parse(txtPort.Text)))
{
btnStart.IsEnabled = true;
}
}
}
}
動作確認
サーバー側とクライアント側のアプリケーションをそれぞれ起動します。
サーバーアプリにポート番号を入力してから受信開始状態にし、クライアントアプリからサーバーへ接続要求をしてみましょう。
TCP/IP 通信でデータの送受信ができていることが確認できました。
まとめ
サーバー側の TCP/IP 通信について記載しました。この記事ではTcpListener
クラスで説明しましたが、Soket
クラスを使うことで TCP/IP 通信することもできます。
IoT が進む中、ありとあらゆる物が今後ネットワークに接続していくことが想像されます。ネットワークを用いた通信は今後も扱う機会が増えてくると思いますので、TCP/IP 通信以外の通信方法も学んでいこうと思います。
以上、最後まで読んでいただきありがとうございました。