如何解决Python套接字服务器脚本
我在运行最新Raspbian版本的RasPi(第一代)上设置连续运行的python编写的套接字服务器时遇到问题。我想做的是将RasPi设置为远程温度的数据服务器。使用ESP32和WiFi进行通讯的传感器。 我遇到的问题是服务器冻结,这是不定期发生的。服务器要做的(或至少应该做的)是,它接受远程连接,获取数据,将其切成块并将数据保存到文件中。脚本开始运行良好,一切都按预期运行,但是随着时间的流逝,它冻结了,出于对编码之神的热爱,我不知道为什么。也许有人可以帮我吗?
下面的服务器代码:
import socket
import datetime
PARAMS=['T','P','A','H','B','C']
sensors={'id':0}
for param in PARAMS:
sensors[param]={'value':0.0,'index_start':0,'index_end':0}
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind(('192.168.0.10',7543 ))
s.listen(1)
while True:
print("****Waiting for connection")
conn,addr = s.accept()
#create index for data slicing
contents=[]
with conn:
print('***Incoming connection from: ',addr)
while conn: # change here!
print("****What do you have for me? ")
content = conn.recv(1024)
print("***Received: ")
print(content)
contents.append(content)
print("****Data put into the pool")
if not content:
print("****No more data")
break
#conn.sendall(data)
print("***Data passed for processing: ")
print(contents)
now=datetime.datetime.now()
timestamp=now.strftime("%Y-%m-%d %H:%M:%S")
temp_list=[]
for c in contents:
clean_c=str(c).lstrip('b\'').rstrip('\'')
temp_list.append(clean_c)
text=''.join(temp_list)
print(text)
for i,param in enumerate(PARAMS):
sensors[param]['index_start']=text.index(param)+2
if param != 'T':
prev_param=PARAMS[i-1]
sensors[prev_param]['index_end']=text.index(param)
if param == 'C':
sensors[param]['index_end']=len(text)-1
sensors['id']=text[:sensors['T']['index_start']-2]
for param in PARAMS:
sensors[param]['value']=text[sensors[param]['index_start']:sensors[param]['index_end']]
if param == 'C':
sensors[param]['value']=text[sensors[param]['index_start']:]
value_tuple=(timestamp,sensors['id'],)
for param in PARAMS:
value_tuple+=(sensors[param]['value'].replace('.',','),)
text='\t'.join(value_tuple)
with open('/home/pi/Desktop/sensors.csv','a+') as file:
file.write(text+'\n')
print("****Closing connection")
print(timestamp)
#conn.close()
冻结总是发生在
content = conn.recv(1024)
我知道此功能会锁定我的程序,但是我不知道为什么如果没有数据,为什么它还会继续运行?怎样做才能使其..好..不这样做吗?我在客户端关闭了连接,据我所知with
函数应该响应socet关闭,对吧?
任何帮助将不胜感激。
EDIT1:
因此有人问我有关堆栈错误的信息,但没有。在Ctr + C之后,我得到一个标准响应,仅使用content = conn.recv(1024)
参考:
Traceback (most recent call last):
File "/home/pi/My_Python/Server2.py",line 27,in <module>
conn,addr = s.accept()
File "/usr/lib/python3.7/socket.py",line 212,in accept
fd,addr = self._accept()
KeyboardInterrupt: Execution interrupted
关于客户代码:
#define ver 0.21
#define date "2020.09.03"
#include <displayVersion.h>
displayVersion caseVersion;
#define DEEP_SLEEP_TIME 60
#define numberOfMeasurements 10
#include <WiFi.h>
#define ONBOARD_LED 2
int sensorID = 1001;
bool debug=true;
float temperature,pressure,altitude,humidity;
float AVGtemperature,AVGpressure,AVGaltitude,AVGhumidity;
int batteryLevel;
int AVGbatteryLevel;
bool batteryCharging;
const char* ssid = "xxxx";
const char* password = "xxxx";
const uint16_t port = 7543;
const char * host = "192.168.0.10";
bool wifiConnectionStatus;
void setup()
{
// setup onboard IOs
Serial.begin(115200);
caseVersion.display(ver,date,1000);
pinMode(ONBOARD_LED,OUTPUT);
pinMode(BATTERY_LEVEL_PIN,INPUT);
pinMode(BATTERY_CHARGING_PIN,INPUT);
// setup WiFi
bool status;
Serial.println("Starting operation...");
WiFi.begin(ssid,password);
Serial.println("Connecting to WiFi...");
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
Serial.println("");
Serial.print("WiFi connected with IP: ");
Serial.println(WiFi.localIP());
}
void loop()
{
// checks i f its connected to WiFi
if (WiFi.status()==3)
{
digitalWrite(ONBOARD_LED,HIGH);
wifiConnectionStatus=true;
}
else
{
digitalWrite(ONBOARD_LED,LOW);
wifiConnectionStatus=false;
}
// gets sensor data a set number of times
Serial.println ("Starting measurements...");
for (int i=0;i<numberOfMeasurements; i++)
{
temperature= 4;
AVGtemperature=AVGtemperature+temperature;
pressure=5;
AVGpressure=AVGpressure+pressure;
altitude=6;
AVGaltitude=AVGaltitude+altitude;
humidity= 7;
AVGhumidity=AVGhumidity+humidity;
// gets battery data
batteryLevel = analogRead(BATTERY_LEVEL_PIN);
AVGbatteryLevel=AVGbatteryLevel+batteryLevel;
Serial.printf("Measurement No: %d\n",i+1);
delay(300); // to be uncommented later on
}
AVGtemperature=AVGtemperature/numberOfMeasurements;
AVGpressure=AVGpressure/numberOfMeasurements;
AVGaltitude=AVGaltitude/numberOfMeasurements;
AVGhumidity=AVGhumidity/numberOfMeasurements;
AVGbatteryLevel=AVGbatteryLevel/numberOfMeasurements;
int temp = analogRead(BATTERY_CHARGING_PIN);
if (temp > 1000) batteryCharging = 1;
else batteryCharging = 0;
// while in debug
if (debug)
{
Serial.print("**Sensors** ");
Serial.printf("Temperature: %f ",AVGtemperature);
Serial.printf("Pressure: %f ",AVGpressure);
Serial.printf("Altitude: %f ",AVGaltitude);
Serial.printf("Humidity: %f ",AVGhumidity);
Serial.print("**Status** ");
Serial.printf("Battery level: %d % ",AVGbatteryLevel);
Serial.printf("Battery charging status: %d \n",batteryCharging);
Serial.printf("Data to be sent: \n");
Serial.printf("\n%dT:%2fP:%2fA:2%fH:2%fB:%dC:%d",sensorID,AVGtemperature,AVGhumidity,AVGbatteryLevel,batteryCharging);
}
// connects to a sever
if (wifiConnectionStatus)
{
WiFiClient client;
if (!client.connect(host,port))
{
if (debug) Serial.println("Connection to host failed");
//delay(500);
//return; // removed to save energy
goToDeepSleep();
}
if (debug) Serial.println("Connected to server successful!");
// sends out data
client.printf("%dT:%2fP:%2fA:%2fH:%2fB:%dC:%d",batteryCharging);
// dissconnects
if (debug)
{
Serial.println("Disconnecting...");
}
client.stop();
if (debug)
{
Serial.println("DONE...");
}
delay(5);
}
// goes to sleep
goToDeepSleep();
//wakes up and -> get temp data
}
void goToDeepSleep()
{
Serial.println("Going to sleep...");
esp_sleep_enable_timer_wakeup (DEEP_SLEEP_TIME*1000000);
esp_deep_sleep_start();
}
编辑2:
好的。所以第一件事。我不知道应该如何在stackoverflow上格式化他们的帖子。这实际上是我第一次发布。这就是为什么我不知道是否应该在评论中进行讨论,还是在这里或.... ??我也不知道是否应该将我的评论和发现发布在此帖子的底部,顶部还是单独发布。我想指出的并不是我的问题的答案,而只是发现。话虽这么说,这里是:
我一直在思考@Mark Setchell和@bnaecker关于读取一定数量字节的问题,这让我开始思考。到底是什么?
除了在本地终止连接外,它还发送一条消息。而且我想我正在收到该消息。...看,每当我运行服务器并打印出接收到的数据时,我都会得到2个流。首先是我的数据流,另一个是:
(...)
***Received:
b''
****Data put into the pool
(...)
这可能是stop()函数的EOF吗?如果是这样,我的服务器有时是否会某种程度上没有收到此消息?这将使其冻结。但是,如果是这样的话,我将需要一种方法来查看套接字是否打开。如果我的客户端关闭连接,则表示套接字已完成,服务器应检测到该连接。我可以解决这个问题。但是如何得到呢?我当时想从脚本中起诉“ conn”,但由于我刚开始使用这种语言,它仍然让我感到不知所措。那么“ conn”是一个对象吗?一个变量?当我打印它时,我从中获得了大量数据。那么,它是字符串吗?我在这里不知所措...
EDIT3:
所有这些后期编辑都变得荒谬,但我想这是规则。
首先,我要表示巨大的感谢!至今为止一直在帮助我的每一个人。你们真棒! :D
我花了一段时间才回答,因为我一直在进行故障排除。我猜这是我不特别喜欢Python的事情之一。没有存储库,其中没有功能的完整描述。例如,在我所看到的所有地方,都有这样的说法:“ socket.recv()是一个阻止函数”。并且(引用商业广告中的Alec Baldwin)是的,这是一个阻止功能,除了,不是。您可以做的是conn.recv(59,socket.MSG_DONTWAIT)
,它可以获取传入的缓冲区,仅此而已。不用等了我猜想与conn.recv()
应该是一样的,我很高兴尝试一下,并且运行正常,直到我得到[Errno 11] Resource temporarily unavailable
为止,基本上说没什么好看的。我认为获得EOF信号可能是一个问题-从何时引发异常开始判断-即使我发送/读取了一定数量的字节也是如此。这表示该问题与我到目前为止所遇到的情况一致。酷因此,我决定使用您的建议,并在套接字上设置超时。到目前为止,这是最长的一次! 2小时!我很高兴!直到它崩溃。在那超时。 gh…所以现在我正在运行带有异常处理的下一个测试,如@ glory9211和@urban所建议的。我会让你们知道怎么回事。手指交叉。
干杯, 拉法(Rafał)
解决方法
tl; dr:
- 在
conn
上设置超时以避免可能源自客户端的问题 - 尝试/排除
content = conn.recv(1024)
周围的所有异常,记录它们并关闭套接字 -
contents.append(content)
发生在检查conn关闭之前,因此您总是在输入结尾处以[...,b'']结尾 - 专注于客户端,也许可以使用tcpdump / wireshark进行更多调查
我已经使用netcat和telnet测试了以下最小服务器循环:
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind(('127.0.0.1',7543 ))
s.listen(1)
while True:
print("****Waiting for connection")
conn,addr = s.accept()
#create index for data slicing
contents=[]
with conn:
print('***Incoming connection from: ',addr)
while conn: # change here!
print("****What do you have for me? ")
content = conn.recv(1024)
print("***Received: ")
print(content)
contents.append(content)
print("****Data put into the pool")
if not content:
print("****No more data")
break
#conn.sendall(data)
print("***Data passed for processing: ")
print(contents)
观察1:
通过netcat和telnet的多次测试,服务器可以正常工作。使用netcat时,我使用Ctrl + C在系统上将连接置于TIME_WAIT上,但服务器仍在正常工作(按预期)。 (使用kill
杀死任何一个客户端对服务器也没有影响。
我可能会对服务器造成一些伤害的一种情况是,我用以下方法强行终止了客户端TCP连接:
$ sudo ss -K dst 127.0.0.1 dport = 7543
在这种情况下,服务器中断:
Traceback (most recent call last):
File "./tests.py",line 24,in <module>
content = conn.recv(1024)
ConnectionResetError: [Errno 104] Connection reset by peer
使用try / except接收应该很容易处理。我建议这个原因,因为我看到您让客户入睡。我对这是如何工作还不是很熟练,我看不出这种情况如何发生,但比sry更安全:)
观察2:
如上所述,EOF总是在连接关闭时发送到套接字。在代码中,您首先将数据附加到contents
,然后再检查EOF!结果,您在contents
中有EOF。示例:
***Incoming connection from: ('127.0.0.1',49272)
****What do you have for me?
***Received:
b'asdasd\r\n'
****Data put into the pool
****What do you have for me?
***Received:
b'asdasd\r\n'
****Data put into the pool
****What do you have for me?
***Received:
b'sadasd\r\n'
****Data put into the pool
****What do you have for me?
***Received:
b'asdsda\r\n'
****Data put into the pool
****What do you have for me?
***Received:
b'' # <<<<<< HERE you receive the "close"
****Data put into the pool
****No more data
***Data passed for processing:
[b'asdasd\r\n',b'asdasd\r\n',b'sadasd\r\n',b'asdsda\r\n',b''] # <<<<<< Here you see you added it in contents
以上行为是标准行为。您实际上并没有在问题中得到2个流,而是在同一流中得到2个“消息”(在TCP中,我认为正确的术语是“段”)。第一个是客户端发送的数据,第二个(EOF)是在套接字终止(FIN,ACK + FIN,ACK)时发送到应用程序的。 A random result from google you can read more about。我相信,这里的EOF不是由客户端而是由本地TCP / IP堆栈(内核)发送给您的应用程序的,不是由客户端发送的,以表示终止顺序发生在TCP中套接字,因此您不能“错过它”(对此不能百分百确定)。如果您错过了某件事,那就是FIN序列。
观察3:套接字超时并在recv()上阻塞
您在问conn
是什么:它是特定于特定客户端的套接字对象。那就是accept()
返回的结果。
如果您怀疑客户端已连接,然后长时间不发送任何消息(是的,这会在阻止您的服务器的recv()上阻止),建议您尝试set the timeout on the conn
socket。我之所以这样说,是因为从我收集到的信息来看,客户端在每个循环中都进行连接,发送,关闭。如果由于某种原因没有发生这种情况,而客户端只是连接并等待,那么它将使您的服务器循环保持繁忙,因为它刚刚接受了客户端并等待conn
套接字上的数据!
这是我认为服务器将在recv()
上阻塞的唯一情况,并且很可能是客户端无法发送的问题...无论如何,您的服务器不应对此真正“脆弱”因此我会尝试conn.settimeout(5)
给客户端5秒钟进行传输。您将需要处理在经过时间且客户端未发送任何内容时引发的超时异常。
我已尝试通过以下步骤对这种情况提出建议:
- 启动服务器脚本
- 启动netcat客户端并通过发送一些数据
- 使用
sudo iptables -A INPUT -p tcp --destination-port 7543 -j DROP
阻止防火墙上的dest端口-不再有来自netcat-> server的数据 - Ctrl + C现在尝试发送FIN但已被丢弃的客户端
服务器确实挂起:
***Incoming connection from: ('127.0.0.1',51194)
****What do you have for me?
***Received:
b'asdasd\n'
****Data put into the pool
****What do you have for me?
***Received:
b'asdasd\n'
****Data put into the pool
****What do you have for me?
- 删除防火墙规则(sudo iptables -D INPUT 1)
- 尝试再次连接到服务器,服务器被阻止
- Ctrl + C在recv上显示块:
^CTraceback (most recent call last):
File "./tests.py",in <module>
content = conn.recv(1024)
KeyboardInterrupt
建议的“解决方案”:
注意:这不是根本原因或无法解决问题,只是可以解决该问题(并记录下来)!
- 添加5秒超时
- 连接telnet
- 在5秒内发送一些数据
- 等待5秒以上
服务器输出为(请注意检测到超时):
****Waiting for connection
***Incoming connection from: ('127.0.0.1',51858)
****What do you have for me?
***Received:
b'asd\r\n'
****Data put into the pool
****What do you have for me?
!!! ERROR while receiving: timed out
***Data passed for processing:
[b'asd\r\n']
****Waiting for connection
服务器关闭连接时,也会通知客户端:
$ telnet localhost 7543
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
asd # << This was received before timeout
Connection closed by foreign host. ## << This is the server hanging up
代码:
s = socket.socket(socket.AF_INET,addr = s.accept()
#create index for data slicing
contents=[]
with conn:
conn.settimeout(5) # << NEW!
try: # << NEW!
print('***Incoming connection from: ',addr)
while conn: # change here!
print("****What do you have for me? ")
content = conn.recv(1024)
print("***Received: ")
print(content)
contents.append(content)
print("****Data put into the pool")
if not content:
print("****No more data")
break
#conn.sendall(data)
except Exception as e:
print("!!! ERROR while receiving:",str(e))
print("***Data passed for processing: ")
print(contents)
对于根本原因,您将需要收集带有时间戳的日志或Wireshark / tcpdump跟踪以了解发生了什么。如果问题仅是随机发生并且经过很长一段时间,这可能会很复杂...
,所以让我在这里给出一个简单的答案:
当您说conn.recv(x)
时,您的意思是“直到从套接字读取了x个字节后,才返回”。这称为“阻止I / O”。该程序将等待接收x个字节(在您的情况下为冻结)。
因此,有许多解决方案(请在此答案下评论适合您的方法,以寻求其他帮助)。
-
将
conn.recv(1024)
替换为conn.recv()
。每当新数据到达时,它将返回值。 -
将
conn.recv(1024)
替换为conn.recv(x)
。其中'x'是来自董事会client.printf
语句的数据大小。每当大小为x的新数据到达时,它将返回值。我建议这样做,因为您要发送的数据具有固定大小。 -
另一种方法是使用超时:在调用
conn.settimeout(3)
之前放置conn.recv(x)
,这将仅等待3秒(而不是冻结),并且如果没有接收到数据,它将引发异常您可以通过python try / except 块进行处理。 -
一个棘手的问题,但是您可以使
conn.recv()
的套接字无阻塞(有关信息,您可以在Internet上找到不错的教程,如果您是一个好奇的人,请在这里共享它)
关于您的客户代码的小技巧:
不要在循环内创建 WiFiClient客户端,然后将其关闭。而是全局地声明它(与ssid,温度变量等一起)。在void loop ()
内部,仅调用client.connected()
以获得连接状态并发送数据。
希望解决方案1,2,3或这些的组合将为您服务。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。