前言
大四下學(xué)期阅悍,寒假不努力,現(xiàn)在要拼命趕進(jìn)度了昨稼。畢業(yè)設(shè)計(jì)當(dāng)中节视,我需要下位機(jī)stm32將圖片發(fā)送(串口轉(zhuǎn)wifi)到上位機(jī)(pc)進(jìn)行圖像處理。以前沒(méi)有試過(guò)這樣的操作假栓,思考了一下寻行,因?yàn)橛写谵D(zhuǎn)wifi的模塊,所以決定上位機(jī)用python來(lái)寫(xiě)一個(gè)socket實(shí)現(xiàn)這個(gè)功能(由于下位機(jī)是stm32匾荆,所以下位機(jī)的發(fā)送就只能用c來(lái)寫(xiě)了)拌蜘。
嘗試
先用python寫(xiě)一個(gè)demo來(lái)測(cè)試下是否可以實(shí)現(xiàn)杆烁,寫(xiě)兩個(gè)python程序。一個(gè)python程序充當(dāng)下位機(jī)(客戶端)简卧,一個(gè)pyhon充當(dāng)上位機(jī)(服務(wù)端)连躏。
邏輯如下:首先,服務(wù)端代碼是一直在運(yùn)行贞滨,監(jiān)聽(tīng)著端口入热,如果有客戶端發(fā)起請(qǐng)求,則連接之建立socket晓铆∩琢迹客戶端與服務(wù)端連接后,開(kāi)始發(fā)送文件骄噪。期間尚困,輸出發(fā)送的狀態(tài)信息。
我的目錄結(jié)構(gòu)是這樣子的:
- 桌面新建一個(gè)send_image_test文件夾
- send_image_test文件夾里面分別建py_s文件夾和py_c文件夾
- 以上兩個(gè)文件夾里面分別建py_s.py和py_c.py
- 在文件夾py_c里面放了一張bmp格式的圖片链蕊,命名為image.bmp
根據(jù)上面的邏輯事甜,寫(xiě)出下面的代碼:
# 服務(wù)端代碼,環(huán)境Mac
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# 導(dǎo)入庫(kù)
import socket, threading, os
SIZE = 1024
# 檢查當(dāng)前目錄下是否有等下要命名的圖片,有的話刪除之
def checkFile():
list = os.listdir('.')
for iterm in list:
if iterm == 'image.bmp':
os.remove(iterm)
print 'remove'
else:
pass
# 接受數(shù)據(jù)線程
def tcplink(sock, addr):
print 'Accept new connection from %s:%s...' % addr
sock.send('Welcome from server!')
print 'receiving, please wait for a second ...'
while True:
data = sock.recv(SIZE)
if not data :
print 'reach the end of file'
break
elif data == 'begin to send':
print 'create file'
checkFile()
with open('./image.bmp', 'wb') as f:
pass
else:
with open('./image.bmp', 'ab') as f:
f.write(data)
sock.close()
print 'receive finished'
print 'Connection from %s:%s closed.' % addr
# 創(chuàng)建一個(gè)socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 監(jiān)聽(tīng)端口(這里的ip要在不同的情況下更改)
s.bind(('127.0.0.1', 9999))
# 每次只允許一個(gè)客戶端接入
s.listen(1)
print 'Waiting for connection...'
while True:
sock, addr = s.accept()
# 建立一個(gè)線程用來(lái)監(jiān)聽(tīng)收到的數(shù)據(jù)
t = threading.Thread(target = tcplink, args = (sock, addr))
# 線程運(yùn)行
t.start()
# 客戶端代碼滔韵,環(huán)境Mac
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import socket
SIZE = 1024
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立連接:
s.connect(('127.0.0.1', 9999))
# 接收歡迎消息:
print s.recv(SIZE)
s.send('begin to send')
print 'sending, please wait for a second ...'
with open('./image.bmp', 'rb') as f:
for data in f:
s.send(data)
print 'sended !'
s.close()
print 'connection closed'
需要注意的是逻谦,以上的代碼是在本地測(cè)試的。所以地址使用的是127.0.0.1(如需在不同的機(jī)器上測(cè)試陪蜻,請(qǐng)使用對(duì)應(yīng)的ip地址)邦马,端口號(hào)使用一個(gè)沒(méi)有被其他應(yīng)用程序使用的即可(我隨便選了一個(gè)9999)。
以上的兩個(gè)程序當(dāng)中都有一個(gè)SIZE常量宴卖,是接收緩沖區(qū)的最大字節(jié)數(shù)滋将,如果每次發(fā)送的數(shù)據(jù)大過(guò)SIZE個(gè)字節(jié),則肯定會(huì)出現(xiàn)錯(cuò)誤症昏,這里需要謹(jǐn)慎對(duì)待随闽。
發(fā)送數(shù)據(jù)時(shí),我們是從文件當(dāng)中讀取數(shù)據(jù)到內(nèi)存肝谭,然后再往外發(fā)送掘宪。那么如果讀取的數(shù)據(jù)很大,超過(guò)了內(nèi)存怎么辦分苇。上面的方法就是最好的方法添诉,不要使用file.read(size)這類(lèi)的方法,容易出錯(cuò)医寿。而是栏赴,將整個(gè)file當(dāng)做一個(gè)對(duì)象來(lái)處理,python虛擬機(jī)會(huì)自己分配緩沖區(qū)和內(nèi)存靖秩,參看這里:
另外须眷,寫(xiě)代碼時(shí)需要注意縮進(jìn)竖瘾。要么使用四個(gè)空格,要么使用一個(gè)tab花颗。不能同時(shí)使用兩種捕传,否則,需要在你的編輯器上做空格和tab的轉(zhuǎn)換處理扩劝。
提供一下python 2.7的socket庫(kù)相關(guān)文檔鏈接:
通過(guò)運(yùn)行上面的代碼庸论,圖片成功的傳輸了到服務(wù)端的文件夾下:
進(jìn)階修改
上述的代碼只是直接將一張圖片,從客戶端發(fā)送到了服務(wù)端棒呛。在實(shí)際的使用當(dāng)中聂示,應(yīng)該是需要發(fā)送命令和文件兩種形式。所以簇秒,我們需要對(duì)上面的代碼進(jìn)行一些修改鱼喉。
我們先定義一個(gè)通信協(xié)議:
建立連接后開(kāi)啟一個(gè)線程,用于處理本次的通信(一個(gè)while循環(huán))趋观,如果遇到接收文件的情況扛禽,則開(kāi)啟一個(gè)子線程用于接收。
發(fā)送數(shù)據(jù)有兩種情況皱坛,發(fā)送命令编曼,發(fā)送文件
接收數(shù)據(jù)有兩種情況,一種是接收命令麸恍,一種是接收文件
發(fā)送數(shù)據(jù):
發(fā)送命令時(shí)灵巧,第一個(gè)字符為c(command)表示后面接的是命令,第二個(gè)字符是命令代號(hào)抹沪。
發(fā)送文件時(shí),第一個(gè)字符為f(file)表示后面接的是文件瓤球,后面就是文件的二進(jìn)制形式融欧。
接收數(shù)據(jù):
接收數(shù)據(jù)分成兩段,首先查看第一段報(bào)文是命令還是文件標(biāo)識(shí)卦羡,第二段則是接收具體的數(shù)據(jù)
命令標(biāo)識(shí)為”c“噪馏,文件標(biāo)識(shí)為”f“
修改后的代碼粘貼如下:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# 導(dǎo)入socket庫(kù)
import socket, threading, time, os
SIZE = 1024
# 檢查當(dāng)前目錄下是否有等下要命名的圖片,有的話刪除之
def checkExsit():
list = os.listdir('.')
for iterm in list:
if iterm == 'image.bmp':
os.remove(iterm)
print 'Exsit file has been removed'
print 'Create file ...'
with open('./image.bmp', 'wb') as f: pass
def recvImage(sock):
while True:
data = sock.recv(SIZE)
if not data:
break
else:
with open('./image.bmp', 'ab') as f:
f.write(data)
print 'data received'
def saveImage(sock):
print 'Begin to save image ...'
checkExsit()
t = threading.Thread(target = recvImage, args = (sock,))
t.setDaemon(True)
t.start()
t.join()
print 'Finished saving image ...'
def tcplink(sock, addr):
# 打印連接信息
print 'Accept new connection from %s:%s...' % addr
# 發(fā)送問(wèn)候信息(客戶端接收到后返回一個(gè)'hello server')
sock.send('hello client')
print sock.recv(SIZE)
print 'Communication test success'
# 接受數(shù)據(jù)循環(huán)(一直等待接收數(shù)據(jù)并進(jìn)行處理 *****注意這是在一個(gè)線程里面******)
while True:
recv = sock.recv(SIZE)
# 接收命令
if recv == 'c':
print 'receive command'
cmd = sock.recv(SIZE)
print 'recv: %s' %cmd
# 判斷命令并執(zhí)行相應(yīng)的程序
recv = None
# 接收文件(這里主要是圖片)
elif recv == 'f':
print 'file command'
saveImage(sock)
recv = None
# 創(chuàng)建一個(gè)socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 監(jiān)聽(tīng)端口(這里的ip要在不同的情況下更改)
s.bind(('127.0.0.1', 9999))
# 每次只允許一個(gè)客戶端接入
s.listen(1)
print 'Waiting for connection...'
while True:
sock, addr = s.accept()
# 建立一個(gè)線程用來(lái)監(jiān)聽(tīng)收到的數(shù)據(jù)
t = threading.Thread(target = tcplink, args = (sock, addr))
# 線程運(yùn)行
t.start()
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import socket,time
SIZE = 1024
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立連接:
s.connect(('127.0.0.1', 9999))
# 接收歡迎消息:
print s.recv(SIZE)
s.send('hello server')
time.sleep(0.5)
print 'command test begins ...'
s.send('c')
s.send('weeding')
print 'command test ended'
time.sleep(0.5)
print 'image test begins ...'
s.send('f')
time.sleep(0.2)
with open('./image.bmp', 'rb') as f:
for data in f:
s.send(data)
print 'image test ended'
s.close()
print 'connection closed'
上面的代碼,運(yùn)用了一個(gè)子線程來(lái)執(zhí)行接收?qǐng)D片绿饵。使用多線程欠肾,最好可以了解下下面的內(nèi)容:
守護(hù)線程
線程鎖
線程的中文文檔