自動売買システム日経225ミニ専用をC#で作る。Webhookサーバーを作成する
「自動売買システム日経225ミニ専用をC#で作る」の概要 自動取引売買システム構成図 ③のLocal Webhook Serverを作成しngrokでlocal severを外部公開します。
ネットを検索するといくつかのWebhook Serverの紹介記事が見つかります。python Flask等 今回は「C#で作る」がテーマなのでC#用のwebSeverを探しました。ありました!
SimpleWebSever名前のとうり非常にシンプルで使いやすそうです。これを拝借しこれにTCP Sockeとを組み込み作成します。
開発環境
・ Windows 10
・ Visual studio 2022
SimpleWebServerをGithubよりダウンロードする
github soleil-taruto/Hatena 上でcodeをクリックしURLをコピーする。
自分のPCにクローンを作成する
コマンドプロンプトをオープンする。
git clone をタイプしコピーしたアドレスを貼り付け⏎
pc上の c\xxx\Hatenaを確認しあればOKです
Visual Studio 2022を開きプロジェクトを作成する
1.新しいプロジェクトの作成(N)選択⏎
2.コンソールアプリを選択し次へ(N) ⏎
3.プロジェクト名に「WebhookServer」場所(L)にプロジェクト作成ディレクトリを指定し次へ(N) ⏎
4.フレームワークを選択し作成(C) ⏎
以上でプロジェクトは作成されました。
クローンで作成したファイルからSimpleWebServerをプロジェクトに追加する
ソリューションエクスプローラーウインドウでWebhookServerプロジェクトをセレクトし右クリックしサブウインドウを開き「追加」ー>「既存の項目」
C:\Users\xxxxxx\Hatena\a20211009\SimpleWebServer\SimpleWebServer.cs と Program.cs 2ファイルを追加する
TCP Soket Server用のプロジェクト作成
ソリューション エクスプローラーでソリューションを右クリックし、 [追加]>[新しいプロジェクト] の順に選択します。
[新しいプロジェクトの追加] ページで、検索ボックスに「ライブラリ」と入力します。
言語の一覧 から [C#] を選択し、次に、プラットフォームの一覧から [すべてのプラットフォーム] を選択します。 [クラス ライブラリ] テンプレートを選んでから、 [次へ] を選択します。
[新しいプロジェクトの構成] ページで、 [プロジェクト名] ボックスに「SocketServer」と入力してから、 [次へ] を選択します。
[追加情報] ページで、[.NET6 (長期的なサポート)] を選択し、[作成] を選択します。
Socketを使ったサーバーサーバー作成
1.IPアドレスとポート番号を指定して、ローカルエンドポ
イントを設定する
2.TCP/IPのソケットを作成
3.TCP/IPのソケットをローカルエンドポイントにバインド
4.データを待つ
6.非同期ソケットを開始して、接続をリッスンする
7.端末からデータ受信を待ち受ける
8.受信したメッセージをクライアントに送信する
以上を非同期のsocketメソッドを使って実装します。
次のコードをコピーしSocketServerクラスを作成する
public class TCPServer
{
//マニュアルリセットイベントのインスタンスを生成
public ManualResetEvent allDone = new ManualResetEvent(false);
StateObject? state = null;
public async void Start(int port)
{
await StartListening(port);
}
//TCP/IPの接続開始処理
public async Task<bool> StartListening(int port)
{
// IPアドレスとポート番号を指定して、ローカルエンドポイントを設定
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, port);
// TCP/IPのソケットを作成
Socket TcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
TcpServer.Bind(localEndPoint); // TCP/IPのソケットをローカルエンドポイントにバインド
TcpServer.Listen(10); // 待ち受け開始
await Task.Run(()=>
{
while (true)
{
// シグナルの状態をリセット
allDone.Reset();
// 非同期ソケットを開始して、接続をリッスンする
Debug.WriteLine("接続待機中...");
TcpServer.BeginAccept(new AsyncCallback(AcceptCallback), TcpServer);
// シグナル状態になるまで待機
allDone.WaitOne();
}
});
}
catch (Exception ex)
{
Debug.WriteLine(ex.ToString());
}
return false;
}
public Action<object> WriteLog { get; set; }
public void AcceptCallback(IAsyncResult ar)
{
// シグナル状態にし、メインスレッドの処理を続行する
allDone.Set();
WriteLog("TCP Cilent 接続");
// クライアント要求を処理するソケットを取得
Socket TcpServer = (Socket)ar.AsyncState;
Socket TcpClient = TcpServer.EndAccept(ar);
// 端末からデータ受信を待ち受ける
state = new StateObject();
state.workSocket = TcpClient;
TcpClient.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
public void SendMessage(byte[] message)
{
if (state != null && state.workSocket != null)
{
Socket TcpClient = state.workSocket;
// クライアントへデータの送信を開始
TcpClient.BeginSend(message, 0, message.Length, 0, new AsyncCallback(SendCallback), TcpClient);
}
}
public static void ReceiveCallback(IAsyncResult ar)
{
var content = string.Empty;
try
{
// 非同期オブジェクトからソケット情報を取得
StateObject state = (StateObject)ar.AsyncState;
Socket TcpClient = state.workSocket;
// クライアントソケットからデータを読み取り
int bytesRead = TcpClient.EndReceive(ar);
if (bytesRead > 0)
{
// 受信したデータを蓄積
state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
// 蓄積データの終端タグを確認
content = state.sb.ToString();
if (content.IndexOf("<EOF>") > -1)
{
// 終了タグ<EOF>があれば、読み取り完了
Debug.WriteLine(string.Format("クライアントから「{0}」を受信", content));
// ASCIIコードをバイトデータに変換
byte[] byteData = Encoding.ASCII.GetBytes("OK");
// クライアントへデータの送信を開始
TcpClient.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), TcpClient);
}
else
{
// 取得していないデータがあるので、受信再開
TcpClient.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
}
}
catch (Exception ex)
{
Console.WriteLine("[" + DateTime.Now + "] " + ex.Message);
}
}
private static void SendCallback(IAsyncResult ar)
{
try
{
// 非同期オブジェクトからソケット情報を取得
#pragma warning disable CS8600 // Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています。
Socket TcpClient = (Socket)ar.AsyncState;
#pragma warning restore CS8600 // Null リテラルまたは Null の可能性がある値を Null 非許容型に変換しています。
// クライアントへデータ送信完了
#pragma warning disable CS8602 // null 参照の可能性があるものの逆参照です。
int bytesSent = TcpClient.EndSend(ar);
#pragma warning restore CS8602 // null 参照の可能性があるものの逆参照です。
Console.WriteLine("[" + DateTime.Now + "] " + "Messageをクライアントへ送信");
//ソケット通信を終了
Debug.WriteLine("接続終了");
//TcpClient.Shutdown(SocketShutdown.Both);
//TcpClient.Close();
}
catch (Exception e)
{
Debug.WriteLine(e.ToString());
}
}
}
// 非同期処理でソケット情報を保持する為のオブジェクト
public class StateObject
{
// 受信バッファサイズ
public const int BufferSize = 1024;
// 受信バッファ
public byte[] buffer = new byte[BufferSize];
// 受信データ
public StringBuilder sb = new StringBuilder();
// ソケット
public Socket? workSocket = null;
}
WebhookServerプロジェクト変更
Program.csを以下コードに変更してください
static void Main(string[] args)
{
Console.SetWindowSize(80, 20);
SimpleWebServer.Run(
@"C:\www\DocRoot", // ドキュメントルート (index.html 等を配置したフォルダ) を指定する。
80); // ポート番号 (特に理由が無ければ 80 を指定する)
}
SimpleWebServer.csの変更
TCPServerのインスタンスをSimpleWebServer.csに作成します
SimpleWebServerのメソッドRun()に下記コードを追加する
(public static void Run(string docRoot, int portNo))
87行 public static TCPServer? tcpserver = null;
100行目から下記5行を追加する
1.//socket server 起動
2.int port = 8000;
3.tcpserver = new TCPServer();
4.tcpserver.WriteLog = WriteLog;
5.tcpserver.Start(port);
// tcpserver プロパティ設定
public static TCPServer? tcpserver = null;
public static void Run(string docRoot, int portNo)
{
try
{
if (string.IsNullOrEmpty(docRoot))
throw new Exception("ドキュメントルートを指定して下さい。");
if (!Directory.Exists(docRoot))
throw new Exception("ドキュメントルートは存在しません:" + docRoot);
if (portNo < 1 || 65535 < portNo)
throw new Exception("不正なポート番号です:" + portNo);
//socket server 起動
int port = 8000;
tcpserver = new TCPServer();
tcpserver.WriteLog = WriteLog;
tcpserver.Start(port);
次にメソッド”POST”を書き換える
148行目 case "POST"の下の行
throw new Exception("Unsupported method"); を下記2行に書き換え変更する
this.Webhook(channel, false);
break;
Webhook()メソッドの作成
Webhook()メソッドを173行目に作成する
private void P_Connected(HTTPServerChannel channel)
{
WriteLog("Client: " + channel.Channel.Handler.RemoteEndPoint);
if (7 < channel.Method.Length) // ? 最も長いメソッドより長い。
throw new Exception("Received method is too long");
WriteLog("Method: " + channel.Method);
switch (channel.Method)
{
case "GET":
this.GetOrHead(channel, false);
break;
case "HEAD":
this.GetOrHead(channel, true);
break;
case "POST":
this.Webhook(channel, false);
break;
case "PUT":
throw new Exception("Unsupported method");
case "DELETE":
throw new Exception("Unsupported method");
case "CONNECT":
throw new Exception("Unsupported method");
case "OPTIONS":
throw new Exception("Unsupported method");
case "TRACE":
throw new Exception("Unsupported method");
case "PATCH":
throw new Exception("Unsupported method");
default:
throw new Exception("Unknown method");
}
}
private void Webhook(HTTPServerChannel channel, bool v)
{
int readLen = channel.Body.Length;
string content = System.Text.Encoding.ASCII.GetString(channel.Body, 0, readLen);
tcpserver.SendMessage(channel.Body);
System.Diagnostics.Debug.WriteLine($"contentbody: {content}");
}
「private class HTTPBodyOutputStream : IDisposable」を変更する。このクラスの上の行に「/// POSTを使う場合は#if falseとする」と記述がある これに従って true を falseに変更する。
以上でWebhookServerが出来ました。次回はSocket受信クライエントを作成しサーバーのテストとサーバーを外部に公開する記事を予定しています。
この記事が気に入ったらサポートをしてみませんか?