UDP通信模型中,在通信开始之前,不需要建立相关的链接,只需要发送数据即可,类似于生活中的“写信”。
TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中的"打电话"。
TCP在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接。
连接的建立通过三次握手,释放则需要四次握手,所以说每个连接的建立都是需要资源消耗和时间消耗的。
2MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态。当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间。等待2MSL时间主要目的是怕最后一个 ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。
在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。
不过在实际应用中可以通过设置 SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。
建立连接——数据传输——关闭连接 ... 建立连接——数据传输——关闭连接
建立连接——数据传输 ...(保持连接)... 数据传输——关闭连接
长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。对于频繁请求资源的客户来说,较适用长连接。
client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个TCP连接都需要三次握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,再次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,而且频繁的socket创建也是对资源的浪费。
而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知。所以短连接多用于并发量大,但每个用户无需频繁操作的情况。
在生活中,如果想让别人能更够打通咱们的电话获取相应服务的话,需要做以下几件事情:
如同上面的电话机过程一样,在程序中,如果想要完成一个tcp服务器的功能,需要的流程如下:
示例:
1 import socket 2 3 # 创建socket 4 tcpSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 6 # 绑定本地信息 7 address = ("", 7788) 8 tcpSocket.bind(address) 9 10 # 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的连接的 11 tcpSocket.listen(5) 12 13 # 当有新的客户端来连接服务器时,就会产生一个新的套接字来专门为这个客户端服务 14 # newSocket就是用来为这个新来的客户端服务的 15 # tcpSocket就可以省下来专门等待其他新客户端的连接 16 newSocket, clientAddr = tcpSocket.accept() 17 18 # 接收对方发送过来的数据,最大接收1024个字节 19 recvData = newSocket.recv(1024) 20 print("接收到数据为:", recvData.decode()) 21 22 # 发送一些数据给客户端 23 newSocket.send("Thank you!".encode()) 24 25 # 关闭为这个客户端服务的套接字。只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接 26 newSocket.close() 27 28 # 关闭监听套接字。只要这个套接字关闭了,就意味着整个程序不能再接收任何新的客户端的连接 29 tcpSocket.close()
运行效果:
TCP客户端要比服务器端简单很多。如果说服务器端是需要自己买手机、查手机卡、设置铃声、等待别人打电话流程的话,那么客户端就只需要找一个电话亭,拿起电话拨打即可,流程要少很多。
示例:
1 import socket 2 3 # 创建socket 4 tcpClientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 6 # 连接服务器端 7 serAddress = ("192.168.3.4", 7788) 8 tcpClientSocket.connect(serAddress) 9 10 # 提示用户输入数据 11 sendData = input("请输入要发送给服务器的数据:") 12 13 tcpClientSocket.send(sendData.encode()) 14 15 # 接收对方发送过来的数据,最大接收1024个字节 16 recvData = tcpClientSocket.recv(1024) 17 print("接收到数据为:", recvData.decode()) 18 19 # 关闭监听套接字。只要这个套接字关闭了,就意味着整个程序不能再接收任何新的客户端的连接 20 tcpClientSocket.close()
运行效果:
1 import socket 2 3 # 创建socket 4 tcpServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 6 # 绑定本地信息 7 address = ("", 7788) 8 tcpServerSocket.bind(address) 9 10 # 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的连接了 11 tcpServerSocket.listen(5) 12 13 while True: 14 15 # 如果有新的客户端来连接服务器,那么就产生一个新的套接字专门为这个客户端服务器 16 # newSocket用来为这个客户端服务 17 # tcpSerSocket就可以省下来专门等待其他新客户端的连接 18 newSocket, clientAddr = tcpServerSocket.accept() 19 20 while True: 21 22 # 接收对方发送过来的数据,最大接收大小为2014字节 23 recvData = newSocket.recv(1024).decode() 24 25 # 如果对方发送“88”,则意味着客户端关闭连接 26 if recvData == "88": 27 print("Receive:", recvData) 28 print("----关闭聊天----") 29 break 30 else: 31 print("Receive:", recvData) 32 sendData = input("Send:") 33 newSocket.send(sendData.encode()) 34 35 # 关闭这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了 36 # 如果还需要服务,只能再次重新连接 37 newSocket.close() 38 39 # 关闭监听套接字,只要这个套接字关闭了,就意味着整个程序不能再接收任何新的客户端的连接 40 tcpSerSocket.close()
1 import socket 2 3 # 创建socket 4 tcpClientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 6 # 连接服务器端 7 serAddress = ("localhost", 7788) 8 tcpClientSocket.connect(serAddress) 9 10 while True: 11 12 # 提示用户输入数据 13 sendData = input("Send:") 14 15 tcpClientSocket.send(sendData.encode()) 16 if sendData == "88": 17 break 18 else: 19 # 接收对方发送过来的数据,最大接收1024个字节 20 recvData = tcpClientSocket.recv(1024) 21 print("Receive:", recvData.decode()) 22 23 # 关闭监听套接字。只要这个套接字关闭了,就意味着整个程序不能再接收任何新的客户端的连接 24 tcpClientSocket.close()
原文:https://www.cnblogs.com/juno3550/p/12521478.html