Socketは、オペレーティングシステムが提供するネットワーク通信の基本的な抽象化インターフェースです。Unix系OSではファイル記述子として扱われるこの機構は、Windowsおよび.NET環境でも同様に、ネットワークI/Oをカプセル化した柔軟なアクセス手段として機能します。C#ではSystem.Net.Sockets名前空間を通じて、IPアドレス、ポート、プロトコルなどのネットワーク要素を直接制御可能なSocketクラスが提供されます。
通信モデルの違いを理解する
TCP(Transmission Control Protocol)は接続指向型で、信頼性・順序保証・フロー制御を内包します。これに対しUDP(User Datagram Protocol)は接続不要・軽量・非信頼型であり、アプリケーション側で再送やタイムアウト処理を実装する必要があります。選択は用途に応じて行うべきで、リアルタイム音声・ゲーム状態同期などではUDPの低遅延特性が優位ですが、ファイル転送やHTTP通信にはTCPが適しています。
基本的な通信フロー
- TCPサーバー:ソケット作成 → ローカルエンドポイントへのバインド →
Listen()による接続待機 →Accept()でクライアントとの接続確立 → 双方向データ交換 → クローズ - TCPクライアント:ソケット作成 →
Connect()でサーバーへ接続要求 → 接続確立後の双方向通信 → クローズ - UDPエンドポイント:ソケット作成 → バインド(受信側)または未バインド(送信側)→
SendTo()/ReceiveFrom()で相手のアドレスを明示的に指定して通信
実装例:TCPサーバー(非同期対応設計)
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
public class TcpEchoServer
{
private readonly Socket _listener;
private readonly int _port = 8080;
public TcpEchoServer()
{
_listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
var localEndpoint = new IPEndPoint(IPAddress.Any, _port);
_listener.Bind(localEndpoint);
_listener.Listen(5);
Console.WriteLine($"TCP Echo Server started on port {_port}");
}
public async Task StartAsync()
{
while (true)
{
try
{
var clientSocket = await AcceptClientAsync();
_ = HandleClientAsync(clientSocket); // デタッチされたタスク
}
catch (ObjectDisposedException) { break; }
catch (Exception ex) { Console.WriteLine($"Accept error: {ex.Message}"); }
}
}
private async Task<Socket> AcceptClientAsync()
{
var acceptTask = Task.Factory.FromAsync<Socket>(
_listener.BeginAccept, _listener.EndAccept, null);
return await acceptTask;
}
private async Task HandleClientAsync(Socket client)
{
var remoteEp = client.RemoteEndPoint;
Console.WriteLine($"Client connected: {remoteEp}");
try
{
var welcomeMsg = "Echo server ready. Send messages (empty to exit).";
await SendAsync(client, welcomeMsg);
var buffer = new byte[1024];
while (true)
{
var received = await ReceiveAsync(client, buffer);
if (received == 0) break;
var message = Encoding.UTF8.GetString(buffer, 0, received).Trim();
Console.WriteLine($"Received from {remoteEp}: \"{message}\"");
if (string.IsNullOrEmpty(message)) break;
await SendAsync(client, $"Echo: {message}");
}
}
catch (Exception ex)
{
Console.WriteLine($"Client {remoteEp} error: {ex.Message}");
}
finally
{
client.Shutdown(SocketShutdown.Both);
client.Close();
Console.WriteLine($"Client {remoteEp} disconnected");
}
}
private async Task SendAsync(Socket socket, string data)
{
var bytes = Encoding.UTF8.GetBytes(data + "\n");
await Task.Factory.FromAsync(
(cb, s) => socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, cb, s),
socket.EndSend, null);
}
private async Task<int> ReceiveAsync(Socket socket, byte[] buffer)
{
return await Task.Factory.FromAsync<int>(
(cb, s) => socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, cb, s),
socket.EndReceive, null);
}
}実装例:TCPクライアント(簡易インタラクティブ版)
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
public class TcpEchoClient
{
private readonly string _host;
private readonly int _port;
private Socket? _socket;
public TcpEchoClient(string host = "127.0.0.1", int port = 8080)
{
_host = host;
_port = port;
}
public async Task RunAsync()
{
try
{
var ipHost = await Dns.GetHostAddressesAsync(_host);
var endpoint = new IPEndPoint(ipHost[0], _port);
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
await ConnectAsync(endpoint);
Console.WriteLine("Connected. Type messages (or 'quit' to exit):");
while (true)
{
Console.Write("> ");
var input = Console.ReadLine();
if (input?.ToLower() == "quit") break;
if (!string.IsNullOrWhiteSpace(input))
{
await SendAsync(input);
var response = await ReceiveAsync();
Console.WriteLine($"← {response}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Connection failed: {ex.Message}");
}
finally
{
_socket?.Shutdown(SocketShutdown.Both);
_socket?.Close();
}
}
private async Task ConnectAsync(IPEndPoint endpoint)
{
await Task.Factory.FromAsync(
(cb, s) => _socket!.BeginConnect(endpoint, cb, s),
_socket!.EndConnect, null);
}
private async Task SendAsync(string message)
{
var data = Encoding.UTF8.GetBytes(message + "\n");
await Task.Factory.FromAsync(
(cb, s) => _socket!.BeginSend(data, 0, data.Length, SocketFlags.None, cb, s),
_socket!.EndSend, null);
}
private async Task<string> ReceiveAsync()
{
var buffer = new byte[1024];
var len = await Task.Factory.FromAsync<int>(
(cb, s) => _socket!.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, cb, s),
_socket!.EndReceive, null);
return Encoding.UTF8.GetString(buffer, 0, len).Trim();
}
}UDP双方向通信の実装(シングルスレッド方式)
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
public class UdpEchoNode
{
private readonly Socket _socket;
private readonly IPEndPoint _localEndpoint;
private EndPoint _remoteEndpoint;
public UdpEchoNode(int port = 9050)
{
_localEndpoint = new IPEndPoint(IPAddress.Any, port);
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
_socket.Bind(_localEndpoint);
_remoteEndpoint = new IPEndPoint(IPAddress.Any, 0);
Console.WriteLine($"UDP node listening on {_localEndpoint}");
}
public async Task StartAsync()
{
// 初期挨拶受信
var buffer = new byte[1024];
var recv = await ReceiveFromAsync(buffer);
Console.WriteLine($"First message from {_remoteEndpoint}: {Encoding.UTF8.GetString(buffer, 0, recv)}");
// 応答送信
var reply = "Hello! UDP echo node is active.";
await SendToAsync(Encoding.UTF8.GetBytes(reply));
// メッセージループ
while (true)
{
recv = await ReceiveFromAsync(buffer);
var msg = Encoding.UTF8.GetString(buffer, 0, recv).Trim();
Console.WriteLine($"Received: \"{msg}\"");
if (msg.Equals("quit", StringComparison.OrdinalIgnoreCase)) break;
await SendToAsync(Encoding.UTF8.GetBytes($"Echo: {msg}"));
}
}
private async Task<int> ReceiveFromAsync(byte[] buffer)
{
return await Task.Factory.FromAsync<int>(
(cb, s) => _socket.BeginReceiveFrom(buffer, 0, buffer.Length, SocketFlags.None, ref _remoteEndpoint, cb, s),
(ar) => _socket.EndReceiveFrom(ar, ref _remoteEndpoint), null);
}
private async Task SendToAsync(byte[] data)
{
await Task.Factory.FromAsync(
(cb, s) => _socket.BeginSendTo(data, 0, data.Length, SocketFlags.None, _remoteEndpoint, cb, s),
_socket.EndSendTo, null);
}
}上記の実装は、.NETの非同期I/Oパターン(APM)を用いてブロッキングを回避し、リソース効率を高めています。実際の運用では、async/awaitとSocketAsyncEventArgsを組み合わせた高性能I/Oモデルや、TcpClient/UdpClientなどの高水準ラッパークラスも検討できますが、ネットワークの本質的理解には、低レベルSocket操作の習熟が不可欠です。