概要
.NET Framework / .NET 6 以降でも使える、C# による TCP 通信の最小構成を 2 パターン紹介します。
- 高レベル API である
TcpClient/TcpListenerを使った方法 - 低レベル API である
Socketクラスを使った方法
1. TcpListener / TcpClient を使った実装
サーバー側
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SimpleTcpListener
{
{
internal static class Program
{
private const int Port = 2222;
private static void Main()
{
var listener = new TcpListener(IPAddress.Parse("192.168.1.104"), Port);
listener.Start();
Console.WriteLine($"Listen on {Port}");
using var client = listener.AcceptTcpClient();
using var stream = client.GetStream();
Span<byte> buffer = stackalloc byte[1024];
while (true)
{
int bytesRead = stream.Read(buffer);
if (bytesRead == 0) break;
string msg = Encoding.UTF8.GetString(buffer.Slice(0, bytesRead));
Console.WriteLine($"受信: {msg}");
}
}
}
}
クライアント側
using System;
using System.Net.Sockets;
using System.Text;
namespace SimpleTcpClient
{
internal static class Program
{
private static void Main()
{
using var client = new TcpClient("192.168.1.104", 2222);
using var stream = client.GetStream();
while (true)
{
string? line = Console.ReadLine();
if (string.IsNullOrEmpty(line)) break;
byte[] payload = Encoding.UTF8.GetBytes(line);
stream.Write(payload);
}
}
}
}
2. Socket クラスを使った実装(マルチスレッド対応)
サーバー側
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public sealed class RawSocketServer : IDisposable
{
private readonly Socket _listener;
private readonly byte[] _buffer = new byte[1024 * 1024 * 2];
public RawSocketServer(string ip, int port)
{
_listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_listener.Bind(new IPEndPoint(IPAddress.Parse(ip), port));
_listener.Listen(100);
}
public void Start()
{
Console.WriteLine($"Start listening on {_listener.LocalEndPoint}");
while (true)
{
Socket client = _listener.Accept();
ThreadPool.QueueUserWorkItem(HandleClient!, client);
}
}
private void HandleClient(object state)
{
using var socket = (Socket)state;
Console.WriteLine($"Connected: {socket.RemoteEndPoint}");
try
{
while (true)
{
int received = socket.Receive(_buffer);
if (received == 0) break;
string text = Encoding.UTF8.GetString(_buffer, 0, received);
Console.WriteLine($"[{socket.RemoteEndPoint}] {text}");
byte[] echo = Encoding.UTF8.GetBytes("ACK: " + text);
socket.Send(echo);
}
}
catch (SocketException ex)
{
Console.WriteLine(ex.Message);
}
}
public void Dispose() => _listener.Dispose();
}
クライアント側
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
public sealed class RawSocketClient : IDisposable
{
private readonly Socket _socket;
private readonly byte[] _buffer = new byte[1024 * 1024 * 2];
public RawSocketClient(string ip, int port)
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.Connect(new IPEndPoint(IPAddress.Parse(ip), port));
Console.WriteLine($"Connected to {_socket.RemoteEndPoint}");
ThreadPool.QueueUserWorkItem(ReceiveLoop!);
}
public void Run()
{
while (true)
{
string? input = Console.ReadLine();
if (string.IsNullOrEmpty(input)) break;
byte[] data = Encoding.UTF8.GetBytes(input);
_socket.Send(data);
}
}
private void ReceiveLoop(object _)
{
try
{
while (true)
{
int bytes = _socket.Receive(_buffer);
if (bytes == 0) break;
Console.WriteLine("サーバーから: " + Encoding.UTF8.GetString(_buffer, 0, bytes));
}
}
catch (SocketException ex)
{
Console { }
}
}
public void Dispose()
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Dispose();
}
}
実行方法
- サーバーアプリを先に起動し、指定ポートで Listen を開始。
- クライアントアプリを起動し、サーバーの IP:Port に接続。
- コンソールに文字列を入力するとサーバーに送信され、サーバーは受信内容をコンソールに出力して応答を返す。
まとめ
TcpClient/TcpListenerは内部でSocketをラップしており、コードが短く直感的。Socketクラスを直接使えば、非同期 I/O や独自プロトコルの実装がしやすい。- いずれも
usingやDisposeでリソースを確実に解放すること。