動作環境
Windows 8.1 Pro (64bit)
Microsoft Visual Studio 2017 Community
Sublime Text 2
Visual Studio | WPF > TCPサーバ > echo server > 1対1通信 >Thread処理 | link: 自分のIPアドレスを取得する
上記では一つのクライアント接続時、別のクライアント応答ができなかった。
以下を見つけた。
how do i get TcpListener to accept multiple connections and work with each one individually?
answered Mar 17 '11 at 13:46
Oleg Tarasov
ThreadPool.QueueUserWorkItem()を使った例が紹介されている。
code v0.1
実装してみた。
MainWindow.xaml
<Window x:Class="_171204_t1710_TCPechoServer.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_171204_t1710_TCPechoServer"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Name="B_connect" Content="Connect" Height="30" Click="B_connect_Click"/>
    </Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// 以下を追加した
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace _171204_t1710_TCPechoServer
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        private Thread rcvThr;
        TcpListener tcpListener;
        private readonly int kPort_recv = 7000;
        private bool stopThr = false;
        public MainWindow()
        {
            InitializeComponent();
        }
        private void B_connect_Click(object sender, RoutedEventArgs e)
        {
            this.Title = "Connected";
            tcpListener = new TcpListener(IPAddress.Any, kPort_recv);
            tcpListener.Start();
            rcvThr = new Thread(new ThreadStart(FuncRcvData));
            rcvThr.Start();
        }
        private void FuncRcvData()
        {
            while (stopThr == false)
            {
                TcpClient client = tcpListener.AcceptTcpClient();
                ThreadPool.QueueUserWorkItem(ThreadProc, client);
            }
        }
        private static void ThreadProc(object obj)
        {
            var client = (TcpClient)obj;
            if (client.Connected == false)
            {
                return;
            }
            using (var netStream = client.GetStream())
            {
                using (var sReader = new StreamReader(netStream, Encoding.UTF8))
                {
                    var sWriter = new StreamWriter(netStream, Encoding.UTF8);
                    do
                    {
                        string str = sReader.ReadLine();
                        if (str == null)
                        {
                            break;
                        }
                        sWriter.WriteLine(str);
                        sWriter.Flush();
                        //
                        Console.WriteLine(str);
                    } while (true);
                    sWriter.Close();
                }
            }
            client.Close();
        }
    }
}
複数のクライアントから同時接続されても応答できるようになった。
関連
ThreadPool.QueueUserWorkItem メソッド
code v0.2
- 受信処理でtry, catchを追加した。
- これがない場合、処理によっては
string str = sReader.ReadLine();の部分で「xxxは動作を停止しました」になる。 
 - これがない場合、処理によっては
 - スレッドが動いている場合の終了処理を追加
- これをしないとListenボタン押下後に終了すると
tcpListener.AcceptTcpClient();で処理が止まり、ソフトがゾンビ化する - Window_ClosingをXAMLで追加
 - Window_Closingを実装
 - tcpListerner.Stop()で処理を停止
 
 - これをしないとListenボタン押下後に終了すると
 
MainWindow.xaml
<Window x:Class="_171205_t0900_TCPechoBackUI.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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:_171205_t0900_TCPechoBackUI"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525"
        Closing="Window_Closing">
    <Grid>
        <StackPanel>
            <Button Name="B_listen" Content="Listen" Height="30" Click="B_listen_Click"/>
            <TextBox Name="T_recv" Height="300" VerticalScrollBarVisibility="Visible"
                     Text="{Binding RecvString}"/>
        </StackPanel>
    </Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// 以下を追加した
using System.ComponentModel;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace _171205_t0900_TCPechoBackUI
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private string _recvString = string.Empty;
        private Thread rcvThr;
        TcpListener tcpListener = null;
        private readonly int kPort_recv = 7000;
        private bool stopThr = false;
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;
        }
        public string RecvString
        {
            get { return _recvString; }
            set
            {
                _recvString = value;
                OnPropertyChanged("RecvString");
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName = null)
        {
            if (PropertyChanged != null)
                PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        private void B_listen_Click(object sender, RoutedEventArgs e)
        {
            if (tcpListener != null)
            {
                return; // error
            }
            this.Title = "Connected";
            tcpListener = new TcpListener(IPAddress.Any, kPort_recv);
            tcpListener.Start();
            rcvThr = new Thread(new ThreadStart(FuncRcvData));
            rcvThr.Start();
        }
        private void FuncRcvData()
        {
            while (stopThr == false)
            {
                try
                {
                    TcpClient client = tcpListener.AcceptTcpClient();
                    ThreadPool.QueueUserWorkItem(ThreadProc, client);
                }
                catch(Exception err)
                {
                }
            }
        }
        private void ThreadProc(object obj)
        {
            var client = (TcpClient)obj;
            if(client.Connected == false)
            {
                return;
            }
            using (var netStream = client.GetStream())
            {
                using (var sReader = new StreamReader(netStream, Encoding.ASCII))
                {
                    var sWriter = new StreamWriter(netStream, Encoding.ASCII);
                    while(stopThr == false)
                    {
                        try
                        {
                            Console.WriteLine("read start");
                            string str = sReader.ReadLine();
                            Console.WriteLine("read end");
                            if (str == null)
                            {
                                break;
                            }
                            sWriter.WriteLine(str);
                            sWriter.Flush();
                            string log = RecvString;
                            if (log.Length > 0)
                            {
                                log += System.Environment.NewLine;
                            }
                            RecvString = log + str;
                        }
                        catch (Exception err)
                        {
                            break;
                        }
                    }
                    sWriter.Close();
                }
            }
            client.Close();
        }
        private void Window_Closing(object sender, CancelEventArgs e)
        {
            if (tcpListener != null)
            {
                tcpListener.Stop();
            }
            stopThr = true;
        }
    }
}
エラー処理はもう少し勉強が必要だろう。