1.面向连接的传输协议:TCP
对于 TCP
协议我不想说太多东西,这属于大学课程,又涉及计算机科学,而我不是“学院派”,对于这部分内容,我觉得作为开发人员,只需要掌握与程序相关的概念就可以了,不需要做太艰深的研究。
我们首先知道 TCP
是 面向连接
的,它的意思是说两个远程主机(或者叫进程,因为实际上远程通信是进程之间的通信,而进程则是运行中的 程序),必须首先进行一个握手过程,确认连接成功,之后才能传输实际的数据。比如说进程 A
想将字符串“It's a fine day today
” 发给进程 B
,它首先要建立连接。在这一过程中,它首先需要知道进程 B
的位置(主机地址和端口号)。随后发送一个不包含实际数据的请求报文,我们可以将这个报文称之为“hello
”。如果进程 B
接收到了这个“hello
”,就向进程 A
回复一个“hello
”,进程 A
随后才发送实际的数据 “It's a fine day today
”。
关于 TCP
第二个需要了解的,就是它是 全双工
的。意思是说如果两个主机上的进程(比如进程A
、进程B
),一旦建立好连接,那么数据就既可以由A
流向B
,也可以由B
流向A
。除此以外,它还是 点对点
的,意思是说一个 TCP
连接总是两者之间的,在发送中,通过一个连接将数据发给多个接收方是不可能的。TCP
还有一个特性,就是称为 可靠的数据传输
,意思是连接建立后,数据的发送一定能够到达,并且是有序的,就是说发的时候你发了 ABC
,那么收的一方收到的也一定是 ABC
,而不会是 BCA
或者别的什么。
编程中与 TCP
相关的最重要的一个概念就是 套接字
。我们应该知道网络七层协议,如果我们将上面的应用程、表示层、会话层笼统地算作一 层(有的教材便是如此划分的),那么我们编写的网络应用程序就位于应用层,而大家知道 TCP
是属于传输层的协议,那么我们在应用层如何使用传输层的服务呢 (消息发送或者文件上传下载)?大家知道在应用程序中我们用接口来分离实现,在应用层和传输层之间,则是使用套接字来进行分离。它就像是传输层为应用层开 的一个小口,应用程序通过这个小口向远程发送数据,或者接收远程发来的数据;而这个小口以内,也就是数据进入这个口之后,或者数据从这个口出来之前,我们 是不知道也不需要知道的,我们也不会关心它如何传输,这属于网络其它层次的工作。
举个例子,如果你想写封邮件发给远方的朋友,那么你如何写信、将信打包,属于应用层,信怎么写,怎么打包完全由我们做主;而当我们将信投入邮筒时, 邮筒的那个口就是套接字,在进入套接字之后,就是传输层、网络层等(邮局、公路交管或者航线等)其它层次的工作了。我们从来不会去关心信是如何从西安发往 北京的,我们只知道写好了投入邮筒就OK了。可以用下面这两幅图来表示它:
注意在上面图中,两个主机是对等的,但是按照约定,我们将发起请求的一方称为客户端,将另一端称为服务端。
可以看出两个程序之间的对话是通过套接字这个出入口来完成的,实际上套接字包含的最重要的也就是两个信息:连接至远程的本地的端口信息(本机地址和端口号),连接到的远程的端口信息(远程地址和端口号)。注意上面词语的微妙变化,一个是本地地址,一个是远程地址。
这里又出现了了一个名词 端口
。一般来说我们的计算机上运行着非常多的应用程序,它们可能都需要同远程主机打交道,所以远程主机就需要 有一个 ID
来标识它想与本地机器上的哪个应用程序打交道,这里的 ID
就是端口。将端口分配给一个应用程序,那么来自这个端口的数据则总是针对这个应用程序 的。有这样一个很好的例子:可以将主机地址想象为电话号码,而将端口号想象为分机号。
在 .NET
中,尽管我们可以直接对套接字编程,但是 .NET
提供了两个类将对套接字的编程进行了一个封装,使我们的使用能够更加方便,这两个类是 TcpClient
和 TcpListener
,它与套接字的关系如下:
从上面图中可以看出 TcpClient
和 TcpListener
对套接字进行了封装。从中也可以看出,TcpListener
位于接收流的位 置,TcpClient
位于输出流的位置(实际上 TcpListener
在收到一个请求后,就创建了 TcpClient
,而它本身则持续处于侦听状态,收 发数据都可以由 TcpClient
完成。这个图有点不够准确,而我暂时没有想到更好的画法,后面看到代码时会更加清楚一些)。
我们考虑这样一种情况:两台主机,主机 A
和主机 B
,起初它们谁也不知道谁在哪儿,当它们想要进行对话时,总是需要有一方发起连接,而另一方则需要对本机的某一端口进行侦听。而在侦听方收到连接请求、并建立起连接以后,它们之间进行收发数据时,发起连接的一方并不需要再进行侦听。因为连接是全双工的,它可以使用现有的连接进行收发数据。
而我们前面已经做了定义:将发起连接的一方称为客户端,另一段称为服务端,则现在可以得出:总是服务端在使用TcpListener类,因为它需要建立起一个初始的连接。