1、socket通信創建過程(以客戶端爲例)
a:創建Socket,涉及到的主要參數有:domain、type、protocal。
domain是協議域,其中AF_INET->IPv4;AF_INET6->IPv6 。
type對應socket類型,SOCK_STREAM->TCP;SOCK_DGRAM->UDP。
protocol是IPPROTO_TCP,若傳入0,則會根據第二個參數type,自動選擇合適的參數。
b,連接到服務器,主要參數:
客戶端socket
指向數據結構socketaddr的指針,其中包括目的端口和IP地址
結構體數據長度
c,發送數據到服務器,主要參數:
客戶端socket
發送內容地址
發送內容長度
發送方式標誌,一般爲0
d,從服務器接受數據,主要參數:
客戶端socket
接受內容緩衝區地址
接受內容緩衝區長度
接受方式,0表示阻塞,必須等待服務器返回數據返回值,若成功,則返回讀入的字節數,失敗則 返回SOCKET_ERROR。
e,關閉socket。
2、python 實現socket通信代碼實例
服務器端:
"""
file: service.py
socket service
"""
import socket
import threading
import time
import sys
def socket_service():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 實例化
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #防止socket server重啓後端口被佔用(socket.error: [Errno 98] Address already in use)
s.bind(('0.0.0.0', 6666)) #bind端口
s.listen(100) #允許連接數
except socket.error as msg:
print(msg)
sys.exit(1)
print('Waiting connection...')
while 1:
conn, addr = s.accept() #阻塞連接
t = threading.Thread(target=deal_data, args=(conn, addr)) #多線程
t.start()
def deal_data(conn, addr):
print('Accept new connection from {0}'.format(addr))
conn.send(('Hi, Welcome to the server!').encode()) #發送響應
while 1:
data = conn.recv(1024)
print('{0} client send data is {1}'.format(addr, data.decode()))
time.sleep(1)
if data == 'exit' or not data: #防止客戶斷線或異常中斷
print('{0} connection close'.format(addr))
conn.send(bytes('Connection closed!'),'UTF-8')
break
"""
reavFull += data #如果客戶發送100k數據的處理
if len(reavFull)!=1024*100:
continue
"""
conn.send(bytes('Hello, {0}'.format(data),"UTF-8"))
conn.close()
if __name__ == '__main__':
socket_service()
客戶端:
import socket
import sys
def socket_client():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.1.101', 6666))
except socket.error as msg:
print(msg)
sys.exit(1)
print(s.recv(1024))#目的在於接受:Accept new connection from (...
while 1:
data = input('please input work: ').encode()
s.send(data)
print('aa',s.recv(1024))
if data == 'exit':
break
s.close()
if __name__ == '__main__':
socket_client()
3、python 實現socket通信代碼實例二
進行文件的傳輸,如,.txt,.jpg等等
服務器端:
###服務器端server.py
import socket
import os
import sys
import struct
def socket_service_image():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# s.bind(('127.0.0.1', 6666))
s.bind(('127.0.0.1', 6666))
s.listen(10)
except socket.error as msg:
print(msg)
sys.exit(1)
print("Wait for Connection.....................")
while True:
sock, addr = s.accept() # addr是一個元組(ip,port)
deal_image(sock, addr)
def deal_image(sock, addr):
print("Accept connection from {0}".format(addr)) # 查看發送端的ip和端口
while True:
fileinfo_size = struct.calcsize('128sq')
print('fileinfo_size is',fileinfo_size)
buf = sock.recv(fileinfo_size) # 接收圖片名
print('buf is ',buf)
if buf:
filename, filesize = struct.unpack('128sq', buf)
print('filename ,filesize is',filename.decode(),filesize )
fn = filename.decode().strip('\x00')
print('fn is ',fn)
new_filename = os.path.join('./',
'new_' + fn) # 在服務器端新建圖片名(可以不用新建的,直接用原來的也行,只要客戶端和服務器不是同一個系統或接收到的圖片和原圖片不在一個文件夾下)
recvd_size = 0
fp = open(new_filename, 'wb')
while not recvd_size == filesize:
if filesize - recvd_size > 1024:
data = sock.recv(1024)
recvd_size += len(data)
else:
data = sock.recv(1024)
recvd_size = filesize
print('data is',data)
fp.write(data) # 寫入圖片數據
fp.close()
sock.close()
break
if __name__ == '__main__':
socket_service_image()
客戶端:
'''
Fuction:客戶端發送圖片和數據
Date:
Author:mxh
'''
###客戶端client.py
import socket
import os
import sys
import struct
def sock_client_image():
while True:
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 6666)) # 服務器和客戶端在不同的系統或不同的主機下時使用的ip和端口,首先要查看服務器所在的系統網卡的ip
# s.connect(('127.0.0.1', 6666)) #服務器和客戶端都在一個系統下時使用的ip和端口
except socket.error as msg:
print(msg)
print(sys.exit(1))
filepath = input('input the file: ') # 輸入當前目錄下的圖片名 xxx.jpg
fhead = struct.pack(b'128sq', bytes(os.path.basename(filepath), encoding='utf-8'),
os.stat(filepath).st_size) # 將xxx.jpg以128sq的格式打包
s.send(fhead)
fp = open(filepath, 'rb') # 打開要傳輸的圖片
while True:
data = fp.read(1024) # 讀入圖片數據
if not data:
print('{0} send over...'.format(filepath))
break
s.send(data) # 以二進制格式發送圖片數據
s.close()
# break #循環發送
if __name__ == '__main__':
sock_client_image()
IO多路複用
I/O(input/output),即輸入/輸出端口。每個設備都會有一個專用的I/O地址,用來處理自己的輸入輸出信息首先什麼是I/O:
I/O分爲磁盤io和網絡io,這裏說的是網絡io
IO多路複用:
I/O多路複用指:通過一種機制,可以監視多個描述符(socket),一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。
Linux
Linux中的 select,poll,epoll 都是IO多路複用的機制。
Linux下網絡I/O使用socket套接字來通信,普通I/O模型只能監聽一個socket,而I/O多路複用可同時監聽多個socket.
I/O多路複用避免阻塞在io上,原本爲多進程或多線程來接收多個連接的消息變爲單進程或單線程保存多個socket的狀態後輪詢處理.
Python
Python中有一個select模塊,其中提供了:select、poll、epoll三個方法,分別調用系統的 select,poll,epoll 從而實現IO多路複用。
對於select模塊操作的方法:
句柄列表
11
, 句柄列表
22
, 句柄列表
33
=
select.select(句柄序列
1
, 句柄序列
2
, 句柄序列
3
, 超時時間)
參數: 可接受四個參數(前三個必須)
返回值:三個列表
select方法用來監視文件句柄,如果句柄發生變化,則獲取該句柄。
1
、當 參數
1
序列中的句柄發生可讀時(accetp和read),則獲取發生變化的句柄並添加到 返回值
1
序列中
2
、當 參數
2
序列中含有句柄時,則將該序列中所有的句柄添加到 返回值
2
序列中
3
、當 參數
3
序列中的句柄發生錯誤時,則將該發生錯誤的句柄添加到 返回值
3
序列中
4
、當 超時時間 未設置,則select會一直阻塞,直到監聽的句柄發生變化
5
、當 超時時間 =
1
時,那麼如果監聽的句柄均無任何變化,則select會阻塞
1
秒,之後返回三個空列表,如果監聽的句柄有變化,則直接執行
例子1:
服務端:
sk1 = socket.socket()
sk1.bind(("127.0.0.1",8001))
sk1.listen()
inpu = [sk1,]
while True:
r_list,w_list,e_list = select.select(inpu,[],[],1)
for sk in r_list:
if sk == sk1:
conn,address = sk.accept()
inpu.append(conn)
else:
try:
ret = str(sk.recv(1024),encoding="utf-8")
sk.sendall(bytes(ret+"hao",encoding="utf-8"))
except Exception as ex:
inpu.remove(sk)
客戶端
import socket
obj = socket.socket()
obj.connect(('127.0.0.1',8001))
while True:
inp = input("Please(q\退出):\n>>>")
obj.sendall(bytes(inp,encoding="utf-8"))
if inp == "q":
break
ret = str(obj.recv(1024),encoding="utf-8")
print(ret)
例子2:
服務端:
import socket
sk1 = socket.socket()
sk1.bind(("127.0.0.1",8001))
sk1.listen()
inputs = [sk1]
import select
message_dic = {}
outputs = []
while True:
r_list, w_list, e_list = select.select(inputs,[],inputs,1)
print("正在監聽的socket對象%d" % len(inputs))
print(r_list)
for sk1_or_conn in r_list:
if sk1_or_conn == sk1:
conn,address = sk1_or_conn.accept()
inputs.append(conn)
message_dic[conn] = []
else:
try:
data_bytes = sk1_or_conn.recv(1024)
data_str = str(data_bytes,encoding="utf-8")
sk1_or_conn.sendall(bytes(data_str+"好",encoding="utf-8"))
except Exception as ex:
inputs.remove(sk1_or_conn)
else:
data_str = str(data_bytes,encoding="utf-8")
message_dic[sk1_or_conn].append(data_str)
outputs.append(sk1_or_conn)
for conn in w_list:
recv_str = message_dic[conn][0]
del message_dic[conn][0]
conn.sendall(bytes(recv_str+"好",encoding="utf-8"))
for sk in e_list:
inputs.remove(sk)
客戶端:
import socket
obj = socket.socket()
obj.connect(('127.0.0.1',8001))
while True:
inp = input("Please(q\退出):\n>>>")
obj.sendall(bytes(inp,encoding="utf-8"))
if inp == "q":
break
ret = str(obj.recv(1024),encoding="utf-8")
print(ret)
4、esp32之post至mysql數據庫
ESP32代碼:
import network
import urequests
import json
import time
d1=10
d2=20
d3='h0101'
while True:
_response = urequests.post("http://www.winvip.top/test/test.php/", headers={"Content-Type":"application/json"}, data=json.dumps({"api_key":"tPmAafaT5Ab3j7F9", "value1":d1, "value2":d2, "value3":d3}))
print(_response.text)
time.sleep(3)
PHP接收post,解析json,更新數據庫代碼:
<?php
$servername = "localhost"; //數據庫地址
// 數據庫名
$dbname = "XXXXXXXX";
// 數據庫用戶名
$username = "XXXXXXX";
// 數據庫密碼
$password = "XXXXXXXX";
//保持此API密鑰值與項目頁面中提供的ESP32代碼兼容。
//如果您更改此值,則ESP32草圖需要匹配
$api_key_value = "tPmAT5Ab3j7F9";
$api_key= $sensor = $location = $value1 = $value2 = $value3 = "";
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$read_post = file_get_contents('php://input');
$datas=json_decode($read_post, true);
$api_key = $datas['api_key'];
if($api_key == $api_key_value) {
$value1 = $datas['value1'];
$value2 = $datas['value2'];
$value3 = $datas['value3'];
// 創建數據庫連接
$conn = new mysqli($servername, $username, $password, $dbname);
// 檢查數據庫連接狀態
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
//插入一個新的數據行
$sql = "INSERT INTO tempdata( date1, date2, date3)
VALUES ('" . $value1 . "', '" . $value2 . "', '" . $value3 . "')";
//更新表中內容
//$sql = mysqli_query($conn,"UPDATE SensorData SET value1 = '".$value1."',value2 = '".$value2."'
// WHERE sensor='Light'");
//如果數據修改成功
if ($conn->query($sql) === TRUE) {
echo "New record created successfully";
}
else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
$conn->close();
}
else {
echo "Wrong API Key provided.<br/>";
}
}
else {
echo "No data posted with HTTP POST.<br/>";
}
?>
PHP數據庫查詢,顯示代碼:(以下參考爲網上獲得,未經測試!!!)
<!DOCTYPE html>
<html><body>
<?php
$servername = "localhost";
// 數據庫名
$dbname = "bak";
// 數據庫用戶名
$username = "root";
// 數據庫密碼
$password = "123456";
// 創建數據庫連接
$conn = new mysqli($servername, $username, $password, $dbname);
// 檢查數據庫連接狀態
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
//查詢數據庫bak SensorData表中的內容
$sql = "SELECT id, sensor, location, value1, value2, value3, reading_time FROM SensorData ORDER BY id DESC";
echo '<table cellspacing="5" cellpadding="5">
<tr>
<td>ID</td>
<td>Sensor</td>
<td>Location</td>
<td>Value 1</td>
<td>Value 2</td>
<td>Value 3</td>
<td>Timestamp</td>
</tr>';
if ($result = $conn->query($sql)) {
while ($row = $result->fetch_assoc()) {
$row_id = $row["id"];
$row_sensor = $row["sensor"];
$row_location = $row["location"];
$row_value1 = $row["value1"];
$row_value2 = $row["value2"];
$row_value3 = $row["value3"];
$row_reading_time = $row["reading_time"];
echo '<tr>
<td>' . $row_id . '</td>
<td>' . $row_sensor . '</td>
<td>' . $row_location . '</td>
<td>' . $row_value1 . '</td>
<td>' . $row_value2 . '</td>
<td>' . $row_value3 . '</td>
<td>' . $row_reading_time . '</td>
</tr>';
}
$result->free();
}
$conn->close();
//<!--JS 頁面自動刷新 -->
echo ("<script type=\"text/javascript\">");
echo ("function fresh_page()");
echo ("{");
echo ("window.location.reload();");
echo ("}");
echo ("setTimeout('fresh_page()',3000);"); //3秒刷新一次
echo ("</script>");
?>
</table>
</body>
</html>