@Niranda: Ich habe die Idee mit dem Server mal weiter aufgegriffen und einen kleinen HttpServer gemacht. Wenn es dich interessiert, kannst du ja mal drüber schauen und Feedback geben. Kritik ist selbstverständlich immer erwünscht. Man lernt ja gerne dazu. Der Server an sich funktioniert schon und soll dafür verwendet werden, über das Internet Gesundheitsdaten eines Rechners per WMI abzufragen. Wenn man ihn anbrowst, dann liefert er zuerst die allgemeinen Leistungsdaten des PC's zurück. WMI muss ich allerdings noch integrieren, ich bastel mir da derzeit noch die Methoden zusammen. Hab bisher noch nicht viel mit WMI gemacht, ist aber ganz witzig. Im Prinzip wie SQL.
Ich zeige dann z.b. an, welche CPU verbaut ist, wieviel Ram, wie der Host-Name ist usw. und sofort. Desweiteren wird ein Button mit gerendert und etwas Javascript mit ausgegeben.
Wenn man den Button anklickt, dann wird über Ajax zum Beispiel die CPU-Auslastung angezeigt. Das Ajax-Script läuft dann so lange, bis ein weiterer Button gedrückt wurde und frägt jede Sekunde
die CPU-Auslastung ab und sendet sie an den Browser.
Ich möchte auch noch eine Leistungskurve mit einbauen. Also serverseitig dann im konfigurierten Interval ein Bitmap mit der aktuellen Leistungskurve erzeugen und dann an den Browser schicken. Wie gesagt, es fehlt noch einiges, aber der Server an sich ist fertig und funktioniert. Das Aajax-Script für die CPU-Auslastung funktioniert auch schon. Muss serverseitig nur noch die Daten aufbereiten.
Zum groben Überblick!
Die Solution besteht bisher aus 4 Librarys:
HelperClasses:
Enthält bisher nur die Klasse, die für das Auslesen der CPU-Last dient. Muss ich noch auf meine Bedürfnisse zuschneiden. Hab die zuerst mal separat codiert, um zu sehen, wie man es umsetzen könnte.
HtmlContextLibrary:
Enthält die Klassen für den Html- und JavaScript-Context, den der Server zurückgibt.
HttpServer:
Der eigentliche Server.
Shared:
Meine Interfaces. Die nutze ich bisher nur, um den Javascript-Code, den mein Server zurück gibt möglichst modular verwalten zu können.
This is the Code
Zuerst die Klasse CPULoadWatcher zum auslesen der CPU-Last:
[codebox]using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Collections;
namespace HelperClasses
{
public class CPULoadWatcher
{
#region private variables
private Watcher _watch;
private Dictionary<ushort, Watcher> WatcherCol = new Dictionary<ushort, Watcher>();
#endregion
#region public delegates
public delegate void CPULoadChanged(LoadChangedEventArgs e);
#endregion
#region public events
public event CPULoadChanged LoadChanged;
#endregion
#region public methods
/// <summary>
/// used, to add a new Watcher to the collection
/// </summary>
/// <param name="CPU">the cpu, which should be watched</param>
/// <param name="interval">the interval, where the data should be refreshed</param>
public void AddWatcher(ushort CPU, int interval)
{
if (!WatcherCol.ContainsKey(CPU))
{
_watch = new Watcher(CPU, interval);
_watch._Changed += OnLoadChanged;
WatcherCol.Add(CPU, _watch);
_watch.Start();
}
}
#endregion
#region protected virtual methods
/// <summary>
/// fires the Load Changed event
/// </summary>
/// <param name="e">event args</param>
protected virtual void OnLoadChanged(LoadChangedEventArgs e)
{
if (LoadChanged != null)
{
LoadChanged(e);
}
}
#endregion
}
/// <summary>
/// the class, which represent the watcher
/// </summary>
public class Watcher
{
#region private variables
private int _interval;
private bool _stop;
private ushort _cpu;
private PerformanceCounter _perfCount;
private Thread _Thread;
#endregion
#region public delegates
public delegate void LoadChanged(LoadChangedEventArgs e);
#endregion
#region public events
/// <summary>
/// the event, which is triggered, if the load value has changed
/// </summary>
public event LoadChanged _Changed;
#endregion
#region constructor
/// <summary>
/// the constructor of the class watcher
/// </summary>
/// <param name="cpu">the cpu, which should be watched</param>
/// <param name="interval">the interval for refreshing the data</param>
public Watcher(ushort cpu, int interval)
{
this._interval = interval;
this._cpu = cpu;
}
#endregion
#region private methods
/// <summary>
/// the watch method, which determine the load value
/// </summary>
private void Watch()
{
do
{
if (_stop)
{
_stop = false;
_Thread.Abort();
break;
}
if (_Changed != null)
{
_Changed(new LoadChangedEventArgs(_cpu, _perfCount.NextValue()));
}
Thread.Sleep(_interval);
}
while (true);
}
/// <summary>
/// the start method, which triggers the watching process
/// </summary>
public void Start()
{
_perfCount = new PerformanceCounter("Processor", "% Processor Time", _cpu.ToString());
_Thread = new Thread(Watch);
_Thread.Start();
}
#endregion
#region properties
public bool pStop
{
get { return this._stop; }
set { this._stop = value; }
}
#endregion
}
/// <summary>
/// the event args for the load changed event
/// </summary>
public class LoadChangedEventArgs
{
ushort _cpu;
float _load;
public LoadChangedEventArgs(ushort cpu, float load)
{
this._cpu = cpu;
this._load = load;
}
public ushort pCPU
{
get { return this._cpu; }
}
public float pLoad
{
get { return this._load; }
}
}
}[/codebox]
In der HtmlContextLybrary die Klasse HtmlSite, die die eigentliche Seite genrieren soll. Ist noch ein bisschen billig, aber dient bisher ja nur zu Testzwecken.
[codebox]using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Shared;
namespace HTMLContextLibrary
{
public class HTMLSite
{
public static string displayWebSite(IJavaScriptProvider JavaScriptContext)
{
string Html = "<html><head></head><body><form method=\"get\" name=\"StartForm\">";
Html += "<h1 align=\"center\">WMI Web-Interface</h1><br /><br />";
Html += "<input type=\"button\" value=\"show health status\" name=\"btn_showHealthData\" onclick=\"requestHealthStatus()\"/><br /><br />";
Html += "<div id=\"replacibleContent\"></div>";
Html += "</from></body>";
Html += "<script type=\"text/javascript\">" + JavaScriptContext.getJavaScriptContext() + "</script></html>";
return Html;
}
}
}
[/codebox]
Dann die Klasse XMLHttpRequestStratecy, die ich über das Interface einsetze. Die enthält jetzt zum Beispiel das Ajax-Script, daß ich zum Auslesen der CPU-Last verwende und über das Interace in die eigentliche Html-Seite einbete.
[codebox]using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Shared;
namespace HTMLContextLibrary
{
public class XMLHttpRequestStratecy : IJavaScriptProvider
{
public string getJavaScriptContext()
{
string url = "\"http://localhost:4510/Load\"";
//variables for javascript
string JavaScript = "var xmlObj; ";
JavaScript += "var delay = 1000; ";
JavaScript += "var processRunning = true; ";
JavaScript += "var timerID; ";
//function requestHealthStatus
JavaScript += "function requestHealthStatus() {";
JavaScript += "if(window.XMLHttpRequest)";
JavaScript += "{ xmlObj = new XMLHttpRequest();";
JavaScript += "xmlObj.onreadystatechange = processReqChange;";
JavaScript += "xmlObj.open(\"Get\", " + url + ", true);";
JavaScript += "xmlObj.send(null);}";
JavaScript += "else if(window.ActiveXObject) {";
JavaScript += "xmlObj = new ActiveXObject(\"Microsoft.XMLHTTP\");";
JavaScript += "if(xmlObj) {";
JavaScript += "xmlObj.onstatechangestate = processReqChange;";
JavaScript += "xmlObj.open(\"Get\", " + url + ", true);";
JavaScript += "xmlObj.send(null); }}";
JavaScript += "if (processRunning) {";
JavaScript += "timerID = self.setTimeout(\"requestHealthStatus('http://localhost:4510/Load')\", delay) }; ";
JavaScript += "function stopProcess() {";
JavaScript += "processRunning = false; } ";
JavaScript += "function processReqChange() {";
JavaScript += "var input = xmlObj.responseText; ";
JavaScript += "document.getElementById('replacibleContent').innerHTML = input; }};";
return JavaScript;
}
}
}
[/codebox]
Dann der eigentliche Server. In der Klasse Programm zunächst einfach mal das User-Interface über eine einfache Konsole. Außerdem abonniere ich hier gleich das Event, daß getriggert wird,
wenn vom TCP-Listener eine Anfrage empfangen wurde. Und dann wird der Http-Server aufgerufen.
[codebox]using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
namespace HttpServer
{
class Program
{
private static string _promt = "Ready> ";
/// <summary>
/// the userinterface
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
Console.WriteLine("Simple Http Server");
Console.WriteLine("Version 1.0");
Console.WriteLine("For availible commands type \"command\"");
Console.WriteLine("");
Console.WriteLine("");
Listener listener = new Listener(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 4510));
listener.ClientAccept += new EventHandler<ClientAcceptedEventArgs>(listener_ClientAccept);
while (true)
{
Promt();
string command = Console.ReadLine();
switch (command)
{
case "command":
Console.WriteLine("Type \"start\" to start the server");
Console.WriteLine("Type \"stop\" to stop the server");
Console.WriteLine("Type \"exit\" to close the application");
break;
case "start":
try
{
listener.Start();
Console.WriteLine("Listener was started");
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
break;
case "stop":
listener.Stop(false);
Console.WriteLine("Listener was stopped");
break;
case "exit":
goto exit;
default:
Console.WriteLine("Unknown command, please type \"command\" for all " +
System.Environment.NewLine + "availible commands!");
break;
}
}
exit:
listener.Stop(true);
return;
}
/// <summary>
/// called, if a requesting client was accepted
/// </summary>
/// <param name="sender"></param>
/// <param name="e">event args</param>
static void listener_ClientAccept(object sender, ClientAcceptedEventArgs e)
{
Console.WriteLine(e.pClient.Client.RemoteEndPoint.ToString() + " connected");
HttpClient httpClient = new HttpClient(e.pClient);
httpClient.Response();
Promt();
}
/// <summary>
/// used, to display the promt
/// </summary>
private static void Promt()
{
Console.Write(_promt);
}
}
}[/codebox]
Dann der eigentliche Server HttpClient
[codebox]using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using Shared;
using HTMLContextLibrary;
using System.Net;
namespace HttpServer
{
class HttpClient
{
#region member variables
private TcpClient _tcpClient;
private string _contentLength;
#endregion
#region constructor
/// <summary>
/// constructor
/// </summary>
/// <param name="client">the requesting client, which was accepted from the tcp listener</param>
public HttpClient(TcpClient client)
{
this._tcpClient = client;
}
#endregion
#region protected virtual methods
/// <summary>
/// the method, which response any request from the user
/// </summary>
public virtual void Response()
{
ThreadPool.QueueUserWorkItem(state =>
{
IJavaScriptProvider javaScriptContext = new XMLHttpRequestStratecy();
NetworkStream stream = _tcpClient.GetStream();
StreamReader sr = new StreamReader(stream);
string line = null;
Match match;
string body = null;
line = sr.ReadLine();
if (line.StartsWith("GET"))
{
match = Regex.Match(line, "GET /(.*) HTTP");
if (match.Success)
{
body = HTMLSite.displayWebSite(javaScriptContext);
}
do
{
line = sr.ReadLine();
}
while (line != "");
}
else if (line.StartsWith("POST"))
{
match = Regex.Match(line, "POST /(.*) HTTP");
if (match.Success)
{
char[] buffer = null;
do
{
line = sr.ReadLine();
if (line.StartsWith("Content-Length:"))
{
_contentLength = line.Substring(15);
buffer = new char[int.Parse(_contentLength)];
}
}
while (line != "");
sr.ReadBlock(buffer, 0, int.Parse(_contentLength));
string sendingBtn = null;
for (int i = 0; i <= buffer.Length; i++)
{
if (buffer
!= '=')
{
sendingBtn += buffer.ToString();
}
else
break;
}
match = Regex.Match(sendingBtn, "btn_showHealthData");
if (match.Success)
{
}
}
}
string header = "HTTP/1.0 200 OK" + System.Environment.NewLine
+ "Content-Length:{0}" + System.Environment.NewLine
+ "Connection:close" + System.Environment.NewLine
+ "" + System.Environment.NewLine;
if (!string.IsNullOrEmpty(body))
{
BinaryWriter wr = new BinaryWriter(stream);
wr.Write(Encoding.ASCII.GetBytes(String.Format(header, Encoding.ASCII.GetBytes(body).Length) + body));
wr.Flush();
wr.Close();
}
sr.Dispose();
});
}
#endregion
protected virtual void xmlHttpListener_ClientAccept(object sender, ClientAcceptedEventArgs e)
{
HttpClient httpClient = new HttpClient(e.pClient);
}
}
}
[/codebox]
Dann der TCP-Listener. Ich habe meinen eigenen Listener geschrieben, weil der .net-Listener keine Events bietet.
[codebox]using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using System.Net;
namespace HttpServer
{
class Listener
{
#region private variables
private Thread _Thread;
private bool _stop;
private IPEndPoint _endpoint;
#endregion
#region constructor
/// <summary>
/// creates a new instance of the listener
/// </summary>
/// <param name="endpoint">the endpoint which contains the ip adress an the port</param>
public Listener(IPEndPoint endpoint)
{
this._endpoint = endpoint;
ParameterizedThreadStart threadStart = new ParameterizedThreadStart(Listen);
_Thread = new Thread(threadStart);
}
#endregion
#region private methods
/// <summary>
/// listen to the the specified port until the flag _stop was set to true
/// </summary>
/// <param name="state"></param>
private void Listen(object state)
{
TcpListener listener = new TcpListener((IPEndPoint)state);
listener.Start();
while (!_stop)
{
TcpClient client = listener.AcceptTcpClient();
ClientAcceptedEventArgs clientEventArgs = new ClientAcceptedEventArgs(client);
OnClientAccept(clientEventArgs);
}
listener.Stop();
}
#endregion
#region protected virtual methods
/// <summary>
/// handles and fires the ClientAccepted event
/// </summary>
/// <param name="e">the event args</param>
protected virtual void OnClientAccept(ClientAcceptedEventArgs e)
{
EventHandler<ClientAcceptedEventArgs> handler = ClientAccept;
if (handler != null)
{
handler(this, e);
}
}
#endregion
#region public methods
/// <summary>
/// starts the listener
/// </summary>
public void Start()
{
if (_Thread.ThreadState == ThreadState.Running)
{
throw new Exception("Listener is already running");
}
else
{
_Thread.Start(_endpoint);
}
}
/// <summary>
/// stop the listener
/// </summary>
/// <param name="force">if the listener should stop immediately</param>
public void Stop(bool force)
{
this._stop = true;
if (force)
{
_Thread.Abort();
}
}
#endregion
#region public events
/// <summary>
/// happens if a new client was accepted
/// </summary>
public event EventHandler<ClientAcceptedEventArgs> ClientAccept;
#endregion
}
#region event args
/// <summary>
/// event args which contains the tcpclient
/// </summary>
public class ClientAcceptedEventArgs : EventArgs
{
private TcpClient _client;
public ClientAcceptedEventArgs(TcpClient client)
{
this._client = client;
}
public TcpClient pClient
{
get { return this._client; }
set { this._client = value; }
}
}
#endregion
}
[/codebox]
Und unter Shared dann das Interface IJavaScriptProvider, welches ich wie gesagt nutze, um den JavaScript-Code modula auszutauschen.
[codebox]using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Shared
{
public interface IJavaScriptProvider
{
string getJavaScriptContext();
}
}[/codebox]