3次握手4次挥手概述
1.回忆关于udp和tcp相应的部分知识点
2.三次握手
3.四次挥手
1.回忆关于udp和tcp相应的部分知识点
- udp : 不稳定 面向无连接。 且主机发送一条数据,本身并不知道对方有没有收到
- tcp: 稳定 面向有连接。 tcp是操作系统和操作系统之间打招呼,发送一条数据给对方,本身是知道对方有没有收到的
问题在于 这些特点的原理是什么?
原因是:
udp直接发送
tcp先要经过3次握手才会发送。
2.三次握手(打开连接,准备资源)
syn标记请求的数据
ack标记应答的数据
如何实现:
-
第一次握手 客户端发送数据给服务器端(目的让服务器端准备好资源)属于syn
-
第二次握手 服务器端做了两件事。
1.给客户端回送数据(让客户端知道我服务器端准备好了)
2.发送数据让客户端准备好数据--------->这里connect解堵塞
1属于ack,回应上一步客户端发的syn
2属于syn 发送给客户端数据
这里是两步合在一起了 -
第三次握手 客户端给服务器端发送数据(让服务器端知道我客户端准备好了)属于ack,回应上一步服务器端发的syn
得到结论:udp不稳定,数据可能丢失,因为udp属于直接发送,没有回应并不知道对方有没有接收到。比如断线了,系统故障了,导致没有发出去的话,并不知道对方有没有接收到。
tcp稳定,数据不太可能丢失,tcp在连接的时候是操作系统之间会有通信,因为有syn就有ack回应,当收到回应就明白对方已经接收到。
3.四次挥手(断开连接,释放资源)
先明白:套接字有两个通道, 一个收道 一个发道 关闭两个才是真的关闭
如何实现:
-
第一次挥手客户端调用tcp_socket.close() 客户端告诉服务器不会再给服务器发送任何数据(自己关闭发道)属于syn
-
第二,三次挥手 服务器做了两件事情
第二次挥手:1.给客户端发送数据说我收到了
第三次挥手:2.服务器端调用new_socket.close(),给客户端发送数据表示不会给你发数据,让客户端关收道。准备一个超时时间。
1属于ack,回应上一步客户端发的syn
2属于syn 发送给客户端数据
这里是跟3次握手有区别,两步不能合在一起 -
第四次挥手
让双方把准备的资源都释放了属于ack,回应上一步服务器端发的syn
两个问题
- 1.为什么客户端先调close()
原因在于最后,如何确认两边都收到了信息,可以在一个时间段都释放掉资源。
想象一个循环:
客户端给服务器端发送数据syn表示想结束聊天了,
服务器端为了让客户端知道自己收到了给客户端发送一个回应ack,
但是服务器端不知道客户端是否收到,你敢关闭资源吗?
比如说因为网络问题,客户端没有收到。你关闭了资源。
得,客户端停了老半天,以为你没收到,又发信息,结果你关闭资源了,这不坑人嘛。
所以客户端也得回服务器端一个消息表示自己收到了,然后问题来了,客户端没收到回应敢关资源吗?万一服务器端也突然断下网呢,
同样的道理,服务器端又发送,哦 我收到了。 客户端, 哦 我收到了 ,服务器端,客户端…
就这样陷入死循环了。。。
所以怎么解决这个问题?
谁先调close() 在收到对方传来的syn的时候,谁的资源就会保存2-5分钟。
客户端先调用资源,
所以在第三次挥手服务器端调用close()发送一个syn的时候,当客户端收到这个syn请求数据,会执行第四次挥手回应,
但服务器端怎么知道客户端收到了呢,
这时候服务器端会设置一个超时时间,比如4s,超过这个时间4s,我就再发,超过再发,直到接收到回应就关闭资源。
然后客户端在收到这个syn请求的时候发送一个ack,然后停留2-5分钟后再关闭资源。
谁最后调用close会有超时时间
如果超时了的话,会再次发送数据
服务器端是绑定的固定的ip和端口,你不释放资源,停留2分钟及以上,你还工不工作了。
客户端则不同,我端口是随时可以变的啊,我一个端口不释放资源停留,我换一个就好了。
所以这就是为什么客户端先调close()
- 2.二次挥手和三次挥手为什么不像3次握手一样中间合在一起。
因为第三次挥手是调用close(),我不调用也可以啊,我不调用,你能合在一起使用吗,肯定不行啊。
返回浏览器需要的HTTP页面
1.创建套接字
2.绑定端口
3.转为接听模式
4.客户端连接
5.接收数据
6.发送数据
7.关闭套接字
1–4过程
# 打开套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定端口
localaddr = ("",8080)
tcp_server_socket.bind(localaddr)
# 转为接听模式
tcp_server_socket.listen(128)
# 客户端连接
new_socket,client_addr = tcp_server_socket.accept()
5过程中接收到的数据是
GET /1.html HTTP/1.1
Host: 192.168.79.1:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: Hm_lvt_6bcd52f51e9b3dce32bec4a3997715ac=1551868678
Upgrade-Insecure-Requests: 1
利用正则表达式将 /1.html 提取出来
先用splitlines将接收到的代码切割成很多部分
['GET /1.html HTTP/1.1', 'Host: 192.168.79.1:8080', 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0', 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 'Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'Accept-Encoding: gzip, deflate', 'Connection: keep-alive', 'Cookie: Hm_lvt_6bcd52f51e9b3dce32bec4a3997715ac=1551868678', 'Upgrade-Insecure-Requests: 1', '']
再用正则match将第一组内的 /1.html 提取
ret = re.match(r"[^/]+(/[^ ]*)",request_lines[0])
if ret:
html_name = ret.group(1)
print (html_name)
然后发送数据分为两部分
第一部分
打开本地的/1.html文件的代码 复制下来用一个html_content来存储
第二部分
用一个response来存储"HTTP /1.1 200 OK\r\n"
response +="\r\n"
注意html是二进制,response是字符串
所以应该分别发送:
new_socket.send(response.encode("utf-8"))
new_socket.send(html_content)
总代码:
import socket
import threading
import re
def client_socket(new_socket):
recv_data = new_socket.recv(1024).decode("utf-8")
print (recv_data)
request_lines = recv_data.splitlines()
print(">"*20)
print (request_lines)
# 用正则表达式把recv_data的值提取出来
ret = re.match(r"[^/]+(/[^ ]*)",request_lines[0])
if ret:
html_name = ret.group(1)
print (html_name)
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
try:
f = open("." + html_name,"rb")
except:
response = "HTTP/1.1 404 NOT found\r\n"
response += "\r\n"
response += "-----file not found----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
new_socket.send(response.encode("utf-8"))
new_socket.send(html_content)
new_socket.close()
def main():
# 打开套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 绑定端口
localaddr = ("",8080)
tcp_server_socket.bind(localaddr)
# 转为接听模式
tcp_server_socket.listen(128)
# 客户端连接
new_socket,client_addr = tcp_server_socket.accept()
# 打开一个线程给客户端
t1 = threading.Thread(target=client_socket,args=(new_socket,))
t1.start()
if __name__ == '__main__':
main()
先执行py程序,然后打开网页进行连接就可以了