Mikä olisi paras tapa pitää serveriyhteydet omissa "tiloissaan" pyörimässä ja pollaamassa I/O-liikennettä häiritsemättä toistensa ja pääohjelman toimintaa?
Threadid tuntuvat vähän turhan raskaalta ratkaisulta ja (ainakaan) niiden sammuttaminenkaan ei käy niin helposti kuin odotin.
Kyllä tuo säikeillä kannattaa tehdä, ja säikeiden peruskäyttäminen onkin C#:lla suhteellisen helppoa.
Tässä yksinkertainen bottiesimerkki säikeillä toteutettuna (konsoliapplikaatio):
Program.cs
using System; using System.Text; namespace IrcBotTutorial { class Program { const string BotName = "TutorialBot"; const string BotNick = "TutorialBot"; static bool RunProgram = true; static void Main(string[] args) { WriteHelp(); while (RunProgram) { string input = Console.ReadLine(); if (input.ToLower() == "exit") break; try { string[] ServerInfo = input.Split(new string[] {" ",":"},StringSplitOptions.RemoveEmptyEntries); switch(ServerInfo[0].ToLower()) { case "connect": Config BotConfig = new Config(); BotConfig.name = BotName; BotConfig.nick = BotNick; BotConfig.server = ServerInfo[1]; BotConfig.port = System.Convert.ToInt32(ServerInfo[2]); Server Ircserver = new Server(BotConfig); break; case "exit": RunProgram = false; break; case "help": WriteHelp(); break; case "clear": Console.Clear(); break; default: Console.WriteLine("Invalid command!"); break; } } catch { Console.WriteLine("Invalid Command or data."); } } Console.WriteLine("Exiting... Press enter to close"); Console.ReadLine(); } static void WriteHelp() { Console.WriteLine("To connect: type Connect [Server]:[Port]"); Console.WriteLine("To clear screen: type Clear"); Console.WriteLine("To Exit: type exit"); Console.WriteLine("To display commands: type Help"); } } }
Config.cs
using System; using System.Text; namespace IrcBotTutorial { class Config { public string server; public int port; public string nick; public string name; } }
IRCBot.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; using System.IO; namespace IrcBotTutorial { class IRCBot { TcpClient IRCConnection = null; Config config; NetworkStream ns = null; StreamReader sr = null; StreamWriter sw = null; /// <summary> /// Constructor for IRCBot /// </summary> /// <param name="config">IRCBot Configuration</param> public IRCBot(Config config) { this.config = config; try { IRCConnection = new TcpClient(config.server, config.port); } catch( Exception exception) { Console.WriteLine("Connection Error: {0}",exception.ToString()); } try { ns = IRCConnection.GetStream(); sr = new StreamReader(ns); sw = new StreamWriter(ns); sendData("USER", config.nick + " something.com " + " something.com" + " :" + config.name); sendData("NICK", config.nick); //default channel to join sendData("JOIN", "#IrcBotTutorial"); IRCWork(); } catch(Exception exception) { Console.WriteLine("Communication error {0}", exception.ToString()); } finally { if (sr != null) sr.Close(); if (sw != null) sw.Close(); if (ns != null) ns.Close(); if (IRCConnection != null) IRCConnection.Close(); } } /// <summary> /// Sends data /// </summary> /// <param name="cmd">Command name</param> /// <param name="param">Data</param> public void sendData(string cmd, string param) { if (param == null) { sw.WriteLine(cmd); sw.Flush(); Console.WriteLine(config.server + "(" + config.port + "):" + cmd); } else { sw.WriteLine(cmd + " " + param); sw.Flush(); Console.WriteLine(config.server + "(" + config.port + "):" + cmd + " " + param); } } /// <summary> /// Receives and sends data from/to irc server /// </summary> public void IRCWork() { string[] ex; string data; bool shouldRun = true; while (shouldRun) { data = sr.ReadLine(); Console.WriteLine(config.server + "(" + config.port + "):" + data); char[] charSeparator = new char[] { ' ' }; ex = data.Split(charSeparator, 5); if (ex[0] == "PING") { sendData("PONG", ex[1]); } if (ex.Length > 4) //is the command received long enough to be a bot command? { string command = ex[3]; //grab the command sent switch (command.ToLower()) { case ":!join": sendData("JOIN", ex[4]); //if the command is !join send the "JOIN" command to the server with the parameters set by the user break; case ":!say": sendData("PRIVMSG", ex[2] + " " + ex[4]); //if the command is !say, send a message to the chan (ex[2]) followed by the actual message (ex[4]). break; case ":!quit": sendData("QUIT", ex[4]); //if the command is quit, send the QUIT command to the server with a quit message shouldRun = false; //turn shouldRun to false - the server will stop sending us data so trying to read it will not work and result in an error. This stops the loop from running and we will close off the connections properly break; } } if (ex.Length > 3) { string command = ex[3]; switch (command.ToLower()) { case ":!part": sendData("PART", ex[2]); break; } } } } } }
Server.cs
using System; using System.Text; using System.Net.Sockets; using System.Threading; using System.Net; namespace IrcBotTutorial { class Server { private Thread ServerThread; /// <summary> /// Creates a new threaded connection to Irc server /// </summary> /// <param name="NewServerConfig">IrcBot Configuration</param> public Server(Config NewServerConfig) { this.ServerThread = new Thread(new ParameterizedThreadStart(ConnectToServer)); this.ServerThread.IsBackground = true; //Thread forces to exit when application closes (if ConnectToServer is still running) this.ServerThread.Start(NewServerConfig); } /// <summary> /// Connects to server /// </summary> /// <param name="BotConfig">IrcBot Configuration</param> private void ConnectToServer(object BotConfig) { while (true) { Config IRCConfig = (Config)BotConfig; new IRCBot(IRCConfig); Console.WriteLine("Bot {0} quit/crashed", IRCConfig.server + ":" + IRCConfig.port); break; } } } }
Aihe on jo aika vanha, joten et voi enää vastata siihen.