天天看點

一個不錯的C# Telnet類庫(改進了漢字顯示亂碼的問題)

//調用方法如下:

Telnet p = new Telnet("192.168.1.100", 23, 50);

if(p.Connect()==false)

{

  Console.WriteLine("連接配接失敗");

  return;

}

//等待指定字元傳回後才執行下一指令

p.WaitFor("login:");

p.Send("admin");

p.WaitFor("password:");

p.Send("123456");

p.WaitFor(">");

//Console.WriteLine(p.SessionLog);

Console.WriteLine(p.WorkingData);

改進後代碼(注意标藍色的部分):

using System;

using System.Collections.Generic;

using System.Text;

using System.Net;

using System.Net.Sockets;

using System.Collections;

using System.Threading;

namespace Lwolf

    public class Telnet

    {

        #region telnet的資料定義

        /// <summary>       

        /// 标志符,代表是一個TELNET 指令       

        /// </summary>       

        readonly Char IAC = Convert.ToChar(255);

        /// 表示一方要求另一方使用,或者确認你希望另一方使用指定的選項。

        readonly Char DO = Convert.ToChar(253);

        /// <summary>

        /// 表示一方要求另一方停止使用,或者确認你不再希望另一方使用指定的選項。      

        /// </summary>      

        readonly Char DONT = Convert.ToChar(254);

        /// 表示希望開始使用或者确認所使用的是指定的選項。

        /// </summary>

        readonly Char WILL = Convert.ToChar(251);

        /// 表示拒絕使用或者繼續使用指定的選項。

        readonly Char WONT = Convert.ToChar(252);

        /// 表示後面所跟的是對需要的選項的子談判

        readonly Char SB = Convert.ToChar(250);

        /// 子談判參數的結束

        readonly Char SE = Convert.ToChar(240);

        const Char IS = '0';

        const Char SEND = '1';

        const Char INFO = '2';

        const Char VAR = '0';

        const Char VALUE = '1';

        const Char ESC = '2';

        const Char USERVAR = '3';

        /// 流

        /// /// </summary>

        byte[] m_byBuff = new byte[100000];

        /// 收到的控制資訊

        private ArrayList m_ListOptions = new ArrayList();

        /// 存儲準備發送的資訊

        string m_strResp;

        /// 一個Socket套接字

        private Socket s;

        #endregion

        private IPEndPoint iep;

        private string address;

        private int port;

        private int timeout;

        private string strWorkingData = "";     // 儲存從伺服器端接收到的資料

        private string strFullLog = "";

        //==== 夏春濤 擴充 20110531 ================================================           

        private string strWorkingDataX = "";

        //用于擷取目前工作的資料内容

        public string WorkingData

        {

            get { return strWorkingDataX; }

        }

        //===================================================================

        public Telnet(string Address, int Port, int CommandTimeout)

            address = Address;

            port = Port;

            timeout = CommandTimeout;

        /// 啟動socket 進行telnet操作       

        public bool Connect()

            IPAddress import = GetIP(address);

            s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            iep = new IPEndPoint(import, port);

            try

            {

                // Try a blocking connection to the server

                s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                s.Connect(iep);

          //異步回調

                AsyncCallback recieveData = new AsyncCallback(OnRecievedData);

                s.BeginReceive(m_byBuff, 0, m_byBuff.Length, SocketFlags.None, recieveData, s);

                return true;

            }

            catch (Exception)

                return false;

        /// 當接收完成後,執行的方法(供委托使用)      

        /// </summary>     

        /// <param name="ar"></param>      

        private void OnRecievedData(IAsyncResult ar)

                //從參數中獲得給的socket 對象          

                Socket sock = (Socket)ar.AsyncState;

                //EndReceive方法為結束挂起的異步讀取        

                int nBytesRec = sock.EndReceive(ar);

                //如果有接收到資料的話           

                if (nBytesRec > 0)

                {

                    //聲明一個字元串,用來存儲解析過的字元串               

                    string m_strLine = "";

                    //周遊Socket接收到的字元                

                    /*      

                     * 此循環用來調整linux 和 windows在換行上标記的差別      

                     * 最後将調整好的字元賦予給 m_strLine     

                     */

                    for (int i = 0; i < nBytesRec; i++)

                    {

                        Char ch = Convert.ToChar(m_byBuff[i]);

                        switch (ch)

                        {

                            case '\r':

                                m_strLine += Convert.ToString("\r\n");

                                break;

                            case '\n':

                            default:

                                m_strLine += Convert.ToString(ch);

                        }

                    }

                    try

                        //獲得轉義後的字元串的長度                 

                        int strLinelen = m_strLine.Length;

                        //如果長度為零                  

                        if (strLinelen == 0)

                            //則傳回"\r\n" 即回車換行                   

                            m_strLine = Convert.ToString("\r\n");

                        //建立一個流,把接收的資訊(轉換後的)存進 mToProcess 中  

                        Byte[] mToProcess = new Byte[strLinelen];

                        for (int i = 0; i < strLinelen; i++)

                            mToProcess[i] = Convert.ToByte(m_strLine[i]);

                        //對接收的資訊進行處理,包括對傳輸過來的資訊的參數的存取和      

                        string mOutText = ProcessOptions(mToProcess);

                        //==== 夏春濤 擴充 20110531 ==============================================

                        mOutText = ConvertToGB2312(mOutText);

                        strWorkingDataX = mOutText;

                        //===================================================================

                        //解析指令後傳回 顯示資訊(即除掉了控制資訊)                       

                        if (mOutText != "")

                            //Console.Write(mOutText);//顯示輸出//夏春濤 去掉 20110531/////////////////////////

                            strWorkingData = mOutText;

                            strFullLog += mOutText;

                        //接收完資料,處理完字元串資料等一系列事物之後,開始回發資料         

                        RespondToOptions();

                    catch (Exception ex)

                        throw new Exception("接收資料的時候出錯了! " + ex.Message);

                }

                else// 如果沒有接收到任何資料的話          

                    // 關閉連接配接           

                    // 關閉socket              

                    sock.Shutdown(SocketShutdown.Both);

                    sock.Close();

            catch { }

        ///  發送資料的函數      

        private void RespondToOptions()

                //聲明一個字元串,來存儲 接收到的參數           

                string strOption;

                /*              

                 * 此處的控制資訊參數,是之前接受到資訊之後儲存的             

                 * 例如 255   253   23   等等                               

                 */

                for (int i = 0; i < m_ListOptions.Count; i++)

                    //獲得一個控制資訊參數                  

                    strOption = (string)m_ListOptions[i];

                    //根據這個參數,進行處理                  

                    ArrangeReply(strOption);

                DispatchMessage(m_strResp);

                m_strResp = "";

                m_ListOptions.Clear();

            catch (Exception ers)

                Console.WriteLine("出錯了,在回發資料的時候 " + ers.Message);

        /// 解析接收的資料,生成最終使用者看到的有效文字,同時将附帶的參數存儲起來      

        ///</summary>      

        ///<param name="m_strLineToProcess">收到的處理後的資料</param>    

        /// <returns></returns>    

        private string ProcessOptions(byte[] m_strLineToProcess)

            string m_DISPLAYTEXT = "";

            string m_strTemp = "";

            string m_strOption = "";

            string m_strNormalText = "";

            bool bScanDone = false;

            int ndx = 0;

            int ldx = 0;

            char ch;

                //把資料從byte[] 轉化成string   

                for (int i = 0; i < m_strLineToProcess.Length; i++)

                    Char ss = Convert.ToChar(m_strLineToProcess[i]);

                    m_strTemp = m_strTemp + Convert.ToString(ss);

                //此處意義為,當沒描完資料前,執行掃描     

                while (bScanDone != true)

                    //獲得長度               

                    int lensmk = m_strTemp.Length;

                    //之後開始分析指令,因為每條指令為255 開頭,故可以用此來區分出每條指令

                    ndx = m_strTemp.IndexOf(Convert.ToString(IAC));

                    //此處為出錯判斷,本無其他含義              

                    if (ndx > lensmk)

                        ndx = m_strTemp.Length;

                    //此處為,如果搜尋到IAC标記的telnet 指令,則執行以下步驟       

                    if (ndx != -1)

                        #region 如果存在IAC标志位

                        // 将 标志位IAC 的字元 指派給最終顯示文字          

                        m_DISPLAYTEXT += m_strTemp.Substring(0, ndx);

                        // 此處獲得指令碼             

                        ch = m_strTemp[ndx + 1];

                        //如果指令碼是253(DO) 254(DONT)  521(WILL) 252(WONT) 的情況下

                        if (ch == DO || ch == DONT || ch == WILL || ch == WONT)

                            //将以IAC 開頭3個字元組成的整個指令存儲起來         

                            m_strOption = m_strTemp.Substring(ndx, 3);

                            m_ListOptions.Add(m_strOption);

                            // 将 标志位IAC 的字元 指派給最終顯示文字                        

                            m_DISPLAYTEXT += m_strTemp.Substring(0, ndx);

                            //将處理過的字元串删去                         

                            string txt = m_strTemp.Substring(ndx + 3);

                            m_strTemp = txt;

                        //如果IAC後面又跟了個IAC (255) 

                        else if (ch == IAC)

                            //則顯示從輸入的字元串頭開始,到之前的IAC 結束       

                            m_DISPLAYTEXT = m_strTemp.Substring(0, ndx);

                            //之後将處理過的字元串排除出去                 

                            m_strTemp = m_strTemp.Substring(ndx + 1);

                        //如果IAC後面跟的是SB(250)      

                        else if (ch == SB)

                            ldx = m_strTemp.IndexOf(Convert.ToString(SE));

                            m_strOption = m_strTemp.Substring(ndx, ldx);

                            m_strTemp = m_strTemp.Substring(ldx);

                        #endregion

                    //若字元串裡已經沒有IAC标志位了 

                    else

                        //顯示資訊累加上m_strTemp存儲的字段     

                        m_DISPLAYTEXT = m_DISPLAYTEXT + m_strTemp;

                        bScanDone = true;

                //輸出人看到的資訊   

                m_strNormalText = m_DISPLAYTEXT;

            catch (Exception eP)

                throw new Exception("解析傳入的字元串錯誤:" + eP.Message);

            return m_strNormalText;

        /// <summary>     

        /// 獲得IP位址     

        /// <param name="import"></param>   

        /// <returns></returns>  

        private static IPAddress GetIP(string import)

            IPHostEntry IPHost = Dns.GetHostEntry(import);

            return IPHost.AddressList[0];

        #region magic Function

        //解析傳過來的參數,生成回發的資料到m_strResp  

        private void ArrangeReply(string strOption)

                Char Verb;

                Char Option;

                Char Modifier;

                Char ch;

                bool bDefined = false;

                //排錯選項,無啥意義             

                if (strOption.Length < 3) return;

                //獲得指令碼              

                Verb = strOption[1];

                //獲得選項碼            

                Option = strOption[2];

                //如果選項碼為 回顯(1) 或者是抑制繼續進行(3)

                if (Option == 1 || Option == 3)

                    bDefined = true;

                // 設定回發消息,首先為标志位255        

                m_strResp += IAC;

                //如果選項碼為 回顯(1) 或者是抑制繼續進行(3) ==true  

                if (bDefined == true)

                    #region 繼續判斷

                    //如果指令碼為253 (DO)            

                    if (Verb == DO)

                        //我設定我應答的指令碼為 251(WILL) 即為支援 回顯或抑制繼續進行    

                        ch = WILL;

                        m_strResp += ch;

                        m_strResp += Option;

                    //如果指令碼為 254(DONT)    

                    if (Verb == DONT)

                        //我設定我應答的指令碼為 252(WONT) 即為我也會"拒絕啟動" 回顯或抑制繼續進行

                        ch = WONT;

                    //如果指令碼為251(WILL)  

                    if (Verb == WILL)

                        //我設定我應答的指令碼為 253(DO) 即為我認可你使用回顯或抑制繼續進行

                        ch = DO;

                        //break;              

                    //如果接受到的指令碼為251(WONT)        

                    if (Verb == WONT)

                        //應答  我也拒絕選項請求回顯或抑制繼續進行      

                        ch = DONT;

                        //break;           

                    //如果接受到250(sb,标志子選項開始)           

                    if (Verb == SB)

                        /*                 

                         * 因為啟動了子标志位,指令長度擴充到了4位元組,                   

                         * 取最後一個标志位元組為選項碼                    

                         * 如果這個選項碼位元組為1(send)                   

                         * 則回發為 250(SB子選項開始) + 擷取的第二個位元組 + 0(is) + 255(标志位IAC) + 240(SE子選項結束)              

                         */

                        Modifier = strOption[3];

                        if (Modifier == SEND)

                            ch = SB;

                            m_strResp += ch;

                            m_strResp += Option;

                            m_strResp += IS;

                            m_strResp += IAC;

                            m_strResp += SE;

                    #endregion

                else //如果選項碼不是1 或者3

                    #region 底下一系列代表,無論你發那種請求,我都不幹

            catch (Exception eeeee)

                throw new Exception("解析參數時出錯:" + eeeee.Message);

        /// <summary>    

        /// 将資訊轉化成charp[] 流的形式,使用socket 進行發出  

        /// 發出結束之後,使用一個匿名委托,進行接收, 

        /// 之後這個委托裡,又有個委托,意思是接受完了之後執行OnRecieveData 方法

        ///      

        /// <param name="strText"></param> 

        void DispatchMessage(string strText)

                //申請一個與字元串相當長度的char流     

                Byte[] smk = new Byte[strText.Length];

                for (int i = 0; i < strText.Length; i++)

                    //解析字元串,将其存儲到char流中去  

                    Byte ss = Convert.ToByte(strText[i]);

                    smk[i] = ss;

                //發送char流,之後發送完畢後執行委托中的方法(此處為匿名委托)   

                IAsyncResult ar2 = s.BeginSend(smk, 0, smk.Length, SocketFlags.None, delegate(IAsyncResult ar)

                    //當執行完"發送資料" 這個動作後                 

                    // 擷取Socket對象,對象從beginsend 中的最後個參數上獲得         

                    Socket sock1 = (Socket)ar.AsyncState;

                    if (sock1.Connected)//如果連接配接還是有效                   

                        //這裡建立一個委托     

                        AsyncCallback recieveData = new AsyncCallback(OnRecievedData);

                        sock1.BeginReceive(m_byBuff, 0, m_byBuff.Length, SocketFlags.None, recieveData, sock1);

                }, s);

                s.EndSend(ar2);

                Console.WriteLine("出錯了,在回發資料的時候:" + ers.Message);

        /// 等待指定的字元串傳回

        /// <param name="DataToWaitFor">等待的字元串</param>

        /// <returns>傳回0</returns>

        public int WaitFor(string DataToWaitFor)

            long lngStart = DateTime.Now.AddSeconds(this.timeout).Ticks;

            long lngCurTime = 0;

            while (strWorkingData.ToLower().IndexOf(DataToWaitFor.ToLower()) == -1)

                lngCurTime = DateTime.Now.Ticks;

                if (lngCurTime > lngStart)

                    throw new Exception("Timed Out waiting for : " + DataToWaitFor);

                Thread.Sleep(1);

            strWorkingData = "";

            return 0;

        public void Send(string message)

            DispatchMessage(message);

            //因為每發送一行都沒有發送回車,故在此處補上        

            DispatchMessage("\r\n");

        /// 取完整日志

        public string SessionLog

            get

                return strFullLog;

        //======================================================================================

        /// 字元串編碼轉換,解決漢字顯示亂碼問題。

        /// 原始字元串中的漢字存儲的是漢字内碼,此代碼實質是将漢字内碼轉換為GB2312編碼。(夏春濤20110531)

        /// <param name="str_origin">需要轉換的字元串</param>

        /// <returns>轉換後的字元串</returns>

        private string ConvertToGB2312(string str_origin)

            char[] chars = str_origin.ToCharArray();

            byte[] bytes = new byte[chars.Length];

            for (int i = 0; i < chars.Length; i++)

                int c = (int)chars[i];

                bytes[i] = (byte)c;

            Encoding Encoding_GB2312 = Encoding.GetEncoding("GB2312");

            string str_converted = Encoding_GB2312.GetString(bytes);

            return str_converted;

    }