質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

TCP

TCP(Transmission Control Protocol)とは、トランスポート層のプロトコルで、コネクション型のデータサービスです。

Q&A

解決済

2回答

1310閲覧

TCP接続を行う際、ポート番号を変換するプログラムを作りたい

rarirurero3421

総合スコア1

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

TCP

TCP(Transmission Control Protocol)とは、トランスポート層のプロトコルで、コネクション型のデータサービスです。

0グッド

0クリップ

投稿2022/07/16 12:14

前提

Azure上の仮想マシン(Windows)にリモートデスクトップ接続することを考えています。
この時に、詳しくは説明できないのですが、ちょっと理由があって、ポート番号を変換して接続することを検討しています。

以下の方式で接続しようと思っています。

【Microsoft リモートデスクトップ (mstsc.exe)】

|127.0.0.1:13389に接続。

【今から開発するプログラム】

|Azure VM:3389に接続。

【Azure VM】

【今から開発するプログラム】は、C#での開発を考えています。
TcpListenerクラスで、mstsc.exeから送信されデータを全て受け取り、
受け取ったデータをTcpClientクラスで、Azure VMに送信するという方針で作ろうと思いました。

質問内容

上記のような方針で作ってみたのですが、全くRDP接続できなくて困っています。
そもそもの方針が間違っている可能性も含めて、どうしたらいいか考えています。

どのようにすれば、上記のようなプログラムを作ることができるでしょうか?
C#/Windowsという条件は変えることができません。

発生している問題・エラーメッセージ

RDPが発するエラーメッセージは以下の通りです。

内部エラーが発生しました。

また、後述のプログラムで出力したデバッグメッセージは以下の通りです。

*** リモートデスクトップ(mstsc.exe)から、19バイトのデータを読み取りました。 *** 19バイトのデータを送信。(本プログラム -> サーバー) *** クライアント -> 本プログラム -> サーバーへのデータ送信完了 *** サーバーから、19バイトのデータを読み取りました。 *** 19バイトのデータをクライアント(mstsc.exe)に送信しました。 *** サーバー -> 本プログラム -> クライアントへのデータ送信完了 *** リモートデスクトップ(mstsc.exe)から、47バイトのデータを読み取りました。 *** 47バイトのデータを送信。(本プログラム -> サーバー) *** クライアント -> 本プログラム -> サーバーへのデータ送信完了 *** サーバーから、19バイトのデータを読み取りました。 *** 19バイトのデータをクライアント(mstsc.exe)に送信しました。 *** サーバー -> 本プログラム -> クライアントへのデータ送信完了 *** リモートデスクトップ(mstsc.exe)から、47バイトのデータを読み取りました。 *** 47バイトのデータを送信。(本プログラム -> サーバー) *** クライアント -> 本プログラム -> サーバーへのデータ送信完了 *** サーバーから、19バイトのデータを読み取りました。 *** 19バイトのデータをクライアント(mstsc.exe)に送信しました。 *** サーバー -> 本プログラム -> クライアントへのデータ送信完了

該当のソースコード

C#

1using System; 2using System.Collections.Generic; 3using System.Diagnostics; 4using System.Linq; 5using System.Net; 6using System.Net.Sockets; 7using System.Text; 8using System.Threading.Tasks; 9using System.Windows; 10using System.Windows.Controls; 11using System.Windows.Data; 12using System.Windows.Documents; 13using System.Windows.Input; 14using System.Windows.Media; 15using System.Windows.Media.Imaging; 16using System.Windows.Navigation; 17using System.Windows.Shapes; 18 19namespace WpfApp1 20{ 21 /// <summary> 22 /// MainWindow.xaml の相互作用ロジック 23 /// </summary> 24 public partial class MainWindow : Window 25 { 26 private TcpListener tcpListener; 27 private const int rdpPortNumber = 3389; 28 private const int localRdpPortNumber = 13389; 29 private const string virtualMachine = "<< Azure 仮想マシンのIPアドレス >>"; 30 31 public MainWindow() 32 { 33 InitializeComponent(); 34 } 35 36 private void Window_Loaded(object sender, RoutedEventArgs e) 37 { 38 if (tcpListener != null) 39 { 40 return; 41 } 42 43 tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), localRdpPortNumber); 44 tcpListener.Start(); 45 46 tcpListener.BeginAcceptTcpClient(TcpClientAccepted, null); 47 } 48 49 private void TcpClientAccepted(IAsyncResult result) 50 { 51 // client : mstsc.exe 52 // bridge : this program. 53 // server : azure virtual machine. 54 using (var clientToBridge = tcpListener.EndAcceptTcpClient(result)) 55 { 56 tcpListener.BeginAcceptTcpClient(TcpClientAccepted, null); 57 58 if (clientToBridge == null) 59 { 60 Debug.WriteLine(" *** clientToBridge == null"); 61 return; 62 } 63 64 using (var clientToBridgeStream = clientToBridge.GetStream()) 65 { 66 using (var bridgeToServer = new TcpClient()) 67 { 68 bridgeToServer.Connect(IPAddress.Parse(virtualMachine), rdpPortNumber); 69 using (var bridgeToServerStream = bridgeToServer.GetStream()) 70 { 71 byte[] buffer = new byte[10240]; 72 // client -> this program -> server という経路でデータを送信する。 73 while (true) 74 { 75 // クライアントプログラムからのデータを読み取る。 76 var length = clientToBridgeStream.Read(buffer, 0, buffer.Length); 77 Debug.WriteLine($" *** リモートデスクトップ(mstsc.exe)から、{length}バイトのデータを読み取りました。"); 78 if (length > 0) 79 { 80 // 本プログラムから Azure Virtual Machine にデータを送信する。 81 bridgeToServerStream.Write(buffer, 0, length); 82 Debug.WriteLine($" *** {length}バイトのデータを送信。(本プログラム -> サーバー)"); 83 } 84 85 if (length < buffer.Length) 86 { 87 Debug.WriteLine($" *** クライアント -> 本プログラム -> サーバーへのデータ送信完了"); 88 break; 89 } 90 } 91 92 // server -> bridge -> client という経路でデータを送信する。 93 while (true) 94 { 95 // サーバーから、本プログラムへのデータを読み取る。 96 var length = bridgeToServerStream.Read(buffer, 0, buffer.Length); 97 Debug.WriteLine($" *** サーバーから、{length}バイトのデータを読み取りました。"); 98 if (length > 0) 99 { 100 clientToBridgeStream.Write(buffer, 0, length); 101 Debug.WriteLine($" *** {length}バイトのデータをクライアント(mstsc.exe)に送信しました。"); 102 } 103 104 if (length < buffer.Length) 105 { 106 Debug.WriteLine($" *** サーバー -> 本プログラム -> クライアントへのデータ送信完了"); 107 break; 108 } 109 } 110 } 111 } 112 } 113 } 114 } 115 } 116} 117

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KOZ6.0

2022/07/16 20:05 編集

ご提示のコードでは一回受送信して終わりに見えますが、どちらかから切断されるまで受送信を繰り返す必要があるはずです。
guest

回答2

0

次のように作ってみてください。

■ 初期処理

(0) TcpListener のインスタンスを作成、開始(Start)を行い、クライアント接続待ちの非同期操作を開始(BeginAcceptTcpClient)。

■ 接続処理 (BeginAcceptTcpClient のコールバック関数内)

(1) RDP クライアントからの接続を受け付け (EndAcceptTcpClient) 、次のクライアント接続待ちの非同期操作を開始(BeginAcceptTcpClient)
(2) RDP サーバーに接続する (TCPClient のインスタンスを作成し、Connect)
(3) RDP クライアントからの非同期受信開始(NetworkStream.BeginRead)
(4) RDP サーバーからの非同期受信開始(NetworkStream.BeginRead)

■ 通信処理 (BeginRead のコールバック関数内)

(5) RDP クライアントから受信したら RDP サーバーに送信
(6) RDP サーバーから受信したら RDP クライアントに送信

(5),(6) は独立して動く必要があります。
(非同期受信のメソッドを使えば自然にそうなります。送信は同期でOK)
EndRead → 受信データを送信バッファにコピー → 次の非同期受信開始(BeginRead) → 同期送信(Write)

■ 切断処理 (BeginRead のコールバック関数内)

相手側から切断されると、受信がゼロバイトで完了します、
その場合は反対側の Socket オブジェクトを Shutdown(SocketShutdown.Send) します。
Socket オブジェクトは TCPClient.Client プロパティで取り出せます。

(7) (5) でRDP クライアントからゼロバイト受信したら RDP サーバー側を Shutdown。
(8) (6) でRDP サーバーからゼロバイト受信したら RDP クライアント側を Shutdown。
(9) 両方でゼロバイト受信が起きたら 両方の TCPClient を閉じて終了です。

投稿2022/07/17 05:02

編集2022/07/17 18:32
KOZ6.0

総合スコア2626

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

KOZ6.0

2022/07/17 05:04

WinGate を入れてプロキシ経由で通信する方法もありかなと思います。 「WinGate」 https://www.wingate.jp/
rarirurero3421

2022/07/17 11:08

ありがとうございます。 今から作ってみて試してみます。 分からないことがあれば、再度質問するかもしれません。
rarirurero3421

2022/07/17 11:42

> (3) RDP クライアントからの非同期受信開始 > (4) RDP サーバーからの非同期受信開始 > 重ねて、ご回答ありがとうございます。 (3)は分かるのですが、(4)が分からず、どうしたらいいか確認中です。 > (3) RDP クライアントからの非同期受信開始 > これは以下のコードで問題ないでしょうか? tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), 13389); tcpListener.Start(); tcpListener.BeginAcceptTcpClient(TcpClientAccepted, null); 注意) tcpListenerはメンバ変数です。 注意) TcpClientAcceptedメソッドは『(5) RDP クライアントから受信したら RDP サーバーに送信』を行っています。 > (4) RDP サーバーからの非同期受信開始 > こちらは、どうしたらよいでしょうか? RDPサーバーは、接続先の仮想マシンなので、そのIPアドレスは当然分かるとして・・・ コードは↑と同じように↓のコードになるのでしょうか? tcpListener = new TcpListener(IPAddress.Parse("<< Azure VMのIPアドレス >>"), 3389); tcpListener.Start(); tcpListener.BeginAcceptTcpClient(TcpClientAccepted, null);
KOZ6.0

2022/07/17 12:07

(3) RDP クライアントからの非同期受信開始 (1) で接続を受ける(EndAcceptTcpClient) と TcpClient が取り出せますね。 その TcpClient を使って非同期読み込み開始です。 具体的には GetStream メソッドで NetworkStream を取り出して BeginRead です。 (4) RDP サーバーからの非同期受信開始 (2) で TCPClient を使って RDP サーバーに接続します。 (3) と同じように GetStream メソッドで NetworkStream を取り出して BeginRead です。
KOZ6.0

2022/07/17 12:43

「TcpListener.BeginAcceptTcpClient(AsyncCallback, Object) Method」 https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.tcplistener.beginaccepttcpclient?view=netframework-4.8 「TcpListener.EndAcceptTcpClient(IAsyncResult) Method」 https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.tcplistener.endaccepttcpclient?view=netframework-4.8 「NetworkStream.BeginRead(Byte[], Int32, Int32, AsyncCallback, Object) Method」 https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.networkstream.beginread?view=netframework-4.8 「NetworkStream.EndRead(IAsyncResult) Method」 https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.networkstream.endread?view=netframework-4.8 日本語のページは翻訳の精度があまりよくないので英文のページを紹介しています。 この辺に目をとおしてください。
guest

0

ベストアンサー

サンプルを書いてみました。何かあっても自己責任でお願いします。

C#

1namespace ConsoleApp8 2{ 3 using System; 4 using System.Collections.Generic; 5 using System.IO; 6 using System.Net; 7 using System.Net.Sockets; 8 using System.Threading; 9 using System.ComponentModel; 10 11 class Program 12 { 13 private const int rdpPortNumber = 3389; 14 private const int localRdpPortNumber = 13389; 15 private const string virtualMachine = "<< Azure 仮想マシンのIPアドレス >>"; 16 17 static void Main(string[] args) { 18 TcpBridge breidge = new TcpBridge(IPAddress.Any, localRdpPortNumber, 19 IPAddress.Parse(virtualMachine), rdpPortNumber); 20 Console.WriteLine("breidge.Start()"); 21 breidge.Start(); 22 Console.ReadKey(); 23 Console.WriteLine("breidge.Stop(true)"); 24 // 強制切断 25 breidge.Stop(true); 26 27 Console.WriteLine("breidge.Start()"); 28 breidge.Start(); 29 Console.ReadKey(); 30 Console.WriteLine("breidge.Stop(false)"); 31 // 全てのクライアントが切断するまで待機 32 breidge.Stop(false); 33 34 Console.WriteLine("Enter キーを押すと終了します。"); 35 Console.ReadLine(); 36 } 37 } 38 39 class TcpBridge 40 { 41 private readonly AsyncCallback OnAcceptTcpClientCallback; 42 private readonly IPAddress serverAddress; 43 private readonly int serverPort; 44 private readonly ConnectionCollection connections = new ConnectionCollection(); 45 private readonly TcpListener listener; 46 47 public bool IsStoped { get; private set; } = true; 48 49 public TcpBridge(IPAddress listenAddress, int listenPort, 50 IPAddress serverAddress, int serverPort) { 51 this.serverAddress = serverAddress; 52 this.serverPort = serverPort; 53 OnAcceptTcpClientCallback = new AsyncCallback(OnAcceptTcpClient); 54 listener = new TcpListener(listenAddress, listenPort); 55 } 56 57 public void Start() { 58 if (!IsStoped) { 59 throw new InvalidOperationException("既に開始しています。"); 60 } 61 IsStoped = false; 62 try { 63 listener.Start(); 64 listener.BeginAcceptTcpClient(OnAcceptTcpClientCallback, null); 65 } catch { 66 IsStoped = true; 67 throw; 68 } 69 } 70 71 public void Stop() { 72 Stop(false); 73 } 74 75 public void Stop(bool force) { 76 if (IsStoped) { 77 throw new InvalidOperationException("既に終了しています。"); 78 } 79 IsStoped = true; 80 connections.WaitAll(force); 81 listener.Stop(); 82 } 83 84 private void OnAcceptTcpClient(IAsyncResult ar) { 85 TcpClient client; 86 try { 87 client = listener.EndAcceptTcpClient(ar); 88 if (IsStoped) { 89 client.Close(); 90 return; 91 } 92 } catch (IOException) { 93 return; 94 } catch (ObjectDisposedException) { 95 return; 96 } 97 Console.WriteLine("Connect From {0}", client.Client.RemoteEndPoint); 98 listener.BeginAcceptTcpClient(OnAcceptTcpClientCallback, null); 99 100 try { 101 TcpClient server = new TcpClient(); 102 server.Connect(serverAddress, serverPort); 103 Console.WriteLine("Connect To {0}", server.Client.RemoteEndPoint); 104 Connection connection = new Connection(client, server); 105 connections.Add(connection); 106 107 } catch (SocketException) { 108 client.Close(); 109 } 110 } 111 } 112 113 class Connection : IDisposable 114 { 115 private readonly TcpClient client; 116 private readonly TcpClient server; 117 118 private readonly Forwarder clientToServer; 119 private readonly Forwarder serverToClient; 120 121 public event EventHandler CloseEvent; 122 123 public Connection(TcpClient client, TcpClient server) { 124 this.client = client; 125 this.server = server; 126 127 clientToServer = new Forwarder(client, server); 128 serverToClient = new Forwarder(server, client); 129 130 clientToServer.EndOfStream += EndOfStreamEvent; 131 serverToClient.EndOfStream += EndOfStreamEvent; 132 } 133 134 private void EndOfStreamEvent(object sender, EventArgs e) { 135 if (clientToServer.AtEndOfStream && serverToClient.AtEndOfStream) { 136 Close(); 137 } 138 } 139 140 public void Close() { 141 Dispose(); 142 } 143 144 protected virtual void OnCloseEvent(EventArgs e) { 145 CloseEvent?.Invoke(this, e); 146 } 147 148 private bool disposedValue; 149 150 protected virtual void Dispose(bool disposing) { 151 if (!disposedValue) { 152 disposedValue = true; 153 if (disposing) { 154 Console.WriteLine("Close {0}", client.Client.RemoteEndPoint); 155 Console.WriteLine("Close {0}", server.Client.RemoteEndPoint); 156 client.Close(); 157 server.Close(); 158 OnCloseEvent(EventArgs.Empty); 159 } 160 } 161 } 162 163 public void Dispose() { 164 Dispose(disposing: true); 165 GC.SuppressFinalize(this); 166 } 167 } 168 169 class ConnectionCollection 170 { 171 private readonly ManualResetEvent resetEvent = new ManualResetEvent(true); 172 private readonly List<Connection> list = new List<Connection>(); 173 174 public void Add(Connection connection) { 175 resetEvent.Reset(); 176 lock (this) { 177 list.Add(connection); 178 } 179 connection.CloseEvent += Connection_CloseEvent; 180 } 181 182 public int Count { 183 get { 184 return list.Count; 185 } 186 } 187 188 public Connection this[int index] { 189 get { 190 return list[index]; 191 } 192 } 193 194 private void Connection_CloseEvent(object sender, EventArgs e) { 195 Connection connection = (Connection)sender; 196 connection.CloseEvent -= Connection_CloseEvent; 197 lock (this) { 198 list.Remove(connection); 199 } 200 if (list.Count == 0) { 201 resetEvent.Set(); 202 } 203 } 204 205 public void WaitAll(bool force) { 206 if (force) { 207 lock (this) { 208 foreach (Connection con in list.ToArray()) { 209 con.Close(); 210 } 211 } 212 } 213 resetEvent.WaitOne(); 214 } 215 } 216 217 class Forwarder 218 { 219 private readonly NetworkStream reader; 220 private readonly NetworkStream writer; 221 private readonly Socket writerSocket; 222 private readonly byte[] readBuffer = new byte[2048]; 223 private readonly byte[] writeBuffer = new byte[2048]; 224 private readonly AsyncCallback OnReadCallback; 225 226 public event EventHandler EndOfStream; 227 public bool AtEndOfStream { get; private set; } = false; 228 229 public Forwarder(TcpClient readClient, TcpClient writeClient) { 230 reader = readClient.GetStream(); 231 writer = writeClient.GetStream(); 232 writerSocket = writeClient.Client; 233 OnReadCallback = new AsyncCallback(OnRead); 234 reader.BeginRead(readBuffer, 0, readBuffer.Length, OnReadCallback, null); 235 } 236 237 private void OnRead(IAsyncResult ar) { 238 int size = EndRead(ar); 239 if (size > 0) { 240 Array.Copy(readBuffer, writeBuffer, size); 241 reader.BeginRead(readBuffer, 0, readBuffer.Length, OnReadCallback, null); 242 if (writer.CanWrite) { 243 writer.Write(writeBuffer, 0, size); 244 } 245 } else { 246 OnEndOfStream(EventArgs.Empty); 247 } 248 } 249 250 private int EndRead(IAsyncResult ar) { 251 int size = 0; 252 try { 253 size = reader.EndRead(ar); 254 } catch (IOException) { 255 } catch (ObjectDisposedException) { 256 } 257 // size がゼロ以上なら正常 258 if (size >= 0) { 259 return size; 260 } else { 261 // ゼロ未満ならエラーが発生している 262 Win32Exception exception = new Win32Exception(); 263 Console.WriteLine(exception.ToString()); 264 return 0; 265 } 266 } 267 268 protected virtual void OnEndOfStream(EventArgs e) { 269 if (writerSocket.Connected) { 270 writerSocket.Shutdown(SocketShutdown.Send); 271 } 272 AtEndOfStream = true; 273 EndOfStream?.Invoke(this, e); 274 } 275 } 276}

投稿2022/07/18 02:16

編集2022/07/18 08:23
KOZ6.0

総合スコア2626

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

rarirurero3421

2022/07/18 05:43

ありがとうございます。とても助かります。 今からこちらを試させていただきます。
rarirurero3421

2022/07/18 06:14

ありがとうございます。 少しだけ変更する必要がありましたが、結果的に動作しました。 とっても助かりました! (294行周辺を以下のように変えました。) try { size = reader.EndRead(ar); } catch (IOException) { return 0; }
KOZ6.0

2022/07/18 07:46 編集

修正ありがとうございます。CanRead が効かないケースがあるんですね。.NET6 です? そこをそのように修正するなら ObjectDisposeException も catch する必要があると思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問