LwIP應用開發筆記之九:LwIP無操作系統TELNET服務器


  前面我們已經實現了基於RAW API的TCP服務器和客戶端,也在此基礎上實現了HTTP應用。接下來我們實現一個基於TCP的Telnet服務器應用。

1Telnet協議簡介

  Telnet協議是TCP/IP協議族中的一員,是Internet遠程登陸服務的標准協議和主要方式。它為用戶提供了在本地計算機上完成遠程主機工作的能力。在終端使用者的電腦上使用telnet程序,用它連接到服務器。終端使用者可以在telnet程序中輸入命令,這些命令會在服務器上運行,就像直接在服務器的控制台上輸入一樣。可以在本地就能控制服務器。要開始一個telnet會話,必須輸入用戶名和密碼來登錄服務器。Telnet是常用的遠程控制Web服務器的方法。

  Telnet是位於OSI模型的第7層---應用層上的一種協議,是一個通過創建虛擬終端提供連接到遠程主機終端仿真的TCP/IP協議。這一協議需要通過用戶名和口令進行認證,是Internet遠程登陸服務的標准協議。應用Telnet協議能夠把本地用戶所使用的計算機變成遠程主機系統的一個終端。它提供了三種基本服務:

  • Telnet定義一個網絡虛擬終端為遠程系統提供一個標准接口。客戶機程序不必詳細了解遠程系統,他們只需構造使用標准接口的程序;
  • Telnet包括一個允許客戶機和服務器協商選項的機制,而且它還提供一組標准選項; .
  • Telnet對稱處理連接的兩端,即Telnet不強迫客戶機從鍵盤輸入,也不強迫客戶機在屏幕上顯示輸出。

2TELNET服務器的設計

  Telnet是一種基於TCP實現的遠程登錄方式,Telnet協議也分配有固定端口23,在這里我們就是用這一端口來實現一個Telnet服務器。這個服務器可以提供給多個客戶端訪問。

 

  我們要實現的這個Telnet服務器是比較簡單的一個設計。當客戶端成功鏈接到服務器后,服務器就會提示用戶登錄,成功登陸后就可以向服務器發送命令,當發送不同的命令時,服務器給出不同的響應。具體的操作流程設計如下:

 

  從上面的流程圖看其實我們設計的Telnet服務器功能已經非常明確了。但有兩點需要描述一下。首先是關於連接狀態的設定,在這里我們只是簡單的將狀態定義為兩種:已登錄和未登錄。如果已登錄則按命令交互來解析。如果未登錄則按登錄密碼來解析。

  另一方面,為了實現命令交互,我們需要為Telnet服務器設定命令。我們簡單的設定6種命令:"hello"、"date"、"time"、"version"、"quit"與"help"等命令。事實上我們實現Telnet服務器主要就是處理:如何接收和響應這些命令。

3TELNET服務器的實現

  我們已經設計了Telnet服務器的基本功能。接下來就是如何實現它了。我們已經有前面實現TCP服務器的基礎。所以實現他的重點就是我們設計的Telnet服務器了。

  我們依然采用實現普通TCP服務器結構來實現Telnet服務器,只是在信息處理回調函數上更復雜一點。還有就是端口方面我們采用Telnet的慣用端口。首先必然是Telnet服務器的初始化。

 1 /* TELNET服務器初始化配置*/
 2 void Telnet_Server_Initialization(void)
 3 {
 4   struct tcp_pcb *pcb;                            
 5  
 6   /* 生成一個新的TCP控制塊 */
 7   pcb = tcp_new();                                   
 8  
 9   /* 控制塊邦定到本地IP和對應端口 */
10   tcp_bind(pcb, IP_ADDR_ANY, TCP_TELNET_SERVER_PORT);      
11  
12   /* 服務器進入偵聽狀態 */
13   pcb = tcp_listen(pcb);                       
14  
15   /* 注冊服務器accept回調函數 */
16   tcp_accept(pcb, TelnetServerAccept);                                       
17 }

  其實初始化部分就是我們已經熟悉的TCP服務器的初始化,只是使用了Telnet的慣用端口。接下來就是實現在初始化中注冊的Telnet服務器接收回調函數。該函數為tcp_accept_fn類型,注冊到了監聽控制塊的accept字段。在服務器上有新連接建立時就會被內核調用。

 1 /* TELNET接收回調函數,客戶端建立連接后,本函數被調用 */
 2 static err_t TelnetServerAccept(void *arg, struct tcp_pcb *pcb, err_t err)
 3 {    
 4   u32_t remote_ip;
 5   char linkInfo [100];
 6   u8_t iptab[4];
 7   telnet_conn_arg *conn_arg = NULL;
 8   remote_ip = pcb->remote_ip.addr;
 9  
10   iptab[0] = (u8_t)(remote_ip >> 24);
11   iptab[1] = (u8_t)(remote_ip >> 16);
12   iptab[2] = (u8_t)(remote_ip >> 8);
13   iptab[3] = (u8_t)(remote_ip);
14  
15   //生成登錄提示信息
16   sprintf(linkInfo, "Welcome to Telnet! your IP:Port --> [%d.%d.%d.%d:%d]\r\n", \
17                    iptab[3], iptab[2], iptab[1], iptab[0], pcb->remote_port);  
18  
19   conn_arg = mem_calloc(sizeof(telnet_conn_arg), 1);
20   if(!conn_arg)
21   {
22     return ERR_MEM;
23   }
24  
25   conn_arg->state = TELNET_SETUP;
26   conn_arg->client_port = pcb->remote_port;
27   conn_arg->bytes_len = 0;
28   memset(conn_arg->bytes, 0, MAX_MSG_SIZE);
29  
30   tcp_arg(pcb, conn_arg);
31  
32   /* 注冊Telnet服務器連接錯誤回調函數 */
33   tcp_err(pcb, TelnetServeConnectError);
34   /* 注冊Telnet服務器消息處理回調函數*/
35   tcp_recv(pcb, TelnetServerCallback);
36  
37   /* 連接成功,發送登錄提示信息 */ 
38   tcp_write(pcb, linkInfo, strlen(linkInfo), 1);
39   tcp_write(pcb, LOGIN_INFO, strlen(LOGIN_INFO), 1);
40  
41   return ERR_OK;
42 }

  在這個函數中,我們實現的功能主要是三方面:注冊Telnet服務器消息處理回調函數;注冊Telnet服務器連接錯誤回調函數;初始化Telnet服務器的狀態。這個初始化是在連接建立后,Telnet服務器與客戶端的交互初始化,比如登錄狀態,用戶提示等。

  在上面的函數中,我們注冊了兩個回調函數,接下來必然就是實現這兩個函數。我們先來實現Telnet服務器信息處理回調函數。這個函數其實就是我們前面注冊過的TCP服務器數據接收處理函數。這個函數是tcp_recv_fn類型。這是使用RAW API實現TCP服務器最重要的函數,因為我們實現的TCP服務器究竟有什么功能,完全依賴於這個函數及其所調用的函數。

 1 /* TELNET服務器信息處理回調函數,在有消息需要處理時,調用此函數 */
 2 static err_t TelnetServerCallback(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err)
 3 {
 4   telnet_conn_arg *conn_args = (telnet_conn_arg *)arg;
 5   char sndbuf[50];
 6   int strlen = 0;
 7   int ret = 0;
 8  
 9   if(NULL == conn_args || pcb->remote_port != conn_args->client_port)
10   {
11     if(p!= NULL)
12     {
13       pbuf_free(p);
14     }
15     return ERR_ARG;
16   }
17  
18   if (p != NULL)
19   {       
20     /* 更新接收窗口 */
21     tcp_recved(pcb, p->tot_len);
22  
23     ret = TelnetCommandInput(pcb, conn_args, p);
24  
25     if(ret == 1)//是完整命令
26     {
27       switch(conn_args->state)
28       {
29       case TELNET_SETUP:
30         {
31           if(strcmp(conn_args->bytes,PASSWORD) == 0)//密碼正確
32           {
33             strlen = sprintf(sndbuf,"##Hello! This is an LwIP-based Telnet Server##\r\n");
34             tcp_write(pcb, sndbuf, strlen,TCP_WRITE_FLAG_COPY);
35             strlen = sprintf(sndbuf,"##Created by Moonan...                      ##\r\n");
36             tcp_write(pcb, sndbuf, strlen,TCP_WRITE_FLAG_COPY);
37             strlen = sprintf(sndbuf,"##Enter help for help.  Enter quit for quit.##\r\n");
38             tcp_write(pcb, sndbuf, strlen,TCP_WRITE_FLAG_COPY);
39             strlen = sprintf(sndbuf,"LwIP Telnet>");
40             tcp_write(pcb,sndbuf,strlen, 1);
41  
42             conn_args->state = TELNET_CONNECTED;//轉換狀態
43           }
44           else//密碼錯誤,提示重新登錄
45           {
46             strlen = sprintf(sndbuf,"##PASSWORD ERROR! Try again:##\r\n");
47             tcp_write(pcb, sndbuf, strlen,TCP_WRITE_FLAG_COPY);
48           }
49           memset(conn_args->bytes, 0, MAX_MSG_SIZE);
50           conn_args->bytes_len = 0;
51           break;
52         }
53       case TELNET_CONNECTED:
54         {
55           if(TelnetCommandParse(pcb, conn_args->bytes) == 0)
56           {
57             memset(conn_args->bytes, 0, MAX_MSG_SIZE);
58             conn_args->bytes_len = 0;
59           }
60           else
61           {
62             /* 服務器關閉連接 */
63             ServerCloseTelnetConnection(pcb);
64           }
65           break;
66         }
67       default:
68         {
69           break;
70         }
71       }
72     }
73     pbuf_free(p);
74   } 
75   else if (err == ERR_OK)
76   {
77     /* 服務器關閉連接 */
78     ServerCloseTelnetConnection(pcb);
79   }
80  
81   return ERR_OK;
82  
83 }

  在這個函數中,我們實現了Telnet服務器的各種功能,如登錄驗證,命令檢查,命令響應等。已經具備一個Telnet服務器的基本框架。接下來還要實現Telnet連接錯誤回調函數。這個函數是tcp_err_fn類型,在這個程序中主要完成連接異常結束時的一些處理,可以釋放一些必要的資源。在這個函數被內核調用時,連接實際上已經斷開,相關控制塊也已經被刪除。所以在這個函數中我們可以重新初始化連接及其資源。

1 /* TELNET連接錯誤回調函數,連接故障時調用本函數 */
2 static void TelnetServeConnectError(void *arg, err_t err)
3 {
4   Telnet_Server_Initialization();
5 }

  至此,我們就實現了一個簡單的Telnet服務器,當然它只是一個雛形,需要開發更復雜的功能則需要修改這幾個回調函數。

4TELNET服務器總結

  我們已經實現了一個簡單的Telnet服務器。當然,我們的目的主要是以此來學習基於LwIP的復雜的TCP應用。事實上理解了TCP服務器的實現機制,諸如此類基於TCP的高級應用協議並不是特別復雜的事情。

歡迎關注:


免責聲明!

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



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