同步發行到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來連接的時候,需要發送一系列的指令來協商(綿羊協商)通信,
流程圖類似這個
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
}
}
效果如下:
首先,收到遠程服務端的信息(第一次接)
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
意思為
/*=====================================
我也不想自己開啟回顯
同事我也覺得你開啟回顯很合適
=====================================*/
服務器端終於結束驗證了,開始發正文顯示......(第四次接)
解析過來就是
Ubuntu 9.04
atpking-desktop login:
之后為了告訴服務器咱們收到消息了(第四次發送消息)
255 252 1 255 253 1 255 252 1 255 253 1
客戶端
=====================================
我的,禁止回顯的設置
服務器的,請你接受 回顯
我自己想禁用回顯
我希望服務器接受回顯
======================================
登錄畫面
登錄成功~
================================================
后記
確實通信那塊很嚼人,
而且現在也還是半懂不懂的狀態(不知道為什么第四次回發消息的時候,服務器就不再發消息了等)
只不過最起碼的,從cocket本身模擬來說,還算能寫篇blog
花了不少時間研究socket 和寫這篇日子,
希望能對看這篇文章的人產生一點幫助吧.