Learning-Python【28】:基于TCP协议通信的套接字
什么是 Socket
Socket 是应用层与 TCP/IP 协议通信的中间软件抽象层,它是一组接口。在设计模式中,Socket 其实就是一个门面模式,它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面,对用户来说,一组简单的接口就是全部,让 Socket 去组织数据,以符合指定的协议。
所以,我们无需深入理解 TCP/UDP 协议,socket 已经为我们封装好了,我们只需要遵循 socket 的规定去编程,写出的程序自然就是遵循 TCP/UDP 标准的。
套接字的分类:
基于文件类型的套接字家族:AF_UNIX(在 Unix 系统上,一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程同时运行在同一机器,可以通过访问同一个文件系统间接完成通信)
基于网络类型的套接字家族:AF_INET(Python 支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我们只使用 AF_INET)
基于 TCP 协议的 socket
工作流程:
下面我们举个打电话的小例子来说明一下
如果你要给你的一个朋友打电话,先拨号,朋友听到电话铃声后提起电话,这时你和你的朋友就建立起了连接,就可以讲话了。等交流结束,挂断电话结束此次交谈。 生活中的场景就解释了这工作原理。
(如果你去一家餐馆吃饭,假设那里的老板就是服务端,而你自己就是客户端,当你去吃饭的时候,你肯定的知道那个餐馆,也就是服务端的地址,但是对于你自己来说,餐馆的老板不需要知道你的地址)
服务端:
1)创建套接字描述符(socket)
2)设置服务器的 IP 地址和端口号(需要转换为网络字节序的格式)
3)将套接字描述符绑定到服务器地址(bind)
4)将套接字描述符设置为监听套接字描述符(listen),等待来自客户端的连接请求,监听套接字维护未完成连接队列和已完成连接队列
5)从已完成连接队列中取得队首项,返回新的已连接套接字描述符(accept),如果已完成连接队列为空,则会阻塞
6)从已连接套接字描述符读取来自客户端的请求(read / recv)
7)向已连接套接字描述符写入应答(write / send)
8)关闭已连接套接字描述符(close),回到第 5 步等待下一个客户端的连接请求
服务端必须满足至少三点:
1)绑定一个固定的 IP 和端口号
2)一直对外提供服务,稳定运行
3)能够支持并发
客户端:
1)创建套接字描述符(socket)
2)设置服务器的 IP 地址和端口号(需要转换为网络字节序的格式)
3)请求建立到服务器的 TCP 连接并阻塞,直到连接成功建立(connect)
4)向套接字描述符写入请求(write / send)
5)从套接字描述符读取来自服务器的应答(read / recv)
6)关闭套接字描述符(close)
import socket
socket.socket(socket_family, socket_type, proto=0)
socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。proto 一般不填,默认值为 0。 获取TCP/IP套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 获取UDP/IP套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
socket模块函数用法
import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 1. 服务端套接字函数
phone.bind('主机ip地址', 端口号) # 绑定到(主机,端口号)套接字
phone.listen() # 开始TCP监听
phone.accept() # 被动接受TCP客户的连接,等待连接的到来
服务端套接字函数
# 2. 客户端套接字函数
import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 买手机
phone.connect() # 主动连接服务端的ip和端口
phone.connect_ex() # connect()函数的扩展版本,出错的时候返回错码,而不是抛出异常
客户端套接字函数
# 3. 服务端和客户端的公共用途的嵌套字函数
phone.recv() # 接受TCP数据
phone.send() # 发送TCP数据
phone.recvfrom() # 接受UDP数据
phone.sendto() # 发送UDP数据
phone.getpeername() # 接收到当前套接字远端的地址
phone.getsockname() # 返回指定套接字的参数
phone.setsockopt() # 设置指定套接字的参数
phone.close() # 关闭套接字
服务端和客户端的公共用途的嵌套字函数
# 面向锁的套接字方法
phone.setblocking() # 设置套接字的阻塞与非阻塞模式
phone.settimeout() # 设置阻塞套接字操作的超时时间
phone.gettimeout() # 得到阻塞套接字操作的超时时间
面向锁的套接字方法
# 面向文件的套接字函数
phone.fileno() # 套接字的文件描述符
phone.makefile() # 创建一个与该套接字相关的文件
面向文件的套接字函数
TCP是基于链接的,必须先启动服务器,然后再启动客户端去链接服务端
简单版
import socket # 1. 创建套接字描述符, 用来建立链接
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(phone) # 2. 设置IP和端口号, 绑定套接字描述符
phone.bind(("127.0.0.1", 8080)) # 3. 将套接字描述符设置为监听状态, 设置同一时刻最大请求数为5
phone.listen(5) print("start...")
# 4. 等待来自客户端的连接
conn, client_addr = phone.accept()
# accept有返回值,是一个元组
# 元组的第一个参数是双向链接的套接字对象(即三次握手的结果), 用来收发消息
# 第二个参数是一个元组,存放客户端的IP和端口号
# print(conn)
# print(client_addr) # 5. 收/发消息, 1024是接收的最大字节数bytes
data = conn.recv(1024)
print("收到客户端的数据", data)
conn.send(data.upper()) # 6. 关闭双向链接的套接字对象
conn.close() # 7. 关闭套接字描述符
phone.close()
服务端
import socket # 1. 创建套接字描述符
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 连接服务端的IP地址和端口号
phone.connect(("127.0.0.1", 8080)) # 3. 发/收消息
phone.send("hello".encode("utf-8")) # 只能发bytes类型
data = phone.recv(1024)
print("收到服务端的消息", data) # 4. 关闭套接字描述符
phone.close()
客户端
由于 socket 模块中有太多的属性。在这里破例使用了 'from module import *' 语句。使用 'from socket import *',就把 socket 模块里的所有属性都带到命名空间里了,这样能大幅减短代码。
例如 tcpSock = socket(AF_INET, SOCK_STREAM)
通信循环
from socket import * server = socket(AF_INET, SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5) conn, client_addr = server.accept() # 通信循环
while True:
data = conn.recv(1024)
conn.send(data.upper()) conn.close()
server.close()
服务端
from socket import * client = socket(AF_INET, SOCK_STREAM)
client.connect(("127.0.0.1", 8080)) # 通信循环
while True:
msg = input("请输入: ").strip()
client.send(msg.encode("utf-8"))
data = client.recv(1024)
print(data) client.close()
客户端
但是这样写有一个 bug,当你手动结束客户端的程序运行时,服务端也会跟着崩溃
因为 conn 代表的是一个双向连接,只有服务端和客户端都正常运行的时候,conn 才有意义,然而此时客户端是非正常的断开,服务端还在使用没有意义的 conn 做 recv 操作,无法收到消息,所以在 Windows 上直接崩溃,而在 Linux 上,相同的操作服务端会一直处于收空的状态
补救措施是,在 Windows 系统上捕捉异常,在 Linux 系统上加上判断
from socket import * server = socket(AF_INET, SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5) conn, client_addr = server.accept() # 通信循环
while True:
try:
data = conn.recv(1024)
# 针对Linux系统
if len(data) == 0:
break
conn.send(data.upper()) except ConnectionResetError:
break conn.close()
server.close()
服务端
from socket import * client = socket(AF_INET, SOCK_STREAM)
client.connect(("127.0.0.1", 8080)) # 通信循环
while True:
msg = input("请输入: ").strip()
client.send(msg.encode("utf-8"))
data = client.recv(1024)
print(data) client.close()
客户端
链接通信循环
这样虽然解决了崩溃问题,但是当手动结束客户端时,服务端还是会跟着结束,所以在服务端等待客户端的连接前加上循环,从而达到 “链接 + 通信” 循环
from socket import * server = socket(AF_INET, SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5) # 链接循环
while True:
conn, client_addr = server.accept() # 通信循环
while True:
try:
data = conn.recv(1024)
# 针对Linux系统
if len(data) == 0:
break
conn.send(data.upper()) except ConnectionResetError:
break conn.close()
server.close()
服务端
from socket import * client = socket(AF_INET, SOCK_STREAM)
client.connect(("127.0.0.1", 8080)) # 通信循环
while True:
msg = input("请输入: ").strip()
client.send(msg.encode("utf-8"))
data = client.recv(1024)
print(data) client.close()
客户端
但这样做,服务端每次只能针对于一个客户端,只有当这个客户端的收发消息结束后才能给下一个客户端服务,无法达到并发的效果,这个后面学到并发时再讲
其实还有一个问题,当客户端传一个空消息时,会发生阻塞状态,因为发空的时候服务端时无法收到的(空时是什么都没有),服务端收不到,无法返回给客户端,所以客户端处于阻塞状态。补救方法是不让客户端输入空
from socket import * client = socket(AF_INET, SOCK_STREAM)
client.connect(("127.0.0.1", 8080)) # 通信循环
while True:
msg = input("请输入: ").strip()
if len(msg) == "":
continue
client.send(msg.encode("utf-8"))
data = client.recv(1024)
print(data) client.close()
客户端
from socket import * server = socket(AF_INET, SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5) # 连接循环
while True:
conn, client_addr = server.accept() # 通信循环
while True:
try:
data = conn.recv(1024)
# 针对Linux系统
if len(data) == 0:
break
conn.send(data.upper()) except ConnectionResetError:
break conn.close()
server.close()
服务端
模拟ssh实现远程执行命令
当使用客户端远程连接服务器时,在客户端上执行命令,服务器会返回命令执行的结果给客户端,那么该如何实现呢?
from socket import *
import subprocess server = socket(AF_INET, SOCK_STREAM)
server.bind(("127.0.0.1", 8080))
server.listen(5) # 连接循环
while True:
conn, client_addr = server.accept() # 通信循环
while True:
try:
cmd = conn.recv(1024) # cmd = b'dir'
# # 针对Linux系统
if len(cmd) == 0:
break
# 命令的执行结果
obj = subprocess.Popen(cmd.decode("utf-8"),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
conn.send(stdout + stderr) except ConnectionResetError:
break conn.close()
server.close()
服务端
import socket # 1. 创建套接字描述符
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 连接服务端的IP地址和端口号
phone.connect(("127.0.0.1", 8080)) # 3. 发/收消息
phone.send("hello".encode("utf-8")) # 只能发bytes类型
data = phone.recv(1024)
print("收到服务端的消息", data) # 4. 关闭套接字描述符
phone.close()
客户端
但是目前这样有一个局限性,我将接收端数据的最大字节数设置为1024,当发送端发的数据量小于接收端的1024时,可以被完全接收,但是发送端的数据量大于1024时,就只能接收1024条数据,那么多出的那些数据该如何处理呢?
首先客户端发送一条执行命令给服务端,让服务端接收,这里命令的字节数大多数情况不会大于1024,所以可以被完全接收,暂不考虑,当服务端接收了命令执行后,会将命令的执行结果发送给客户端,让客户端接收,这里命令的执行结果是很有可能大于1024个字节的,例如:tasklist,在终端上显示的最后一条是自己,而在上面所写的两个文件中只能显示几条结果,很显然是大于1024的
但这时再输入 dir 时,竟然是 tasklist 没有执行完的继续显示,再输入其它命令,还是 tasklist 没有执行完的继续显示,这发生了什么?
这就是待解决的粘包问题,下一节将会学习
Learning-Python【28】:基于TCP协议通信的套接字的更多相关文章
- Learning-Python【30】:基于UDP协议通信的套接字
UDP协议没有粘包问题,但是缓冲区大小要足够装数据包大小,建议不要超过 512 服务端: # 服务端 import socket server = socket.socket(socket.AF_IN ...
- 基于TCP协议的socket套接字编程
目录 一.什么是Scoket 二.套接字发展史及分类 2.1 基于文件类型的套接字家族 2.2 基于网络类型的套接字家族 三.套接字工作流程 3.1 服务端套接字函数 3.2 客户端套接字函数 3.3 ...
- python中基于tcp协议的通信(数据传输)
tcp协议:流式协议(以数据流的形式通信传输).安全协议(收发信息都需收到确认信息才能完成收发,是一种双向通道的通信) tcp协议在OSI七层协议中属于传输层,它上承用户层的数据收发,下启网络层.数据 ...
- 基于TCP连接的socket套接字编程
基于TCP协议的套接字编程(简单) 服务端 import socket server = socket.socket() server.bind( ('127.0.0.1', 9999) ) serv ...
- python基础22------python基础之基于tcp和udp的套接字
一.TCP套接字 1.low版tcp套接字 服务器端 客户端 2.改进版tcp套接字 服务端 客户端 二.UDP的套接字 服务器 客户端 注:udp的套接字可以支持多个客户端同时访问,但tcp套接字就 ...
- Python 31 TCP协议 、socket套接字
1.TCP协议 可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割. (1)三次握手建链接( ...
- 网络编程之基于tcp和udp的套接字
一 udp协议网络编程 DNS:将域名解析成ip地址 SOCK_DGRAM:数据报协议,也是udp协议 udp协议的网络编程的一些用法: recvfrom:接收消息,接收的时一个元组,元组里面的元 ...
- 基于UDP协议的socket套接字编程
目录 一.UDP套接字简单示例 1.1 服务端 二.客户端 三.UPD套接字无粘包问题 3.1 服务端 3.2 客户端 四.qq聊天 4.1 服务端 4.2 客户端1 4.3 客户端2 4.4 运行结 ...
- python 30 基于TCP协议的socket通信
目录 1. 单对单循环通信 2. 循环连接通信:可连接多个客户端 3. 执行远程命令 4. 粘包现象 4.1 socket缓冲区 4.2 出现粘包的情况: 4.3 解决粘包现象 bytes 1. 单对 ...
随机推荐
- BOM设计的一些问题及解决方案探讨----合版BOM
BOM是ERP的核心资料,也是比较难的一块,不仅涉及的内容多,要求准确性高,时效性也要求高.但传统的ERP在处理BOM时有不少问题,因此也有些软件公司引入了各种BOM类型,像"标准BOM&q ...
- vlan之间的通信-单臂路由与三层交换之间的互通
注:本试验基于单臂路由通信,三层交换通信,请完成以上两个实验,并保证能够通信 熟练掌握单臂路由的配置 熟练掌握三层交换的配置 三层交换与单臂路由的互通 实验原理 三层交换机在原有二层交换机的基础之上增 ...
- Changing Ethernet Media Speed for AIX
ITS UNIX Systems Changing Ethernet Media Speed for AIX First you need to find out the device name of ...
- MySQL优化小结
数据库的配置是基础.SQL优化最重要(贯穿始终,每日必做),由图可知,越往上优化的面越小,最基本的SQL优化是最重要的,往上各个参数也没太多调的,也不可能说调一个innodb参数性能就会好多少,而动不 ...
- xcode代码提示没了
defaults write com.apple.dt.XCode IDEIndexDisable 0 https://www.jianshu.com/p/57a14bed9d1b
- sourceTree回退撤销commit
https://blog.csdn.net/gang544043963/article/details/71511958
- LeetCode 696 Count Binary Substrings 解题报告
题目要求 Give a string s, count the number of non-empty (contiguous) substrings that have the same numbe ...
- (备忘)怎么去除WinRAR弹窗广告?
1.在WinRAR的安装目录下新建一个记事本,命名为“rarreg.key”. 2.打开记事本,将一下内容复制进去. RAR registration data Federal Agency for ...
- Java文件写入与读取实例求最大子数组
出现bug的点:输入数组无限大: 输入的整数,量大: 解决方案:向文件中输入随机数组,大小范围与量都可以控制. 源代码: import java.io.BufferedReader; import j ...
- docker centos 老是退出
1. 使用docker 镜像可以加快拉去.操作系统的使用第二种格式. 您可以使用以下命令直接从该镜像加速地址进行拉取: $ docker pull registry.docker-cn.com/myn ...