Adsense_top

2009年5月2日土曜日

C# 超簡易パケット(メール)中継・ウォッチプログラム

ちょっとしたプログラムを作ってみました。
今、メール機能を含むソフトを考えていますので、そのソフトのテストに使えるのではと思い作りました。

機能的には、PC上のクライアントプログラムとサーバ間に流れるパケットデータを覗き見することができます。覗き見と言うと隠れて密かに見るようですが、そこまでの機能はありません。
どちらかと言うとパケットデータを中継し、パケットの内容を表示する感じのイメージです。
クライアントプログラムの接続先として当プログラムを設定し、当プログラムから実際の接続先へ接続します。反対にサーバーからの返答も当プログラムが一旦受信してからクライアントプログラムに送信します。

とりあえず簡単なプログラムなので、実際に動かしてみてください。

まずは、ウインドウのデザインです。

・主な規定値から変更したプロパティ
 portNumericUpDown
            Minimum : 0
            Maximum : 65535
 stopButton
            Enabled : false
 messageTextBox
            Anchor : Top, Bottom, Left, Right

まず参照(using ディレクティブ)部です。
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;

次は変数宣言部です。
// スレッド停止通知用フラグ
bool stop = true;
// プリフィックス[inner]がクライアント方向用
// プリフィックス[outer]がサーバー方向用

// クライアント接続
TcpClient innerClient = null;
TcpClient outerClient = null;
// リスナー設定
TcpListener innerListener = null;
// 読み書き用ストリーム
NetworkStream innerStream = null;
NetworkStream outerStream = null;
// スレッド
Thread innerThread = null;
Thread outerThread = null;
// スレッド間でTextBoxへの文字列出力のためのデリゲート
delegate void SetTextCallback(string text);
// 接続先ホスト名を保持する
string host;
// 接続先ポート番号を保持する
int port;
コメントとして「クライアント方向」「サーバー方向」と書いていますが、クライアント方向は、OutlookなどのPC内のソフトとの送受信を担当するという意味です。反対にサーバー方向はメールサーバーなどのインターネット側、つまりPCの外側を担当します。

コンストラクタでは、今回は特に何もしていないので飛ばしますが、必要に応じてホスト名やポート番号の初期値をセットする部分を入れてください。
コントロールイベント部です。
//
// 接続監視を始める
//
private void startButton_Click(object sender, EventArgs e)
{
stop = false;
host = hostTextBox.Text;
port = (int)portNumericUpDown.Value;
stopButton.Enabled =
!(startButton.Enabled = hostTextBox.Enabled =
portNumericUpDown.Enabled = false);
innerListener = new TcpListener(IPAddress.Any, port);
// クライント方向のスレッドを開始
innerThread = new Thread(new ThreadStart(Inner));
innerThread.Start();
}

//
// 接続監視および中継を中止する
//
private void stopButton_Click(object sender, EventArgs e)
{
stop = true;

stopButton.Enabled =
!(startButton.Enabled = hostTextBox.Enabled =
portNumericUpDown.Enabled = true);
Abort();
SetText("--待機中止--");

}
//
// 接続待ち状態であれば、スレッドを止めてかウインドウを閉じる
//
private void Form1_FormClosing(object sender,
FormClosingEventArgs e)
{
if (!stop) Abort();
}
「startButton_Click」で接続待ち状態に入り受信内容を読み取る「Inner」メソッドを別スレッドとして開始します。「stopButton_Click」では、処理を行っているスレッドを終了させます。「Form1_FormClosing」は、フォームを閉じる前に、別スレッドの処理が完了しているか確認を行っています。
次は、このプログラムの中心機能部分である、データの送受信部分です。
//
// クライアント方向に対する処理
//
private void Inner()
{
try
{
// 接続待機
innerListener.Start();
SetText("--接続待機中--");
innerClient = innerListener.AcceptTcpClient();
// 接続されたら、サーバー方向接続を開始
StartOuter();
//接続
SetText("--接続されました--");
innerStream = innerClient.GetStream();

Byte[] bytes = new Byte[1024];
int i;
// 停止フラグがtrueに変更されたら処理を抜ける
while (!stop)
{
// Console.WriteLine(innerClient.Connected);
try
{
if ((i = innerStream.Read(bytes, 0, bytes.Length)) != 0)
{
// ホストにデータを流す
outerStream.Write(bytes, 0, i);
// TextBoxに中継したデータを追加する
String data =
System.Text.Encoding.UTF8.GetString(bytes, 0, i);
SetText(String.Format("C: {0}", data));
}
}
catch(IOException)
{
break;
}
}
// 閉じる
innerStream.Close();
innerClient.Close();
stop = true;
}
catch (ThreadAbortException)
{
// 何もしない
;
}
catch (SocketException)
{
// 何もしない
;
}
// 再度接続待ちをはじめる
ReStart();
}

//
// ホスト側処理のスレッドを実行する
//
private void StartOuter()
{
outerThread = new Thread(new ThreadStart(Outer));
outerThread.Name = "OUTER";
outerThread.Start();
}

//
// ホスト方向に対する処理
//
private void Outer()
{
try
{
outerClient = new TcpClient();
outerClient.Connect(host, port);
outerStream = outerClient.GetStream();
SetText("--ホストに接続しました--");
Byte[] bytes = new Byte[1024];
int i;

//メッセージを受信
while (!stop)//!stop && outerStream.CanRead)
{
if ((i = outerStream.Read(bytes, 0, bytes.Length)) != 0)
{
// クラインとデータを流す
innerStream.Write(bytes, 0, i);
// TextBoxに中継したデータを追加する
String data =
System.Text.Encoding.UTF8.GetString(bytes, 0, i);
SetText(String.Format("S: {0}", data));
}
else
{
break;
}
}
// 閉じる
outerStream.Close();
outerClient.Close();
stop = true;
}
catch (ThreadAbortException)
{
// 何もしない
;
}
catch (SocketException)
{
// 何もしない
;
}
}
「Inner」では、クライントプログラムからの接続を待ち、接続要求を受け付けた後は、サーバー側の接続を開始し、クライアント側の受信ストリームからデータを読み込み、サーバー側にデータを流し同時にフォームのTextBoxに、データを文字列に変換し追加しています。「StartOuter」でサーバー側の接続を受け持つ「Outer」メソッドを別スレッド開始います。「Outer」ではサーバー側に接続し、サーバー側の返答を受信しクライアントにデータを流し同時にフォームのTextBoxに、データを文字列に変換し追加しています。
残りのメソッドです。
//
// 再度接続待ちに入る
//
private void ReStart()
{
stop = false;
SetText("再スタート");
innerThread = new Thread(new ThreadStart(Inner));
innerThread.Start();
}

//
// TextBoxに表示する
//
private void SetText(string text)
{
if (this.messageTextBox.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.messageTextBox.AppendText(text + "\r\n");
}
}

//
// 通信用スレッドを終了させる
//
private void Abort()
{
if (outerThread != null && outerThread.IsAlive)
{
outerThread.Abort();
outerThread.Join();
}
innerListener.Stop();
if (innerThread.IsAlive)
{
innerThread.Abort();
innerThread.Join();
}
// 念のため
if (outerStream != null) outerStream.Close();
if (outerClient != null) outerClient.Close();
if (innerStream != null) innerStream.Close();
if (innerClient != null) innerClient.Close();
}
「ReStart」は、送受信完了後の再接続待ちに入るためのメソッドです。「SetText」は、送受信を行っている別スレッドからデリゲートを介して、フォーム上のTextBoxに文字列を追加するものです。「Abort」は、強制的に送受信を行っている別スレッドを止めるためのメソッドです。
以下の図は、Outlookで送信メールサーバーを「127.0.0.1」に設定し、今回作成したしたプログラムのホスト欄に実際のサーバー名を入力し、Outlookの「アカウント設定のテスト」を行った時の実際の送信内容を中継しているところです。



0 件のコメント:

コメントを投稿