【Python-Socket】socket通信笔记
socket通信例程
- 先来一个简单的demo
服务端
import socket
# 建立一个服务端
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(('0.0.0.0', 18888)) #绑定要监听的端口
server.listen(5) #开始监听 表示可以使用五个链接排队
while True:# conn就是客户端链接过来而在服务端为期生成的一个链接实例
conn,addr = server.accept() #等待链接,多个链接的时候就会出现问题,其实返回了两个值
print(conn,addr)
while True:
try:
data = conn.recv(1024) #接收数据
print('recive:',data.decode()) #打印接收到的数据
conn.send(data.upper()) #然后再发送数据
except ConnectionResetError as e:
print('关闭了正在占线的链接!')
break
conn.close()
客户端
import socket# 客户端 发送一个数据,再接收一个数据
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #声明socket类型,同时生成链接对象
client.connect(('10.49.33.80',8888)) #建立一个链接,连接到本地的6969端口
while True:
# addr = client.accept()
# print '连接地址:', addr
msg = '欢迎访问菜鸟教程!' #strip默认取出字符串的头尾空格
client.send(msg.encode('utf-8')) #发送一条信息 python3 只接收btye流
data = client.recv(1024) #接收一个信息,并指定接收的大小 为1024字节
print('recv:',data.decode()) #输出我接收的信息
client.close() #关闭这个链接
socket 中最主要的是accept-connect 和send-recv 这两组函数的连接,其中accept-connect 负责两台主机之间的连接,send-recv负责连接完成后主机间数据的收发。
【1】章节概览
- 网络数据、网络错误
编码-解码
封帧-引用
socket.shutdown()的用法 - 服务器架构
单线程服务器
多线程和多进程服务器
异步服务器 - 缓存与消息队列
socket缓冲区解释
消息队列
散列和分区
Memcached
【2】通信详解
-
串口通信
-
多线程通信
-
多进程通信
-
Queue
-
pipe
-
Manager
-
缓冲区
-
socket的缓冲区机制
每个
socket
被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
write()/send()
并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由 TCP 协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是 TCP 协议负责的事情。
TCP 协议独立于write()/send()
函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。read()/recv()
函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。归根结底:
write/send/read/recv
这些函数只对缓冲区进行控制,独立于 TCP 协议之外。
这些 I/O 缓冲区特性可整理如下:
1. I/O 缓冲区在每个 TCP 套接字中单独存在;(1个TCP对应1个I/O缓冲区)
2. I/O 缓冲区在创建套接字时自动生成;
3. 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
4. 关闭套接字将丢失输入缓冲区中的数据。 -
对文件的控制
当然,受限于网络环境还有设备的问题,缓冲区有几率存在积压数据没有被发送出去的情况。
此时我们需要考虑python 中关于文件操作的函数:
python.file
对象file.open(file, mode='r', buffering=-1, encoding=None)
关于file.open()
函数的更多参数,请访问Python3 File(文件) 方法file.close()
file.flush()
刷新文件内部缓冲,直接将内部缓冲区的数据立刻写入文件,不需等待输出缓冲区写入file.write()
file.read()
file.readline()
读取整行file.writelines()
写入字符串列表,换行须自己加入\n
file.fileno()
返回文件描述符file.isatty()
是否连接到终端设备file.seek(offset[, whence])
设置文件读取指针,并会影响read()
函数的读取顺序
whence:可选,默认值为 0。给 offset 定义一个参数,表示要从哪个位置开始偏移;0 代表从文件开头开始算起,1 代表从当前位置开始算起,2 代表从文件末尾算起。
f = open('workfile', 'rb+') f.write(b'0123456789abcdef') 16 f.seek(5) # 移动到文件的第六个字节 5 f.read(1) b'5' f.seek(-3, 2) # 移动到文件倒数第三个字节 13 f.read(1) b'd'
file.tell()
返回文件读取指针的位置file.truncate()
返回截断后的文件
程序例程
# 打开文件 fo = open("runoob.txt", "r") print ("文件名为: ", fo.name) for line in fo.readlines(): #依次读取每行 line = line.strip() #去掉每行头尾空白 print ("读取的数据为: %s" % (line)) # 关闭文件 fo.close()
【3】函数笔记
-
makefile
特点:和普通的socket 通信对象相比,makefile 对象更侧重于I/O类型的对象,等到以后有空的时候再进行更新。
参考资料: -
socket.settimeout()
【4】socket 传输图片的问题
以下是作者参考其它博客的传输图片例程
服务端
fileinfo_size = struct.calcsize('32si')
buf = server.recv(fileinfo_size)
if buf:
filename, filesize = struct.unpack('32si', buf)
fn = filename.strip(b'\00')
fn = fn.decode()
print('file name is {0}, filesize if {1}'.format(str(fn), filesize))
recvd_size = 0 # 定义已接收文件的大小
# 存储在该脚本所在目录下面
fp = open('./' + str(filename), 'wb')
print('pic start receiving...')
# 将分批次传输的二进制流依次写入到文件
while not recvd_size == filesize:
if filesize - recvd_size > 1024:
data = conn.recv(1024)
recvd_size += len(data)
else:
data = conn.recv(filesize - recvd_size)
recvd_size = filesize
fp.write(data)
fp.close()
程序分别两个部分,第一部分解开struct包头文件,获取filename和filesize,第二部分是一个循环,终止条件是已接收图片大小等于客户端图片大小(详见客户端代码,引入了python struct
的概念并制作包头文件),这里定义一次接收的大小为1024字节(这个也是socket 通信的限制),因此if
剩余大小大于1024字节,执行接收1024字节;else
剩余大小小于1024字节,则接收剩下长度,并将已接收大小设置为客户端图片大小(这里着重强调,至于原因底下说)。
客户端
filepath = 'camera.jpg'
# 判断是否为文件
if os.path.isfile(filepath):
# 定义定义文件信息。32s表示文件名为32bytes长,i表示一个int或log文件类型(Windows中i为4个字节,Linux中i为8个字节),在此为文件大小
fileinfo_size = struct.calcsize('32si')
# 定义文件头信息,包含文件名和文件大小
fhead = struct.pack('32si', os.path.basename(filepath).encode('utf-8'), os.stat(filepath).st_size)
# 发送文件名称与文件大小
socket.send(fhead)
# 将传输文件以二进制的形式分多次上传至服务器
fp = open(filepath, 'rb')
while 1:
data = fp.read(1024)
if not data:
print('{0} file send over...'.format(os.path.basename(filepath)))
break
socket.send(data)
# 关闭当期的套接字对象
socket.close()
还是两部分,第一部分建立包头文件,请自行参考struct
的用法,第二部分建立fp 文件对象读取图片,然后使用while 循环重复读取并发送图片流直至全部发送完成。
结果
Traceback (most recent call last):
File "d:\vsproject\pyhelloworld\success\1216\Serverimprove.py", line 127, in <module>
main()
File "d:\vsproject\pyhelloworld\success\1216\Serverimprove.py", line 124, in main
t0.run()
File "D:\Users\18120\Anaconda3\lib\threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "d:\vsproject\pyhelloworld\success\1216\Serverimprove.py", line 61, in servergps
dataname, filesize = struct.unpack('32si', data)
struct.error: unpack requires a buffer of 36 bytes
服务端接收包头文件时发生错误,这里经过读者的定位发现,上一张图片服务端没有接收完全,导致部分数据挺在缓冲区,被下一张图片接收包头文件的recv()接收,导致这里的data 是图片数据而非包头文件数据。
数据示例
# 错误数据1
b"\x85\x97v\x00\xa0\xcb\x8fJV\xb7c\xfc'\xdb\x8aT\xb4=X~t\x92H\x0f\xff\xd9camera93.jpg\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xeb\n\x01\x00"
# 错误数据2
b'\xa8q\xb6\xa2j\xc4{x\xddQ\x1f\x97\xadM\xbf\x1c\x01Ls\x91\xc9\xe4P\xec\x1a\x08\xae\x0f&\x95]\t\xc3q\xefP3\xf9m\xb4\x0e;\xd3\xc3\x02\x03z\xd3\xb5\xb5\r\xb5&\x1c\x9f\x96\xa4\x8eLeNq\xdf\x15]X\x92\x05<>\xc3S\xa3\x1139#\x1b\xb23\xc54\xe7\xad&\xf0\xc74\xe0Gq@\x0c\x01\xbb\xd2\xee\xe8\xb4\xaeT\x1f\x94\xf1H\x0eW#\xd3\x9akT1\xdea-\x9ct\x18\xe9Ry\xe3\x1c\n\x85I$\xe6\x85}\xa7\x00\xf7\xe2\x85kY\x81d\x1e\xf4\xc3>0q\xd7\xde\x98er88\xe2\xa2rs\x8a\x94\xbb\x88\xff\xd9'
# 正确数据 正好符合32si结构可以解包
b'camera92.jpg\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x007\x1c\x01\x00'
找到原因后我们得知道怎么修改,回到上面所说的,else
剩余大小小于1024字节,则接收剩下长度,并将已接收大小设置为客户端图片大小(这里着重强调)
while not recvd_size == filesize:
if filesize - recvd_size > 1024:
data = conn.recv(1024)
recvd_size += len(data)
else:
data = conn.recv(filesize - recvd_size)
recvd_size = filesize
fp.write(data)
fp.close()
假设一张图片大小总共7521字节,当前接收6144字节
倒数第二次按理接收1024字节(达到7168字节) ,然而缓冲区只有436个字节,只能接收436个字节,此时服务端接收6580字节
最后一次只有941字节长度,进行最终传输,然而只接收588字节(达到7168字节),并终止传输,并将大小设置为客户端图片大小
这里服务端明显的睁眼说瞎话,明明没有接收完图片可是还设置了实际图片的大小,因此我们只要修改这一个错误的行为即可,以下是修改后的成功代码
while not recvd_size == filesize:
if filesize - recvd_size > 1024:
data = server.recv(1024)
recvd_size += len(data)
else:
data = server.recv(filesize - recvd_size)
recvd_size += len(data)
fp.write(data)
fp.close()
另外可以参考:
问题解决~