(2)socket的基础使用(基于TCP协议)
socket()模块函数用法
基于TCP协议的套接字程序
netstart -an | findstr 8080 #查看所有TCP和UDP协议的状态,用findstr进行过滤监听8080端口
服务端套接字函数
s.bind() #绑定(主机,端口号)到套接字
s.listen() #开始TCP监听
s.accept() #被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() #主动初始化TCP服务器连接
s.connect_ex() #connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() #接收TCP数据
s.send() #发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall() #发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom() #接收UDP数据
s.sendto() #发送UDP数据
s.getpeername() #连接到当前套接字的远端的地址
s.getsockname() #当前套接字的地址
s.getsockopt() #返回指定套接字的参数
s.setsockopt() #设置指定套接字的参数
s.close() #关闭套接字
面向锁的套接字方法
s.setblocking() #设置套接字的阻塞与非阻塞模式
s.settimeout() #设置阻塞套接字操作的超时时间
s.gettimeout() #得到阻塞套接字操作的超时时间
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
socket实例:简单的服务端和客户端的通信
服务端代码
import socket
设定套接字协议标准
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #将套接字放入一个变量,这个变量名自定,但是必须要针对项目具有描述性
#socket下有一个socket类,实例化这个类括号里两个参数,套接字模块下有一个AF_INET(基于网络通信的地址家族),socket.Sock_STREAM流式协议就是TCP协议
PS:socket.SOCK_STREAM就是TCP协议,socket.SOCK_DGRAM数据包协议就是UDP协议
PS:服务端有两种套接字,这个是第一种用来绑定端口和监听端口然后做accept的
绑定端口
phone.bind(('127.0.0.1',8080)) #也是两个参数,一个是IP地址,一个是端口号,端口范围0-65535
PS:127.0.0.1这个地址就是本地回环地址,通常开发测试就用这个地址,只有本机客户端才能访问
开始运行监听端口
phone.listen(5) #一个参数就是backlog,就是指定半连接池的数量
等待syn请求
print('start....')
conn,client_addr=phone.accept() #(这个双向链接accept有2个返回值,第一个是连接对象(能收消息也能发消息),第二个是子元祖是客户端的ip和端口,以元祖的形式返回)
PS:这是第二种套接字,这个套接字就是conn,代表着tcp建好的双向通路,用来收发数据
PS:accept就是建立连接,就是去半连接池里面拿取请求
收发消息 #这个功能都是基于第二种套接字conn的
data=conn.recv(1024) #参数就一个,设定最大接收数据的字节数1024Bytes,这里设定好后要将数据放入一个变量中供后续调用
conn.send(data.upper()) #发消息,把data就是收到的消息变成大写返回
PS:收消息基于方法recv,发消息就是send
关闭conn这个套接字,就是把双向通路关闭
conn.close() #回收资源
关闭第一个套接字
phone.close() #回收资源
客户端代码
import socket
设定套接字协议类型
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
PS:客户端就一种套接字,用来收也用来发
设置发送请求的服务端地址和端口
phone.connect(('127.0.0.1',8080)) #port:0-65535
发送消息
print('连接请求已经发送')
phone.send('hello'.encode('utf-8'))#发送的请求必须是bytes类型
接收消息
data=phone.recv(1024)
print(data)
最后关闭套接字
phone.close()
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
socket实例:通信链接和循环链接
服务端代码
import socket
server =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #REUSEADDR就是重新收回利用,1就是True
PS:加上面的一条代码就可以解决端口占用问题,启动程序时候直接告诉操作系统释放端口
server.bind(('127.0.0.1',8080))
server.listen(5)
print('服务器已启动...')
conn,addr = server.accept()
while True: #这里的while True 用专业术语说就是通信循环
data = conn.recv(1024)
'''recv要求收的字节数必须是大于0 bytes,如果接受一个0 bytes的字节会假死,导致软件卡主'''
print('来自客户端的数据: ',data)
conn.send(data.upper())
conn.close() #如果代码结束就关闭和客户端的通信
客户端的代码
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080)) #连接服务器的地址和端口
while True: #这里的while True 就是通信循环(专业术语)
mes = input('请输入: ').strip()
if len(mes) == 0:
'''这里解决recv函数接收0比特字节的字符导致服务端死机,就是不让用户输入0比特的字节'''
print('非法输入')
continue
client.send(bytes(mes,encoding='utf-8'))
print('发送成功!')
data = client.recv(1024)
print('来自服务端的信息: ',data)
client.close() #如果代码结束就关闭和客户端的通信
端口占用报错:OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次
这个问题的出现原因就是操作系统在服务端关闭后不是立马回收资源进行端口关闭,而是等待一段时间才进行资源回收关闭端口,所以重启或者关闭后立马开始服务端会出现这个问题,
解决办法:
1、服务端换一个端口
2、服务端在绑定地址前加一条server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)代码即可
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
socket实例:当客户端单方面关闭时候导致服务端宕机报出异常的解决办法
服务端代码
import socket
server =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)
while True: #这里的while True 专业术语叫连接循环
print('等待连接中...')
conn,addr = server.accept()
print('来自客户端的连接...')
print(addr)
while True: #这里的while True 专业术语叫通信循环
try:
data = conn.recv(1024)
# if len(data) == 0:break MAC系统要加这个判断,解决客户端单方面关闭导致的循环取空的异常
print('来自客户端的数据: ',data)
conn.send(data.upper())
except Exception as f:
print(f)
break
conn.close()
客户端的代码
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))
while True: #这里的while True 专业术语就叫通信循环
mes = input('请输入: ').strip()
if len(mes) == 0:
print('非法输入')
continue
client.send(bytes(mes,encoding='utf-8'))
print('发送成功!')
data = client.recv(1024)
print('来自服务端的信息: ',data)
client.close()
PS:windows系统如果客户端单方面关闭在服务端会报异常,MAC系统是不会报异常,服务端而是不断的循环收取空,解决办法就是在服务端通信循环下面判断收取的数据是否为0,是0就break
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
socket实例:基于TCP协议简易版的xshell(远程实现指令控制)
服务端代码
import socket
import subprocess
server =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
print('等待连接中...')
conn,addr = server.accept()
print('来自客户端的连接...')
print(addr)
while True:
try:
data = conn.recv(1024)
print('来自客户端的数据: ',data)
obj = subprocess.Popen(
data.decode('utf-8'), #这里将客户端的字节编码
shell = True,#这个就是调用命令解释器
stdout = subprocess.PIPE,stderr = subprocess.PIPE
)
stdouts = obj.stdout.read()
stderror = obj.stderr.read()
'''管道内获取到的结果是以字节形式存在的,所以在客户端获取到的需要编码成系统的字符格式才能正常显示'''
conn.send(stdouts + stderror)
'''python中alt+enter可以实现自动导入模块'''
except Exception as f:
print(f)
break
conn.close()
客户端代码
import socket
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))
while True:
mes = input('请输入: ').strip()
if len(mes) == 0:
print('非法输入')
continue
client.send(bytes(mes,encoding='utf-8'))#发送时候需要解码成
print('发送成功!')
data = client.recv(1024)
print('来自服务端的信息: ',data.decode('GBK'))#管道内的结果是以字节形式返回,读取时候需要编码成系统对应的 编码
'''管道内的结果是以字节形式,所以打印时候需要解码成字符的形式,而且编码的标准是以平台编码为准'''
client.close()
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
socket实例:基于TCP协议简易版的xshell(解决粘包问题)
须知:只有TCP有粘包现象,UDP永远不会粘包
例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
服务端代码
import socket
import struct
import subprocess
server =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
print('等待连接中...')
conn,addr = server.accept()
print('来自客户端的连接...')
print(addr)
while True:
try:
data = conn.recv(1024)
print('来自客户端的数据: ',data)
obj = subprocess.Popen(
data.decode('utf-8'),
shell = True,
stdout = subprocess.PIPE,stderr = subprocess.PIPE
)
stdouts = obj.stdout.read()
stderror = obj.stderr.read()
total_len = len(stdouts) + len(stderror) #先计算出结果的长度
total_len = struct.pack('i',total_len) #然后用struck模块打包结果长度,注意使用的格式化标准是i,i是返回长度为4,而且值不能超过10个字符
conn.send(total_len) #将数据长度打包后发送到客户端
conn.send(stdouts + stderror)
except Exception as f:
print(f)
break
conn.close()
PS:struct模块详解看Python的方法和语法解释笔记
客户端代码
import socket
import struct
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))
while True:
mes = input('请输入: ').strip()
if len(mes) == 0:
print('非法输入')
continue
client.send(bytes(mes,encoding='utf-8'))
print('发送成功!')
total_len = client.recv(4) #获取服务端发来的结果长度
total_len = struct.unpack('i',total_len)[0] #将字节解包取值,因为sturct解包后是以元组形式,所以取值时候用元组取值的方法取值
recv_data = b'' #这里就是用来存放结果的
recv_len = 0 #这个是用来统计长度的
while recv_len < total_len: #如果长度等于解包后获得的长度,则循环结束,表示结果已经取完
data = client.recv(1024) #取值还是每次取1024个字节
recv_data += data #每次循环取到的值放入变量
recv_len += len(data) #每次循环取到的长度和变量相加
print('来自服务端的数据: ',recv_data.decode('gbk'))
client.close()
PS:客户端在获取服务端的数据的时候,客户端设定取值的字节数,就获取服务端发来对等字节数的数据,这是一个强制对等的取值关系
PS:解决粘包问题就是让程序知道消息之间的界限,要让程序知道界限就要用到struct模块的打包和解包功能,在服务端计算出结果的总长度,通过struct模块打包成bytes类型发送给客户端,再由客户端用struct模块进行解包获取长度来判断取值
PS:struct里面的fmt格式化标准有多重,i表示打包成四个字节,l是打包成8个字节,所以可以根据需要来设定打包的标准
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
socket实例:基于TCP协议高级版的xshell
将参数等多项值放入一个字典中,然后客户端通过字典取值进行相应的操作
服务端代码
import json
import socket
import struct
import subprocess
server =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.bind(('127.0.0.1',8080))
server.listen(5)
while True:
print('等待连接中...')
conn,addr = server.accept()
print('来自客户端的连接...')
print(addr)
while True:
try:
data = conn.recv(1024)
print('来自客户端的数据: ',data)
obj = subprocess.Popen(
data.decode('utf-8'),
shell = True,
stdout = subprocess.PIPE,stderr = subprocess.PIPE
)
stdouts = obj.stdout.read()
stderror = obj.stderr.read()
total_len = len(stdouts) + len(stderror)
header_dic = {
'filename' : 'aaa.txt', #文件名称
'total_len' : total_len, #结果长度
'hash' : '123123123123' #hash值
}
header_bytes = json.dumps(header_dic) #用json模块将字典序列化
header_bytes = header_bytes.encode('utf-8') #将序列化后的字符串编码成utf-8的编码格式
header_res = struct.pack('i',len(header_bytes)) #这里打包字典的长度
conn.send(header_res) #发送字典的长度
conn.send(header_bytes) #发送序列化后的字典
conn.send(stdouts + stderror) #发送管道的内容
except Exception as f:
print(f)
break
conn.close()
客户端代码
import json
import socket
import struct
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))
while True:
mes = input('请输入: ').strip()
if len(mes) == 0:
print('非法输入')
continue
client.send(bytes(mes,encoding='utf-8'))
print('发送成功!')
'''这一段定义从服务端接收字节长度,让后面的代码知道字节的分界点,不会粘包'''
header_res = client.recv(4)#客户端用的是struct模块的i标准将字典打包,这个标准就是打包成4个字节,所以这里收的时候4个字节
'''解包后长度是以元组形式存放,从元组中取得长度'''
header_len = struct.unpack('i',header_res)[0]
'''这里定义客户端接收多少长度的字符,这个长度就是解包获得的'''
res = client.recv(header_len)
'''这里将接收到的字节以utf-8的编码标准解码字符串'''
header_str = res.decode('utf-8')
'''用json模块反序列化得到的解码字符串,获取到字典'''
header_dic = json.loads(header_str)
print(header_dic) # 打印字典
'''得到字典就可以以字典取值'''
total_len = header_dic['total_len']
recv_data = b''
recv_len = 0
while recv_len < total_len:
data = client.recv(1024)
recv_data += data
recv_len += len(data)
print('来自服务端的数据: ', recv_data.decode('gbk'))
client.close()