C#でシンプルなTCP通信を実装する:TcpClient/TcpListenerとSocketクラスの使い方

概要

.NET Framework / .NET 6 以降でも使える、C# による TCP 通信の最小構成を 2 パターン紹介します。

  1. 高レベル API である TcpClient / TcpListener を使った方法
  2. 低レベル 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();
    }
}

実行方法

  1. サーバーアプリを先に起動し、指定ポートで Listen を開始。
  2. クライアントアプリを起動し、サーバーの IP:Port に接続。
  3. コンソールに文字列を入力するとサーバーに送信され、サーバーは受信内容をコンソールに出力して応答を返す。

まとめ

  • TcpClient/TcpListener は内部で Socket をラップしており、コードが短く直感的。
  • Socket クラスを直接使えば、非同期 I/O や独自プロトコルの実装がしやすい。
  • いずれも usingDispose でリソースを確実に解放すること。

タグ: C# TcpClient TcpListener socket TCP/IP

6月22日 20:00 投稿