Telnet協議詳解及使用C# 用Socket 編程來實現Telnet協議


同步發行到atpking.com......

這因為有個任務涉及到使用telnet 來連接遠端的路由器,獲取信息,之后進行處理.

所以需要寫一個自動telnet登錄到遠端,之后獲取信息進行處理的程序.

 

自己C++ 一塌糊塗,所以幾乎最開始就沒打算用C++或者C寫

論自己的實力,還是走C#路線稍微穩妥一點吧,

因為telnet 是使用tcp/ip 協議折騰的事情

很容易的想到使用socket來實現telnet

(當然你可以在進程里啟用telnet 命令,只不過總覺得那樣不夠技術,

而且操作不自由--受限於telnet 這個指令)

 

ok,翻協議,弄清原理,結果比預想的難度要大一些

定義

============================================================

Telnet協議是TCP/IP協議族中應用最廣泛的協議。

它允許用戶(Telnet客戶端)通過一個協商過程來與一個遠程設備進行通信。

Telnet協議是基於網絡虛擬終端NVT(Network Virtual Termina1)的實現,

NVT是虛擬設備,連接雙方(客戶機和服務器)都必須把它們的物理終端和NVT進行相互轉換

============================================================

 

大概意思就是   跟遠端通信的一套協議,之后這個協議無視你機器是啥型號,啥樣子

只要是用telnet的,統統都可以看成是NVT  

(類似面向對象中的繼承關系:NVT是父類,各種實用telnet 的都繼承與NVT)

 

好處非常明顯,可以無視型號而直接使用標准命令,任何服從NVT 的設備都能通信

當然不可避免的,標准也同時代表着性能的損失:

 

由於NVT 得顧及到所有的各種型號的機器,所以他定義的 操作十分有限

(因為考慮到包括要支持類似9城小霸王那些性能很差,系統簡單的機器),

為了解決NVT這個"為了照顧小霸王,而導致高端設備的功能不能用"的這個弊病,

Telnet琢磨出了一個比較好的解決方案

"用於擴展基本NVT功能的協議,提供了選項協商的機制"   來解決問題

 

類似那個經典的英國綿羊笑話

使用英文描述兩只綿羊在路上碰到后發生的故事

=========================

綿羊A:  Hi,Sheep!

綿羊B:Hi, Can you speak Chinese?

綿羊A:yes, "jin tian chi le ma ? (今天吃了嗎?)"

綿羊B: "chi la ,hen shuang ! (吃啦,很爽!)"

.....省略500字

改卷的英國人累牛滿面,因為他不會中文,

但又不能說這篇文章有問題.

=========================

這里英文就可以理解為NVT 的標准功能,為通用語,

而后來的中文拼音,就是擴展.

 

ok,原理就是那么回事,講講細節吧

telnet來連接的時候,需要發送一系列的指令來協商(綿羊協商)通信,

流程圖類似這個

image

 

ok,那么,具體的命令是怎樣的呢?

很無趣的,

就是telnet的命令格式

IAC 命令碼 選項碼

一個個的解釋.

IAC:命令解釋符,說白了就是每條指令的前綴都得是它,固定值255  (11111111 B)

命令碼: 一系列定義:(最常用的250~ 254 咱加粗表示)

名稱

代碼(十進制)

描述

EOF

236

文件結束符

SUSP

237

掛起當前進程(作業控制)

ABORT

238

異常中止進程

EOR

239

記錄結束符i

SE

240

自選項結束

NOP

241

無操作

DM

242

數據標記

BRK

243

中斷

IP

244

中斷進程

AO

245

異常中止輸出

AYT

246

對方是否還在運行?

EC

247

轉義字符

EL

248

刪除行

GA

249

繼續進行

SB

250

子選項開始

WILL

251

同意啟動(enable)選項

WONT

252

拒絕啟動選項

DO

253

認可選項請求

DONT

254

拒絕選項請求

選項協商:4種請求

    1)WILL:發送方本身將激活選項

    2)DO:發送方想叫接受端激活選項

    3)WONT:發送方本身想禁止選項

    4)DONT:發送方想讓接受端去禁止選項

 

緊接着就是選項碼

選項標識

名稱

1

回顯

3

抑制繼續進行

5

狀態

6

定時標記

24

終端類型

31

窗口大小

32

終端速度

33

遠程流量控制

34

行方式

36

環境變量

ok,為了搞掂這個telnet 鏈接,

我特地裝了個linux 作為telnet的鏈接對象進行telnet遠程登錄

之后寫了一個惡心的代碼來幫助我進行調試

 

 

額,在寫這個程序之前 ,我搜了將近1天時間的網路

發現大多數代碼注釋的不是太和諧,讀起來很難理解

所以自己根據網上的一個win程序改出了一個console 的程序

 

同時,我特地花了兩天時間,幾乎把每一句能寫注釋的都寫了,

基本上可以說是我目前注釋寫的最多的一次代碼了,

 

代碼特別龐大,就做個窗口放上去了

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Collections;
 
namespace ConsoleApplication1
{
    public class Program
    {
        #region 一些telnet的數據定義,先沒看懂沒關系
        /// <summary>
        /// 標志符,代表是一個TELNET 指令
        /// </summary>
        readonly Char IAC = Convert.ToChar(255);
        /// <summary>
        /// 表示一方要求另一方使用,或者確認你希望另一方使用指定的選項。
        /// </summary>
        readonly Char DO = Convert.ToChar(253);
        /// <summary>
        /// 表示一方要求另一方停止使用,或者確認你不再希望另一方使用指定的選項。
        /// </summary>
        readonly Char DONT = Convert.ToChar(254);
        /// <summary>
        /// 表示希望開始使用或者確認所使用的是指定的選項。
        /// </summary>
        readonly Char WILL = Convert.ToChar(251);
        /// <summary>
        /// 表示拒絕使用或者繼續使用指定的選項。
        /// </summary>
        readonly Char WONT = Convert.ToChar(252);
 
        /// <summary>
        /// 表示后面所跟的是對需要的選項的子談判
        /// </summary>
        readonly Char SB = Convert.ToChar(250);
 
        /// <summary>
        /// 子談判參數的結束
        /// </summary>
        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>
        /// 流
        /// </summary>
        byte[] m_byBuff = new byte[100000];
 
        /// <summary>
        /// 收到的控制信息
        /// </summary>
        private ArrayList m_ListOptions = new ArrayList();
 
        /// <summary>
        /// 存儲准備發送的信息
        /// </summary>
        string m_strResp;
 
        /// <summary>
        /// 一個Socket套接字
        /// </summary>
        private Socket s;
        #endregion
 
 
        /// <summary>
        /// 主函數
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            //實例化這個對象
            Program p = new Program();
            //啟動socket進行telnet 鏈接
            p.doSocket();
 
 
        }
 
        /// <summary>
        /// 啟動socket 進行telnet操作
        /// </summary>
        private void doSocket()
        {
            //獲得鏈接的地址,可以是網址也可以是IP
            Console.WriteLine("Server Address:");
            //解析輸入,如果是一個網址,則解析成ip
            IPAddress import = GetIP(Console.ReadLine());
            //獲得端口號
            Console.WriteLine("Server Port:");
            int port = int.Parse(Console.ReadLine());
 
            //建立一個socket對象,使用IPV4,使用流進行連接,使用tcp/ip 協議
            s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 
            //獲得一個鏈接地址對象(由IP地址和端口號構成)
            IPEndPoint address = new IPEndPoint(import, port);
 
            /*
             * 說明此socket不是處於阻止模式
             * 
             * msdn 對阻止模式的解釋:
             * ============================================================
             * 如果當前處於阻止模式,並且進行了一個並不立即完成的方法調用,
             * 則應用程序將阻止執行,直到請求的操作完成后才解除阻止。
             * 如果希望在請求的操作尚未完成的情況下也可以繼續執行,
             * 請將 Blocking 屬性更改為 false。Blocking 屬性對異步方法無效。
             * 如果當前正在異步發送和接收數據,並希望阻止執行,
             * 請使用 ManualResetEvent 類。
             * ============================================================
            */
            s.Blocking = false;
 
            /*
             * 開始一個對遠程主機連接的異步請求,
             * 因為Telnet 使用的是TCP 鏈接,是面向連接的,
             * 所以此處BeginConnect 會啟動一個異步請求,
             * 請求獲得與 給的address 的連接
             *
             * 此方法的第二個函數是一個類型為AsyncCallback 的委托
             * 
             * 這個AsyncCallback msdn給出的定義如下
             * ===================================================================
             * 使用 AsyncCallback 委托在一個單獨的線程中處理異步操作的結果。A
             * syncCallback 委托表示在異步操作完成時調用的回調方法。
             * 回調方法采用 IAsyncResult 參數,該參數隨后可用來獲取異步操作的結果。
             * ===================================================================
             * 這個方法里的委托實際上就是 當異步請求有回應了之后,執行委托的方法.
             * 委托里的參數,實際上就是BeginConnect的第三個參數,
             * 此處為socket 本身
             * 
             * 我比較懶,寫了一個匿名委托,實際上跟AsyncCallback 效果一個樣.
             * 
             */
            s.BeginConnect(
                address,
                delegate(IAsyncResult ar)
                /*
                 * 此處為一個匿名委托,
                 * 實際上等於 
                 * 建立一個AsyncCallback對象,指定后在此引用一個道理
                 * 
                 * ok這里的意義是,
                 * 當遠程主機連接的異步請求有響應的時候,執行以下語句
                 */
                {
                    try
                    {
                        //獲得傳入的對象 (此處對象是BeginConnect 的第三個參數)
                        Socket sock1 = (Socket)ar.AsyncState;
 
                        /*
                         * 如果 Socket 在最近操作時連接到遠程資源,則為 true;否則為 false。
                         * 
                         * 以下是MSDN 對Connected屬性的備注信息
                         * =========================================================================
                         * Connected 屬性獲取截止到最后的 I/O 操作時 Socket 的連接狀態。
                         * 當它返回 false 時,表明 Socket 要么從未連接,要么已斷開連接。
                         * 
                         * Connected 屬性的值反映最近操作時的連接狀態。如果您需要確定連接的當前狀態,
                         * 請進行非阻止、零字節的 Send 調用。
                         * 如果該調用成功返回或引發 WAEWOULDBLOCK 錯誤代碼 (10035),
                         * 則該套接字仍然處於連接狀態;否則,該套接字不再處於連接狀態。
                         * =========================================================================
                        */
                        if (sock1.Connected)
                        {
 
                            AsyncCallback recieveData = new AsyncCallback(OnRecievedData);
                            /*
                             * 此處沒再用匿名委托的原因是,
                             * 一個匿名委托嵌套一個匿名委托,我自己思路跟不上來了...
                             * 
                             * ok,這里是當Connected  為true時,
                             * 使用BeginReceive 方法
                             * 開始接收信息到m_byBuff(我們在類中定義的私有屬性)
                             * 
                             * 以下是MSDN 對BeginReceive 的一些說明
                             * =========================================================================
                             * 異步 BeginReceive 操作必須通過調用 EndReceive 方法來完成。
                             * 通常,該方法由 callback 委托調用。此方法在操作完成前不會進入阻止狀態。
                             * 若要一直阻塞到操作完成時為止,請使用 Receive 方法重載中的一個。
                             * 若要取消掛起的 BeginReceive,請調用 Close 方法。
                             * ==========================================================================
                             * 
                             * 當接收完成之后,他們就會調用OnRecievedData方法
                             * 我在recieveData所委托的方法OnRecievedData 中調用了sock.EndReceive(ar);
                             */
                            sock1.BeginReceive(m_byBuff, 0, m_byBuff.Length, SocketFlags.None, recieveData, sock1);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("初始化接收信息出錯:" + ex.Message);
                    }
                },
                s);
 
            //此處是為了發送指令而不停的循環
            while (true)
            {
                //發送讀出的數據
                DispatchMessage(Console.ReadLine());
                //因為每發送一行都沒有發送回車,故在此處補上
                DispatchMessage("\r\n");
            }
 
        }
 
        /// <summary>
        /// 當接收完成后,執行的方法(供委托使用)
        /// </summary>
        /// <param name="ar"></param>
        private void OnRecievedData(IAsyncResult ar)
        {
            //從參數中獲得給的socket 對象
            Socket sock = (Socket)ar.AsyncState;
            /*
             * EndReceive方法為結束掛起的異步讀取
             * (貌似是在之前的beginReceive收到數據之后,
             * socket只是"掛起",並未結束)
             * 之后返回總共接收到的字流量
             * 
             * 以下是MSDN給出的EndReceive 的注意事項
             * =========================================================================================
             * EndReceive 方法完成在 BeginReceive 方法中啟動的異步讀取操作。
             * 
             * 在調用 BeginReceive 之前,需創建一個實現 AsyncCallback 委托的回調方法。
             * 該回調方法在單獨的線程中執行並在 BeginReceive 返回后由系統調用。
             * 回調方法必須接受 BeginReceive 方法所返回的 IAsyncResult 作為參數。
             * 
             * 在回調方法中,調用 IAsyncResult 的 AsyncState 方法以獲取傳遞給 BeginReceive 方法的狀態對象。
             * 從該狀態對象提取接收 Socket。在獲取 Socket 之后,可以調用 EndReceive 方法以成功完成讀取操作,
             * 並返回已讀取的字節數。
             * 
             * EndReceive 方法將一直阻止到有數據可用為止。
             * 如果您使用的是無連接協議,則 EndReceive 將讀取傳入網絡緩沖區中第一個排隊的可用數據報。
             * 如果您使用的是面向連接的協議,則 EndReceive 方法將讀取所有可用的數據,
             * 直到達到 BeginReceive 方法的 size 參數所指定的字節數為止。
             * 如果遠程主機使用 Shutdown 方法關閉了 Socket 連接,並且所有可用數據均已收到,
             * 則 EndReceive 方法將立即完成並返回零字節。
             * 
             * 若要獲取接收到的數據,請調用 IAsyncResult 的 AsyncState 方法,
             * 然后提取所產生的狀態對象中包含的緩沖區。
             * 
             * 若要取消掛起的 BeginReceive,請調用 Close 方法。
             * =========================================================================================
             */
            int nBytesRec = sock.EndReceive(ar);
            //如果有接收到數據的話
            if (nBytesRec > 0)
            {
                //將接收到的數據轉個碼,順便轉成string型
                string sRecieved = Encoding.GetEncoding("utf-8").GetString(m_byBuff, 0, nBytesRec);
 
                //聲明一個字符串,用來存儲解析過的字符串
                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':
                            break;
                        default:
                            m_strLine += Convert.ToString(ch);
                            break;
                    }
                }
 
                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]);
 
                    // Process the incoming data
                    //對接收的信息進行處理,包括對傳輸過來的信息的參數的存取和
                    string mOutText = ProcessOptions(mToProcess);
                    //解析命令后返回 顯示信息(即除掉了控制信息)
                    if (mOutText != "")
                        Console.Write(mOutText);
 
 
                    // Respond to any incoming commands
                    //接收完數據,處理完字符串數據等一系列事物之后,開始回發數據
                    RespondToOptions();
                }
                catch (Exception ex)
                {
                    throw new Exception("接收數據的時候出錯了! " + ex.Message);
                }
            }
            else// 如果沒有接收到任何數據的話
            {
                // 輸出   關閉連接
                Console.WriteLine("Disconnected", sock.RemoteEndPoint);
                // 關閉socket
                sock.Shutdown(SocketShutdown.Both);
                sock.Close();
                Console.Write("Game Over");
                Console.ReadLine();
            }
        }
 
        /// <summary>
        ///  發送數據的函數
        /// </summary>
 
        private void RespondToOptions()
        {
            try
            {
                //聲明一個字符串,來存儲 接收到的參數
                string strOption;
                /*
                 * 此處的控制信息參數,是之前接受到信息之后保存的
                 * 例如 255   253   23   等等
                 * 具體參數的含義需要去查telnet 協議
                 */
                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>
        /// 解析接收的數據,生成最終用戶看到的有效文字,同時將附帶的參數存儲起來
        /// </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;
            try
            {
                //把數據從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)
                        {
                            m_DISPLAYTEXT = m_strTemp.Substring(0, ndx);
                            ldx = m_strTemp.IndexOf(Convert.ToString(SE));
                            m_strOption = m_strTemp.Substring(ndx, ldx);
                            m_ListOptions.Add(m_strOption);
                            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地址
        /// </summary>
        /// <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)
        {
            try
            {
 
                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;
                        m_strResp += ch;
                        m_strResp += Option;
 
                    }
                    //如果命令碼為251(WILL)
                    if (Verb == WILL)
                    {
                        //我設置我應答的命令碼為 253(DO) 即為我認可你使用回顯或抑制繼續進行
                        ch = DO;
                        m_strResp += ch;
                        m_strResp += Option;
                        //break;
                    }
                    //如果接受到的命令碼為251(WONT) 
                    if (Verb == WONT)
                    {
                        //應答  我也拒絕選項請求回顯或抑制繼續進行
                        ch = DONT;
                        m_strResp += ch;
                        m_strResp += Option;
                        //    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 底下一系列代表,無論你發那種請求,我都不干
                    if (Verb == DO)
                    {
                        ch = WONT;
                        m_strResp += ch;
                        m_strResp += Option;
                    }
                    if (Verb == DONT)
                    {
                        ch = WONT;
                        m_strResp += ch;
                        m_strResp += Option;
                    }
                    if (Verb == WILL)
                    {
                        ch = DONT;
                        m_strResp += ch;
                        m_strResp += Option;
                    }
                    if (Verb == WONT)
                    {
                        ch = DONT;
                        m_strResp += ch;
                        m_strResp += Option;
                    }
                    #endregion
                }
            }
            catch (Exception eeeee)
            {
 
                throw new Exception("解析參數時出錯:" + eeeee.Message);
            }
 
        }
 
        /// <summary>
        /// 將信息轉化成charp[] 流的形式,使用socket 進行發出
        /// 發出結束之后,使用一個匿名委托,進行接收,
        /// 之后這個委托里,又有個委托,意思是接受完了之后執行OnRecieveData 方法
        /// 
        /// </summary>
        /// <param name="strText"></param>
        void DispatchMessage(string strText)
        {
            try
            {
                //申請一個與字符串相當長度的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流,之后發送完畢后執行委托中的方法(此處為匿名委托)
                /*MSDN 對BeginSend 的解釋
                 * =======================================================================================================
                 * BeginSend 方法可對在 Connect、BeginConnect、Accept 或 BeginAccept 方法中建立的遠程主機啟動異步發送操作。
                 * 如果沒有首先調用 Accept、BeginAccept、Connect 或 BeginConnect,則 BeginSend 將會引發異常。
                 * 調用 BeginSend 方法將使您能夠在單獨的執行線程中發送數據。
                 * 您可以創建一個實現 AsyncCallback 委托的回調方法並將它的名稱傳遞給 BeginSend 方法。
                 * 為此,您的 state 參數至少必須包含用於通信的已連接或默認 Socket。
                 * 如果回調需要更多信息,則可以創建一個小型類或結構,用於保存 Socket 和其他所需的信息。
                 * 通過 state 參數將此類的一個實例傳遞給 BeginSend 方法。
                 * 回調方法應調用 EndSend 方法。
                 * 當應用程序調用 BeginSend 時,系統將使用一個單獨的線程來執行指定的回調方法,
                 * 並阻止 EndSend,直到 Socket 發送了請求的字節數或引發了異常為止。
                 * 如果希望在調用 BeginSend 方法之后使原始線程阻止,請使用 WaitHandle.WaitOne 方法。
                 * 當需要原始線程繼續執行時,請在回調方法中調用 T:System.Threading.ManualResetEvent 的 Set 方法。
                 * 有關編寫回調方法的其他信息,請參見 Callback 示例。
                 * =======================================================================================================
                 */
                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);
 
                        /*
                         * 此處為:開始接受數據(在發送完畢之后-->出自於上面的匿名委托),
                         * 當接收完信息之后,執行OnrecieveData方法(由委托傳進去),
                         * 注意,是異步調用
                         */
                        sock1.BeginReceive(m_byBuff, 0, m_byBuff.Length, SocketFlags.None, recieveData, sock1);
                    }
                }, s);
 
                /*
                 * 結束 異步發送
                 * EndSend 完成在 BeginSend 中啟動的異步發送操作。  
                 * 在調用 BeginSend 之前,需創建一個實現 AsyncCallback 委托的回調方法。
                 * 該回調方法在單獨的線程中執行並在 BeginSend 返回后由系統調用。
                 * 回調方法必須接受 BeginSend 方法所返回的 IAsyncResult 作為參數。
                 * 
                 * 在回調方法中,調用 IAsyncResult 參數的 AsyncState 方法可以獲取發送 Socket。
                 * 在獲取 Socket 之后,則可以調用 EndSend 方法以成功完成發送操作,並返回發送的字節數。
 
            
           
                 */
                s.EndSend(ar2);
            }
            catch (Exception ers)
            {
                Console.WriteLine("出錯了,在回發數據的時候:" + ers.Message);
            }
        }
        #endregion
    }
}

效果如下:

image

 

首先,收到遠程服務端的信息(第一次接)

255 253 24    255 253 32    255 253 35    255 253 39

遠程服務器說

/*========================

我想要求客戶端激活終端類型

我想要求客戶端激活終端速度

我想要求客戶端激活39功能(手冊沒寫是啥)

=========================*/

 

之后,我們客戶端返回以下信息(第一次發)

255 252 24   255 252 32    255 252 35    255 252 39

客戶端說

/*==================================

客戶端想禁止 你說的所有的功能(24,32,35,39)

==================================*/

 

服務器收到了我們發出的信息之后,又發出以下信息(第二次接)

255 251 3    255 253 1    255 253 31    255 251 5    255 253 33

服務器又說

/*======================================

我自己將激活回抑制繼續進行

我希望客戶端激活回顯功能

我希望客戶端激活窗口大小

我自己將激活狀態

我希望客戶端激活遠程流量控制

======================================*/

之后我們客戶端返回以下信息(第二次發)

255 253 3    255 251 1    255 252 31    255 254 5    255 252 33

客戶端說

/*======================================

我希望服務器端激活抑制繼續進行

我自己將激活回顯功能

我自己想禁止窗口大小功能

我希望服務端禁止狀態功能

我自己想禁止遠程流量控制

======================================*/

服務器收到我們消息之后,又給我們消息(第三次接)

255 254 1     255 251 1  

意思為

/*=====================================

我想讓客戶端禁用回顯

我想自己使用 回顯

=====================================*/

我們客戶端接着發(第三次發)

255 252 1    255 253 1

意思為

/*=====================================

我也不想自己開啟回顯

同事我也覺得你開啟回顯很合適

=====================================*/

 

服務器端終於結束驗證了,開始發正文顯示......(第四次接)

4J

解析過來就是

Ubuntu 9.04

atpking-desktop login: 

之后為了告訴服務器咱們收到消息了(第四次發送消息)

255 252 1    255 253 1    255 252 1    255 253 1

客戶端

=====================================

我的,禁止回顯的設置

服務器的,請你接受 回顯

我自己想禁用回顯

我希望服務器接受回顯

======================================

 

 

 

登錄畫面

image 

登錄成功~

 

================================================

后記

確實通信那塊很嚼人,

而且現在也還是半懂不懂的狀態(不知道為什么第四次回發消息的時候,服務器就不再發消息了等)

只不過最起碼的,從cocket本身模擬來說,還算能寫篇blog

花了不少時間研究socket 和寫這篇日子,

希望能對看這篇文章的人產生一點幫助吧.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM