应用层:TCP 套接字编程
TCP 协议
TCP 协议给使用者提供了两种服务,分别是面向连接的服务和可靠的数据传输服务,我们简单介绍一下。简单来说面向连接的含义是客户机和服务器之间需要建立连接,在位于应用层的数据开始交互之前,TCP 协议首先要先让客户机和服务器进行一次握手。这么做相当于告知彼此接下来需要进行数据的交互,请双方做好准备,具体的实现方式为双方在传输层交互信息。握手阶段结束之后,就能够建立起 TCP 连接,双方应用程序的数据交互完毕之后,还需要切断这个连接。
所谓 TCP 连接是一种逻辑上的连接,它可以提供全双工服务,即应用层数据可以在建立连接的进程之间相互传输。TCP 连接也是点对点的,因为这是针对单个发送方和单个接收方之间的连接。
接下来再看看何谓可靠的数据传输服务,即双方的数据交互在 TCP 协议的保障下,彼此发出的数据能够无差别、按照正确顺序传递给对方。即 TCP 会将发送方通过套接字传出的字节流,在保证字节不发生丢失和出现多余的情况下,将字节流交付给接收方的套接字。
套接字编程(TCP)
由于 TCP 是个面向连接的协议,因此我们就不能简单地直接把工作扔给套接字啦,根据上文的介绍,我们需要先在客户机和服务器之间进行握手,建立 TCP 连接。要创建这个连接,我们就需要将客户机和客户端的 IP 地址和端口号关联到连接上,以此达到关联双方的套接字的目的。
这对服务器和客户机提出了什么要求呢?对于服务器而言,为了能够与客户机的握手阶段达成联系,服务器进程需要能够准备好接受这样的信息,即服务器进程要先开始运行,同时为了能识握手的数据信息,服务器的套接字需要具备这样的功能,我们成这样的套接字叫做欢迎套接字。而客户机呢?客户机需要拥有发起握手的请求的能力。
接下来我们来谈谈 TCP 连接的建立,由于握手是由客户机发起的,因此这个连接的建立是由客户机创建一个 TCP 套接字。这个套接字需要指定服务器具有服务器欢迎套接字的 IP 地址和端口号,紧接着客户机将在运输层发起一个三次握手,当三次握手结束之后 TCP 连接就会被建立。
在三次握手的时候,若欢迎套接字接收到了握手的消息,就会生成一个新的他扑街仔,这个新套接字用于专门对接客户机的套接字,被称之为连接套接字。有了连接套接字,客户机和服务器就像连接了一条管道一样进行数据的交互了。
打招呼!(程序需求)
我们做个基础的玩意,让客户端和服务器互相打个招呼吧!
- 显示提示信息,客户从利用键盘输入自己的名字;
- 通过程序,将该数据向服务器发送;
- 服务器接收该数据并生成一段打招呼信息,例如 “Hello!”;
- 服务器将数据发回给客户;
- 客户接收数据并显示返回的数据。
- 下文使用 Python 实现。
TCPClient
我们一步一步来,“巧妇难为无米之炊”,首先我需要一些工具来专门制造套接字,我们就没必要造轮子啦。在 Python 中有个模块是专门用来做这件事情的,名为 “socket” 我们先把它包括进来。
from socket import *
接下来我需要交代接收分组的目的主机,那就分别造 2 个变量来存储就行了。
Name = '名字' #用字符串来存目的地址,“名字”需要替换为目的地的 IP 地址或域名
Port = 端口号 #“端口号”需要替换为一个存在的端口,例如 12000,注意不要“串线”
好,接下来我们需要生成一个套接字来工作,直接看代码吧。不过我们现在看这段代码可能是一头雾水的,这是啥?别急,我们学到网络层的时候就会认识 “AF_INET” 和 “SOCK_STREAM” 这两个东西了。简单地说,这段代码制造了一个 TCP 类型的套接字,我们直接先记下来就行。
Socket = socket(AF_INET,SOCK_STREAM)
接下来我们就要像要求所说的那样,让客户输入他的名字。软件的交互性要好,我们也可以来点提示信息。
print("现在向服务器打个招呼吧!")
sentence = input("Nice to meet you! My name is ")
由于客户机向服务器发送数据时,必须要先在客户机与服务器之间建立一个 TCP 连接,因此我们使用 “.connect()” 方法向服务器发起连接,该方法的参数就是服务器的端地址和对应的端口。执行这句代码之后,经过三次握手就使 TCP 连接连起来了。
Socket.connect((Name,Port))
和 UDP 不同之处在于,我们想要传输的数据不需要结合服务器的 IP 地址打包,因为我们已经经过了三次握手,可以直接利用建立的连接来传输数据。“.send()”方法能够将数据提交到 TCP 连接上,向服务器发送。
Socket.send(sentence.encode())
现在我们发送数据的操作就结束了,接下来就需要接受数据。等待服务器将数据发回来,我们使用“.recv()”方法可以帮助我们接受,然后我们打印出来。
sentence = Socket.recv(1024)
print(sentence.decode())
还记得我们读取文件时,完成操作后要做什么吗?那就是关闭文件,这是一个好习惯。对于套接字也是如此,我们也要养成好习惯,一个进程使用完了之后就把它关闭掉。“.close()” 方法就能实现这个功能了。到此,程序编写结束!
Socket.close()
TCPServer
刚刚我们写的是客户端的 Python 程序,现在我们来写一下服务器端的程序。一步一步来,首先我们还是先把 “socket” 模块包括进来。
from socket import *
接下来我们要指定一下端口号,表示我服务器的套接字只接受某个端口传来的信息,体现一个“门当户对”!我们用一个变量来存下端口号,然后和上文一样,也得造一个套接字出来,我们通过 “.bind()” 方法把端口号绑定给这个套接字。注意这个时候造的是欢迎套接字。
Port = 端口号 #“端口号”需要替换为一个存在的端口,例如 12000,注意不要“串线”
serverSocket = socket(AF_INET,SOCK_STREAM)
Socket.bind(('',Port))
当我们创建好欢迎套接字之后,就要准备好接收握手的请求了。使用“.listen()”方法可以接受这样的请求,方法的参数定义的是请求连接的最大数(至少为 1)。
Socket.listen(1)
为了表示上述操作顺利实现,我们输出一个提示信息。
print("准备就绪,可以接收分组!")
作为一个服务器,我们肯定不是仅仅接收一次分组啦,肯定是不停地准备着相应送来的报文啦,也就是说我这个服务器是要持续保证在工作状态。我们使用一个死循环 “while true” 实现,注意这个时候死循环是不会太消耗资源的,因为接下来的语句之后读取到了分组之后才会有所行动。
当客户机开始发送握手请求时,欢迎套接字可以使用“.accept()”来接受这个请求,同时生成一个“连接套接字”给这个客户机单独使用,握手完成后就可以通过建立的 TCP 连接进行信息交互了。
while True:
connectionSocket,addr = Socket.accept()
sentence = connectionSocket.recv(1024).decode()
backSentence = "Hello, " + sentence.upper() + "! My name is Han Meimei."
connectionSocket.send(backSentence.encode())
connectionSocket.close()
每完成和客户机的信息交互,就要把连接套接字关闭,但是服务器可以保持打开状态,以此接收下一次握手的请求。有小伙伴会问了,那这个死循环什么时候停止呢?那多简单,不想用的时候直接把程序关了就好了喽!
程序测试
由于我没有小伙伴陪我,我只好在一台机子做测试喽。前文提到我们需要保证服务器程序先保持运行状态,再发送字节,如果服务器没运行呢?
“积极拒绝!”由此可见,没有运行服务器程序的话是不行的。所以我们先把服务端程序 “TCPServer.py” 启动起来。
看到提示信息,证明服务器启动很成功,接下来启动客户端程序。
这回总算可以了,我们开始打招呼吧!
程序完成了我们的需求!
参考资料
《计算机网络》 谢希仁 编著,电子工业出版社
《计算机网络 自顶向下方法》 [美] James F.Kurose,Keith W.Ross 著,陈鸣 译,机械工业出版社
关于TCP三次握手,这是我见过最好的解读了,通俗易懂
TCP的三次握手与四次挥手理解及面试题(很全面)
TCP三次握手详解
TCP 为什么是三次握手,而不是两次或四次?