这更像是一个概念问题。我需要一些澄清。
今天我在学习一些套接字编程的东西,并根据Beej 的网络编程指南编写了一个简单的聊天服务器和聊天客户端。(聊天服务器接收客户端消息并将消息发送给所有其他客户端)
聊天客户端只是一个stdin
向服务器发送输入并从服务器打印套接字数据的程序。
后来我注意到指南说我可以telnet
用来连接到服务器。我试过了,它奏效了。
我对telnet不熟悉,很长一段时间我都不知道它到底是什么。
所以现在我的经历让我感到困惑:
telnet 不就是一个简单的 TCP 发送/回显程序吗?是什么让成为协议的东西如此特别?我的哑聊天客户端程序没有创建 [应用程序] 协议。
来自维基百科Communication_protocol:
在电信中,通信协议是一个规则系统, 它允许通信系统的两个或多个实体通过物理量的任何类型的变化来传输信息。
Telnet 创建什么规则?telnet host port
,为原始输入/输出打开一个 TCP 流套接字?这不是规则。
Telnet 在RFC 854中定义。使它(以及其他任何东西)成为协议的原因是一组规则/约束。一个这样的规则是 Telnet 是通过 TCP 完成的,并分配了端口 23——这些东西可能看起来微不足道,但需要在某个地方指定。
你不能随心所欲地发送任何你想要的东西,有些东西是有限制和特殊意义的。例如,它定义了一个“网络虚拟终端”——这是因为当建立 telnet 时,可能会有许多不同的终端:打印机、黑白显示器、支持 ANSI 代码的彩色显示器等。
此外,还有这样的东西:
在现代,大多数东西都不再那么重要了(再说一遍,telnet 作为协议不再被使用,不仅仅是因为它缺乏安全性)所以实际上它归结为发送/回显,除非你必须实际与终端接口。
连接两个终端时“正常工作”的事实
telnet
本身就是一个协议决定:设计者telnet
决定在 TCP 连接的任一端使用带有“网络虚拟终端”的模型,并决定在基本终端上对这些虚拟终端进行建模功能(确实是电传打字机)。除此之外,
telnet
它不仅仅是一个简单的基于 TCP 的回显服务;它支持带内发送的控制代码,用于各种目的。它被编入RFC 854。这来自 RFC 15,在维基百科“telnet”中提到。本文还首先将 telnet 称为“应用程序协议”。
RFC 15 是从 1969 年 9 月开始的——就在几天前,我听到了一个有趣的采访,采访了正好 50 年前建立第一个“互联网”连接的人。他的故事是这样的:
“与 N. Armstrong 不同,我们没有想到任何特殊信息。但是在输入“lo”(表示“登录”)后,系统崩溃了。所以“lo”(如“lo and behold”)是前两个字符通过互联网传输。回想起来,我们不可能想到更好的东西。
(这里是一些采访:wiki arpanet Kleinrock)
Telnet 早于 TCP/IP。RFC 15 谈到“网络原语”。由于 TCP/IP,telnet 只剩下处理“其余部分”,即应用层(RFC854 中的“协商选项”)。
是的,telnet 很简单,即使没有 TCP。RFC 15 以:
“协议”与 telnet 一起使用,因为本质上两个(不同的)系统需要用一种“语言”交谈。
RFC 854 也解释了这个概念:
telnet 协议是一个非常简单的协议,但它确实存在。“存在”是指 telnet 客户端不会将其接收到的所有字节都发送到读取它的程序(通常是外壳程序)。
协议是
所以是的,该协议非常简单,几乎是微不足道的。但这不是一个简单的 tcp 直通。
远程登录命令
如果您对可以发送到 telnet(而不是另一端的软件/服务器)的命令感兴趣,您可以阅读 RFC:https ://www.rfc-editor.org/rfc/rfc854 。基本上 telnet 定义:
请注意,上面的命令是专门的 Telnet 命令。它们不是任何客户端或服务器端终端协议的一部分。例如,如果你想在 unix 中终止一个进程,你通常会键入
ctrl-c
. 这通常作为0x03
终端发送到 shell,然后发送SIGINT
到 shell 正在运行的进程。但是 telnet 本身定义0xff, 0xf4
了发送SIGINT
到进程,这是一个 telnet 特定协议来执行它。同样,终端通常发送0x1b, 0x4b
以清除当前行,但 telnet 定义0xff, 0xf8
.现有的答案已经解释了,它
telnet
可以做的不仅仅是通过 TCP 流式传输输入数据不变;我想转而关注“我的哑聊天客户端程序不会创建 [应用程序] 协议”。-问题的一部分。(顺便说一句,对于实际的“TCP 上的原始数据”,您可以使用netcat
代替telnet
)仅仅因为一个协议简单或糟糕——甚至在任何地方都没有明确规定——并不意味着它不是一个协议。如果没有商定的协议,两个系统就无法通信,即使该协议只是“TCP 上的原始数据”。如果您尝试在一端使用 FTP,而在另一端使用 HTTP,那么您将遇到问题,无论哪种方式 - 如果您幸运的话,它不会起作用;如果你不是,你会得到错误的行为。(至少以前的情况是,如果您在与 HTTP 服务器相同的主机上的非标准端口上运行 FTP 守护程序,在某些浏览器上,您可以使用它来执行 XSS 并通过指向受害者的浏览器来绕过 CSRF 保护POST 到 FTP 服务器,它会在错误消息中回显 POST 的内容,浏览器会将其解释为 HTTP/0。
问“你所说的那个东西
my dumb chat client program
使用什么协议?我想从我的程序中与它交谈”是一个完全有效的问题。一个可能的答案是“TCP 上的原始数据”。然而,这并不是一个详尽的答案。select()
是针对错误的人数,大多数人似乎甚至不知道存在带外数据 - 但即使这是一个不好的例子,我希望你看到我在说什么)close()
还是用它shutdown()
来确保所有待处理的数据都首先交付?您说“为原始输入/输出打开 TCP 流套接字?这不是规则。”但真的是这样吗?“你必须使用 TCP”、“你必须按原样发送数据”和“你应该使用 XXX 端口”对我来说听起来像是规则。
(PS 我在写这篇文章时有点激动;这个答案的其余部分有点切题,并试图回答自然的后续问题“好吧,所以一切都是协议 - 这有什么影响吗?”)
因此,请记住,每次使两个系统相互通信时,您要么使用现有协议,要么创建新协议:在为时已晚之前记录您的协议!(即最迟,当协议变得不再简单,或者开始拥有更多的用户而不仅仅是开发人员时)例如。许多早期的 P2P 协议都说“官方客户端的来源是文档”,这在复杂的项目中非常烦人,只会导致客户端使用微妙的不同协议,并且在这里和那里不兼容。
有一句古老的格言“对你生产的东西要严格,对你接受的东西要自由” - 核心情绪很好,但坦率地说,至少它倾向于应用的方式,我认为这是废话,只会导致更多可维护性和兼容性问题。我说:详细说明(尝试考虑每一个极端情况),并且对您生产和接受的内容非常严格,您可能会预防/解决真正的错误。
一个关于严格解决错误的例子: 几年前,某个 bittorrent 跟踪器开始出现问题:他们的一些 torrent 无法在某些客户端上运行;客户没有抱怨,他们只是得到了不同的信息哈希,因此找不到种子。他们无法弄清楚发生了什么,并宣称受影响的客户一定是有问题的——停止使用它们。Bittorrent 使用非常严格且明确规定的编码 - 并且有充分的理由:相同的输入总是会产生完全相同的编码输出字符串,因此对于相同的输入数据,[info] 哈希总是相同的。当我第一次遇到其中一个种子时,我尝试将其输入到我自己的 - 非常严格的 - 解析器中,然后它马上说:
Error: Bencoded dictionary keys not sorted (last='sha1', key='private')
.所以发生了什么事?跟踪器开始自动将密钥“私有”添加到每个上传的 torrent,但只是将其添加到末尾,而不是像 bencoding 指定的那样保持字典键排序 - 它产生的内容并不严格。一些客户端仅部分解码了 torrent,并计算了未解码、未更改的字符串的哈希值。一些客户端解码了整个种子,并重新编码了他们需要散列的部分,在这个过程中正确地对键进行排序(创建一个有效的编码),并得到一个不同的散列。据我所知/回忆,没有客户抱怨数据无效——客户对他们接受的内容并不严格。那么,哪些哈希值是正确的?两者都不!两种计算哈希的方法都是正确的,但是种子被破坏了!如果即使是其中一位客户也会' 已经抱怨过,这个错误很可能会在同一天得到修复,而不是需要几个月的时间。(IIRC 几年后,我还在另一个完全不相关的跟踪器中报告了一个相同的错误;第一个的软件是内部制作的,所以它也不是相同的代码库)
另一个例子,这次是关于极端情况:eDonkey2000 P2P 协议使用了一个名为 ed2k 的自定义散列;规范没有指定在输入数据长度完全可被块大小整除的极端情况下的行为,因此产生了两个不兼容的变体,它们为某些文件产生不同的哈希,并且不兼容。(我说的是“规范”,但由于这是一个早期的 P2P 协议,我很确定没有规范;在对协议进行逆向工程时可能会遗漏特殊情况)
关于详细规范的必要性:很多时候,在尝试为协议编写客户端或为文件格式编写解析器时,我遇到过一种情况,即使是最初的程序员也不知道某些数据实际上是如何编码的,因为他们已经使用了一些库,但从未详细检查过它的实际作用。两个例子:
某个 API 使用自定义 UDP 协议,带有加密的 [可变长度] 数据包 - 文档指定 AES128,但没有提及使用的填充。当询问开发人员时,响应大致如下:
某个设备的网站对与他们的保存文件的接口有这样的说法:
(注意:但实际上,对足够的文件格式进行逆向工程以导出数据并不需要那么长时间)
其中一些示例是关于编码、文件格式或自定义算法(尽管仍然在协议内部使用,除了最后一个),但我想说所有这些都适用于所有这些。无论您谈论的是协议、文件格式、编码还是自定义算法: