前言
写这篇文章,主要用来作为学习总结昨儿笔记,好记性不如烂笔头,并非用作商业用途。参考资料:
- 掘进课程:Python异步网络编程实战
- 慕课网bobby老师课程(异步编程讲的比较详细推荐学习):Python3高级核心技术97讲
- 三次握手和四次挥详解:
网络的架构模型
下面是网络的五层架构,严格来说是七层架构,比较常见的就是这五层。
OSI层 | 功能 | TCP/IP 协议 |
---|---|---|
应用层 | 文件传输,电子邮件,数据服务 | HTTP,FTP,STMP,DNS,数据库访问等等 |
传输层 | 提供端对端的接口 | TCP、UDP |
网络层 | 为数据包选择路由 | IP、ICMP 等 |
数据链路层 | 传输有地址的帧、错误检测功能 | ARP |
物理层 | 物理媒体 | 1000BASE-SX等 |
而 Socket的含义是插座,就好比当作是我们电器设备和电源连接的一个接口,在我们的开发领域叫套字节,是两台服务器之间的网络 应用层和传输层的之间的传输接口。
socket的服务端和客户端交互图
注:客户端和服务端之间发送的数据都是字节类型的数据,所以字符串都需要进行编码成字节类型的数据
调用流程:
write()方法其实对应的是send()方法,发送数据
read()方法其实对应的是recv()方法,用来接收数据
三次握手流程图:
第一次握手
建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;(x 是随机生成的一个 int 数值)然后,客户端进入SYN_SEND状态,等待服务器的确认;
第二次握手
服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为 y (y 是随机生存的一个 int 数值);服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
第三次握手
客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。
四次挥手
第一次挥手:
Client (可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向 Server发送一个FIN报文段;此时,Client 进入FIN_WAIT_1状态;这表示 Client 没有数据要发送给 Server了;
客户端发送第一次挥手后,就不能在向 服务端发送数据了。
第二次挥手:
Server 收到了 Client 发送的FIN报文段,向 Client 回一个ACK报文段,Acknowledgment Number 为 Sequence Number 加 1;Client 进入 FIN_WAIT_2 状态;Server 告诉 Client ,我“同意”你的关闭请求;
Server 第一次响应后,还可以继续向 Client 发送数据,这里只是告诉 Client ,我收到你发送的关闭请求。
第三次挥手
Server 向 Client 发送 FIN 报文段,请求关闭连接,同时 Server 进入 CLOSE_WAIT 状态;
当 Server 的数据响应完成后,再告诉 Client,我这边也可以关闭请求了, 这时
Server 就不能再向 Client 发送数据了
第四次挥手
Client 收到 Server 发送的 FIN 报文段,向 Server 发送 ACK 报文段,然后 Client 进入
TIME_WAIT 状态;Server 收到 Client 的 ACK 报文段以后,就关闭连接;此时,Client
等待2MSL后依然没有收到回复,则证明 Server 端已正常关闭,那好,Client 也可以关闭连接了。
代码实现简单例子
主要用来展示服务端和客户端socket编程代码示例的演示
服务端单线程的代码实现
# --*-- conding:utf-8 --*--
# todo: tcp单线程的服务端测试
#
# @Time : 2024/6/21 23:20
# @Author : allen.huang
# @Email : hjc_042042@sina.cn
# @Software : PyCharm
import socket
# 预先定义好IP和端口
# ip为"",表示所有ip,也可以用0.0.0.0表示;port 为任意未被占用的端口
ip_port = ('', 9898)
# 定义 TCP 服务器的套字节
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 将IP 的地址和端口绑定到套字节上
server_socket.bind(ip_port)
# 开启服务端网络监听
server_socket.listen()
# 进入监听状态后,不断接收客户端的连接
while True:
# 启动服务器后,立即打印这语句,与客户端断开连接后也会打印该语句
print("waiting for connection...")
# todo 下一行为为阻塞代码,等待客户端的连接
# todo 服务器套字节的accept()方法会一直阻塞,直到有客户端连接上来
# todo 当成功接入客户端,accept()方法会返回一个临时的套字节和客户端的地址,来进行发送和接收数据
tcp_extension_socket, client_address = server_socket.accept()
# todo 如果此时另一个客户端连接上来,会阻塞等待上一个连接断开
# todo 连接成功后保持等待,前一个已经连接的客户端断开连接后,才会处理下一个客户端的连接。
print(f"连接成功后:{client_address}")
while True:
# todo 不断接收客户端发送的数据
# todo 此方法也是阻塞运行,每次接收固定大小的数据,直到全部接收完毕
# todo recv()的参数为数据缓冲区大小,表示一次最多接收多少字节的数据,默认是1024
data = tcp_extension_socket.recv(1024)
if not data:
break
# todo 打印接收到的数据data,data为二进制数据,需要转换为字符串,默认是 utf-8编码
print(f"收到数据:{data.decode()}")
# todo 发送数据给客户端
send_data = f"[Server] {data.decode()}"
tcp_extension_socket.send(send_data.encode())
# todo 循环结束后关闭临时套字节
tcp_extension_socket.close()
# todo 主动关闭服务器套字节
server_socket.close()
服务端的多线程代码实现
服务端主要采用多线程来处理客户端的连接
# --*-- conding:utf-8 --*--
# todo:多线程服务端,并发处理客户端连接
#
# @Time : 2024/6/22 01:14
# @Author : allen.huang
# @Email : hjc_042042@sina.cn
# @Software : PyCharm
import socket
import threading
# ip为"",表示所有ip,也可以用0.0.0.0表示;port 为任意未被占用的端口
ADDR = ('', 9898)
# 一次接收的数据大小,这里是1024个字节
BUFF_SIZE = 1024
# 声明一个套字节
tcp_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址信息
tcp_server.bind(ADDR)
# 监听服务端网络
tcp_server.listen()
def handle(sock, addr):
"""
出来每一个从客户端过来的数据
"""
while True:
# 套字节等待客户端的连接,使用recv接收数据这过程是阻塞的
# 阻塞等待期间,释放 CPU,CPU 可以执行其他线程的任务
data = sock.recv(BUFF_SIZE)
if not data:
sock.close()
break
print(f"[Server]收到数据:{data.decode()}")
send_data = "服务端应答;success".encode()
sock.send(send_data)
# 关闭临时字节
sock.close()
print('{}已关闭'.format(addr))
def main():
print("等待客户端连接...")
# todo 进入无线循环,接收客户端连接请求,就循环一次,创建子线程
# todo 这样就可以创建多线程来并发处理多个客户端请求
while True:
try:
# 接收每一个客户端连接,由于 accept是阻塞的,所以使用多线程来处理可以提高性能。
tcp_extension_socket, addr = tcp_server.accept()
except KeyboardInterrupt:
break
print('[Server]接收到来自{}的连接'.format(addr))
# 创建子线程
# 子线程的参数是一个函数,函数的参数是一个套接字和一个地址
t = threading.Thread(target=handle, args=(tcp_extension_socket, addr))
t.start()
# 循环结束,关闭服务端套字节,退出程序
print("\n Exit")
tcp_server.close()
if __name__ == '__main__':
main()
客户端代码实现
客户端的逻辑比服务端要简单。只要连接服务端的地址和端口,然后给服务端发送数据,接收服务端的反馈就行。
# --*-- conding:utf-8 --*--
# todo:tcp 的客户端连接测试
#
# @Time : 2024/6/21 23:20
# @Author : allen.huang
# @Email : hjc_042042@sina.cn
# @Software : PyCharm
import socket
# todo 定义客户端套字节
tcp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务端套字节
tcp_client.connect(('127.0.0.1', 9898))
# 不断发送给服务端数据
while True:
data = input('>>>')
if not data:
break
# 发送二进制数据,默认是二进制数据
tcp_client.send(data.encode())
# 接收服务端数据
data = tcp_client.recv(1024)
if not data:
break
print(data.decode())
# 关闭客户端套字节
# tcp_client.close()
查看单线执行效果图:
注意在使用时,需要先启动服务端,这样子客户端才能正常连接
如下这是服务端的效果图
客户端的交互图:
客户端输入一个,服务端就接收一个。在输入空字符,即回车的时候,就退出了
查看多线程执行效果图:
这是服务端的:
之后多个客户端连接进来,就可以看到多个的客户端id,并接收了不同的数据
下面就是3个客户端的发数据给服务端的情况
在客户端退出时,服务端就会显示这个提示
使用socket模拟http请求
可以通过开启一个 socket 的客户端来模拟 http 的请求,需要把 header 设置成固定的格式
...
client_socket.send(f'GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n'.encode('utf-8'))
....
在接受到数据超过1024字节时,可以通过拼接的方式来进行组装数据,直到数据请求完成:
...
# 接收响应内容,其实整个html响应结果肯定是大于1024字节的,所以我们需要用循环来接收
data = b""
while True:
response = client_socket.recv(1024)
if response:
data += response
else:
# 如果没有接受到数据了,就退出
break
...
完整代码实现
# --*-- conding:utf-8 --*--
# todo:模拟http客户端来请求百度网站
#
# @Time : 2024/5/30 02:33
# @Author : allen.huang
# @Email : hjc_042042@sina.cn
# @Software : PyCharm
import socket
from urllib.parse import urlparse
def parse_http(url):
# 通过socket模拟http请求获取html
url_info = urlparse(url)
# 获取url的host
host = url_info.netloc
# 获取url的path
path = url_info.path
# 如果path为空,则设置为'/',这是相对路径
if path == '':
path = '/'
# 创建socket连接
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((host, 80))
print("等待服务端响应...")
# 发送请求,这个格式是固定的
client_socket.send(f'GET {path} HTTP/1.1\r\nHost: {host}\r\nConnection: close\r\n\r\n'.encode('utf-8'))
# 接收响应内容,其实整个html响应结果肯定是大于1024字节的,所以我们需要用循环来接收
data = b""
while True:
response = client_socket.recv(1024)
if response:
data += response
else:
# 如果没有接受到数据了,就退出
break
# 解码
html_data = data.decode('utf-8')
# 关闭连接
client_socket.close()
# 返回响应
return html_data
if __name__ == '__main__':
# 获取html
html = parse_http('https://www.baidu.com/')
print(html)
pass
执行效果:
这是响应出来的结构,包含响应头和响应体
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » socket学习python版
发表评论 取消回复