Python的 socket.recv超时会挂起

编程语言 2026-07-08

我想要一个服务器,它只在接收数据时才做点儿工作。只要没有数据可接收,socket.recv(1024) 应该超时,我会捕获异常并重新启动 socket.recv(1024)。这大约能工作一天左右(settimeout=10 seconds),然后就再也不会抛出异常了?

def work():
   print("Enter work at: "+str(datetime.datetime.now()))
   try:
      # read from the TCP conection
      client_socket, client_address = serversocket.accept()
      data = client_socket.recv(1024)
      if (debug>2): print('In work...data received:'+str(data))
      Process_received_data(data)   # process the received data
   except TimeoutError:
      #print('Timeout')
      a='Timeout' 
      print("Timeout at: "+str(datetime.datetime.now()))
   s.enter(0,1,work)       #schedule next call to work  

serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
serversocket.bind(("192.168.1.199", 50001)
serversocket.listen(20)
serversocket.settimeout(10.0)        # wait 10 sec for a message if not - fail..

s=sched.scheduler(time.time, time.sleep)

try:
   s.enter(0,1,work)
   s.enter(43200,1,Inactivate_entries)   # run every 12 hours approx.
   s.run() 
   if ( s.empty() ) :
     print("Scheduling Q empty")
except (ConnectionResetError, UnicodeDecodeError, KeyboardInterrupt) :
   if (serversocket) :
     serversocket.close()
   print("Nice shutdown due to error at: "+str(datetime.datetime.now()))
   exit()

输出是:

Enter work at: 2026-06-09 07:34:11.749858

Timeout at: 2026-06-09 07:34:21.760107

Enter work at: 2026-06-09 07:34:21.760461

Timeout at: 2026-06-09 07:34:31.770794

Enter work at: 2026-06-09 07:34:31.771163

Timeout at: 2026-06-09 07:34:41.781418

Enter work at: 2026-06-09 07:34:41.781844

Enter process received data at: 2026-06-09 07:34:44.760258

Exit process received data at: 2026-06-09 07:34:44.769109

Enter work at: 2026-06-09 07:34:44.769782

Enter process received data at: 2026-06-09 07:34:53.254670

Exit process received data at: 2026-06-09 07:34:53.261811

Enter work at: 2026-06-09 07:34:53.262433

Timeout at: 2026-06-09 07:35:03.272713

Enter work at: 2026-06-09 07:35:03.273073

Timeout at: 2026-06-09 07:35:13.283314

Enter work at: 2026-06-09 07:35:13.283754

(the time is now 9:40)...

问题在于,为什么会这样?在Python recv() 有没有内存泄漏?这已经连续好几周都没问题。近几天起就不再这样了(在 raspberry 上使用 python3.12.3)。另外,是否有更好的办法,例如使用非阻塞套接字?


你要的都在这里:

# read the data via TCP from diverse devices to 92.168.1.199 and put it in
# the mariaDB Haus_sensors, Luftentfeuchter, Temperature etc. 
# NOTE:  the received data (as JSON) should start with a single character, followed by a {.
# NOTE: this because all esp8266's send a byte of data-length as first character!!!
# new structure to use functions more efficiently.
# new approach to straighten logic: 
# switches DB state:
#  '0000'  not active, probably not connected
#  '00x1'  ON  (means ON signal has been sent)
#  '00x2'  OFF (means OFF signal has been sent) 
#  '001x'  in Auto mode: switch on when t>earliest_on AND light low signal has been set 
#  '002x'  in manual mode: switch from timed-OFF to timed-ON when t>earliest_on and from timed-ON to timed-OFF when t>latest_off
#  '003x'  in off mode: do not switch timed-ON. Always stay in timed-OFF  switched-ON and switched-OFF work.
#
# Note: the ON/OFF process from the web-control is directly send (send_data) from test_wsgi_script.py !!
# if send_signal fails, do not update the DB, so that the next go-around (pending events) it is tried again.
#
# structure of switches DB:
#+-------------+----------+------+-----+---------+----------------+
#| Field       | Type     | Null | Key | Default | Extra          |
#+-------------+----------+------+-----+---------+----------------+
#| record_id   | int(11)  | NO   | PRI | NULL    | auto_increment |0
#| mdatetime   | datetime | YES  |     | NULL    |                |1
#| switchname  | char(30) | YES  |     | NULL    |                |2
#| ip          | char(20) | YES  |     | NULL    |                |3
#| state       | char(4)  | YES  |     | NULL    |                |4
#| earliest_on | datetime | YES  |     | NULL    |                |5
#| latest_off  | datetime | YES  |     | NULL    |                |6
#| rand        | datetime | YES  |     | NULL    |                |7
#| light       | int      | YES  |     |         |                |8
#| rand_e_on   | datetime | YES  |     | NULL    |                |9
#| rand_l_off  | datetime | YES  |     | NULL    |                |10
#| fwd_ptr     | int      |      |     |         |                |11
#| bck_ptr     | int      |      |     |         |                |12
#+-------------+----------+------+-----+---------+----------------+

# 4Sept 2025: added 0.5 sec delays on sending query results...esp is not so fast..?
# 28Aug 2025: add backup-switch.
# 10Nov 2025: deleted no longer used code, added ON/OFF processing from button, added purge > 48hr old.

import socket
import json
import random
import datetime
import mariadb
import sys, getopt
import time, sched
# define database stuff:
db_Name="Haus_sensors"
db_User="a"
db_PW=""
debug=0

The_Database = {
    "open" : False,
    "connect" : None,
    "cursor" : None}

def Open_Database(database_name, database_user, database_password):
    # open the database with database_name, if not open already
    # set the database connector and the database cursor
    # return a boolean indicating success/failure
    if (debug>5) : print("Open_Database called at: "+str(datetime.datetime.now()))
    if (The_Database["open"]==False) :
      try:
        The_Database["connect"]=mariadb.connect(
        user=database_user,
        password=database_password,
        host="localhost",
        port=3306,
        database=database_name)
        The_Database["open"]=True
        The_Database["cursor"]=The_Database["connect"].cursor()
      except mariadb.Error as e:
        print(f"Error connecting to MariaDB: {e}")
        The_Database["open"]=False
    return(The_Database["open"]) 

def Close_Database() :
    # close the database and make sure everything is correctly set.
    if (debug>5) : print("Close_Database called at: "+str(datetime.datetime.now()))
    try:
      The_Database["open"]=False
      The_Database["connect"].close()
    except mariadb.Error as e:
      print(f"Error closing MariaDB: {e}")

    return  


def Query_Database(query) :
    # checks if the database is open and executes the query
    # if the query is successful it returnes True, otherwise False
    # the The_Database["cursor"] is set to the result of the query
    # print(query)
    Open_Database(db_Name,db_User,db_PW)
    if (debug>5) : print("Query_Database called with query: "+str(query))
    success=False
    if (The_Database["open"]) :  
      try:
        The_Database["cursor"].execute(query)
        The_Database["connect"].commit()
        success=True
      except mariadb.Error as e:
        print(f"Error in query to DB: {e}")
        print(query)
        success=False
    return(success)

def Process_random(funct,record) :
    # this has never been tested and should take place on the 8266.
    #
    # process the entered times and the random function on the passed record
    # funct is a dict passing either the  passed_e_on (p_e_on), or the  passed_l_off (p_l_off)
    # truth table:
    #  p_e_on:
    #  p_e_on < l_off   p_e_on < t_now    action:
    #     False           False            1.e_on=tomorrow||p_e_on  -  start tomorrow.
    #     False           True             2.e_on=tomorrow||p_e_on  -  start tomorrow
    #     True            False            3.e_on=today||p_e_on
    #     True            True             4.e_on=tomorrow||p_e_on
    #
    #  p_l_off:
    #  p_l_off < e_on   p_l_off < t_now    action:
    #     False           False            l_off=today||p_l_off
    #     False           True             l_off=tomorrow||p_l_off
    #     True            False            l_off=tomorrow||p_l_off
    #     True            True             l_off=tomorrow||p_l_off
    e_on_column = 5
    l_off_column= 6
    rand_column = 7
    time_now=datetime.datetime.now()

    if ('e_on' in funct) :
      p_e_on=funct.get("e_on")
      hours=int(p_e_on[0:2])
      minutes=int(p_e_on[3:5])
      t_e_on=time(hours,minutes,0,0)
      match_tuple=( (t_e_on < datetime.datetime.time(record[l_off_column])), t_e_on < datetime.datetime.time(time_now))
      match match_tuple:
        case ( (False,False) | (False,True) | (True,True) ) :
          t_e_on_date=datetime.datetime.date(time_now)+datetime.timedelta(days=1)
        case ( (True,False) ) :
          t_e_on_date=datetime.datetime.date(time_now)
      # check whether the datetime.datetime of now, e_on, l_off are in a good order:
      # now combine the items to new datetime objects and update the record in the DB:
      # set the same in the rand, as these are used for switching.
      new_dt_e_on=datetime.datetime.combine(t_e_on_date,t_e_on) 
      # check whether the datetime.datetime of now, e_on are in a good order:
      # if the new e_on is smaller than time now, add 1 day.
      if ( time_now > datetime.datetime(new_dt_e_on) ) :
        new_dt_e_on=new_dt_e_on+datetime.timedelta(days=1)
      if (debug>3) : print("New_dt_e_on: "+str(new_dt_e_on))
      statement="UPDATE switches SET earliest_on='"+str(new_dt_e_on)+"' "
      statement=statement+"rand_e_on='"+str(new_dt_e_on)+"' WHERE record_id="+str(record[0])
      Query_Database(statement)
       #END if e_on in funct

    elif ('l_off' in funct) :
      p_l_off=funct.get("l_off")
      hours=int(p_l_off[0:2])
      minutes=int(p_l_off[3:5])
      t_l_off=time(hours,minutes,0,0)
      match_tuple= ( (t_l_off < datetime.datetime.time(record[e_on_column])), t_l_off < datetime.datetime.time(time_now))
      match match_tuple :
        case ( (False,True) |  (True,False) | (True,True)) :
          t_l_off_date=datetime.datetime.date(time_now)+datetime.timedelta(days=1)
        case (False,False) :
          t_l_off_date=datetime.datetime.date(time_now)  
      new_dt_l_off=datetime.datetime.combine(t_l_off_date,t_l_off)
      # check whether the datetime.datetime of now, l_off are in a good order:
      # if the new l_off is smaller than time now, add 1 day.
      if ( time_now > datetime.datetime(new_dt_l_off) ) :
        new_dt_l_off=new_dt_l_off+datetime.timedelta(days=1)
      if (debug>3) : print("New_dt_l_off: "+str(new_dt_l_off))
      statement="UPDATE switches SET latest_off='"+str(new_dt_l_off)+"' "
      statement=statement+"rand_l_off='"+str(new_dt_l_off)+"' WHERE record_id="+str(record[0])
      Query_Database(statement)

    value_random=record[rand_column]
    idx=str(value_random).index(' ')
    HH_MM=str(value_random)[index+1:index+6]
    if (HH_MM != '00:00') :    #  only if the rand-value is not 00:00
      hours=int(HH_MM[0:2])
      minutes= int(HH_MM[3:5])
      minutes= int((minutes+hours*60)/2)
      start_t=random.uniform(-1,1)
      start_t=int(start_t*minutes)
      end_t=random.uniform(-1,1)
      end_t=int(end_t*minutes)
      t_earliest_on=datetime.datetime.time(row[5])
      rand_e_on=t_earliest+datetime.timedelta(minutes=start_t)
      # if the calculated rand_e_off results in a time next day, do add 24, until midnight. 
    return(0)  

def send_signal(signal,IP):
    if (debug>3) : print("send_signal: "+signal+" "+IP+" time="+str(datetime.datetime.now()))
    rc=0
    try: 
       me_client=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
       me_client.settimeout(15)
       me_client.connect((IP,50001))
       rc=me_client.send(signal.encode("utf-8"))
       me_client.close()
       ret_code=0
    except OSError as e:   
       print("Error in procedure send_signal:  signal="+signal+" IP="+IP+" Error="+str(e))
       me_client.close()
       ret_code=20
    return(ret_code)    

def Inactivate_entries():
    print("Enter inactivate entries  at: "+str(datetime.datetime.now()))
    # every 12 hours we go through the switches DB and put entries which have not been
    # updated in the mdatetime field to state=inactive '0000'
    time_now=datetime.datetime.now()

    if (debug>2) : print(f"Inactivate entries {time_now}")
    if (Query_Database("SELECT * from switches")) :
      records_db=The_Database["cursor"].fetchall()
      for row in records_db:
        if (row[1] < (datetime.datetime.now()-datetime.timedelta(hours=24))) :
          # this record has a time more then 24 hours ago. set it to state='0000'
          execute_string="UPDATE switches SET state='0000' WHERE record_id="+str(row[0])
          Query_Database(execute_string)
          # end if
        # if rows are older then 48 hours delete them entirely.
        if (row[1] < (datetime.datetime.now()-datetime.timedelta(hours=48))) :
          execute_string="DELETE FROM switches WHERE record_id="+str(row[0]) 
          Query_Database(execute_string)
        # end for
      #end if (Query..)
    Close_Database()
    s.enter(43200,1,Inactivate_entries)   # schedule next call to Inactivate_entries
    print("Exit Inactivate entries at: "+str(datetime.datetime.now()))
    return(0)

def Register(result):
    # we expect data[1…] to be: {"TYPE":"REGISTER","SWITCH":"Gartenbeleuchtung","IP","192.168.1.202","ti:":"12:10:23 06/01/2024"}
    # we first change the time into a MariaDB time format.
    # then we find the "Gartenbeleuchtung" in the switch column and update the IP and time - and leave the rest as is.
    # if there is no "Gartenbeleuchtung", we add the record and set the state active (0001) and the default
    # earliest_on (16:15) and latest_off (23:15) times
    # then we check all records and set inactive (state= '0000' those which have a time older then 24 hours.
    if (debug>2) : print("In Register: "+str(result))
    mtime = result["ti"][0:result["ti"].index(' ')]
    mdate = result["ti"][result["ti"].index(' ')+1:]
    # turn mdate into mariaDB YYYY/MM/DD format from DD/MM/YYYY
    mdate=datetime.datetime.strptime(mdate,"%d/%m/%Y").strftime("%Y/%m/%d")
    result["ti"]=mdate+" "+mtime
    # find the record in the DB with switchname=result['SWITCH']
    execute_string="SELECT * FROM switches WHERE switchname='"
    execute_string=execute_string+result['SWITCH']+"'"
    if (Query_Database(execute_string)) :
      # process the result of the SELECT stmt.
      if (The_Database["cursor"].rowcount <= 0) :
        # no record found, insert one - set initial state to manual/active (0022) - but OFF (0022)
        execute_string="INSERT INTO switches (mdatetime,switchname,ip,state,earliest_on,latest_off,rand,rand_e_on,rand_l_off,fwd_ptr,bck_ptr)"
        execute_string=execute_string+" VALUES('"+result['ti']+"',"
        execute_string=execute_string+"'"+str(result['SWITCH'])+"','"+str(result['IP'])+"','0022','2024-01-08 16:30','2024-01-08 23:15'" 
        execute_string=execute_string+",'2024-01-08 00:00','2024-01-08 00:00','2024-01-08 00:00',0,0)"
        Query_Database(execute_string)
      else:
        # update the time/date, in case of the state being '0000', set IP and manual/active off state ('0022')
        records_db=The_Database["cursor"].fetchall()
        execute_string="UPDATE switches SET mdatetime='"+result['ti']+"'"
        if ( (records_db[0][4] == '0000') or (records_db[0][3] != str(result['IP'])) ) :
          execute_string=execute_string+", IP='"+str(result['IP'])+"', state='0022'"
        execute_string=execute_string+" WHERE switchname='"+str(result['SWITCH'])+"'"
        Query_Database(execute_string)
    Close_Database()
    return()

def FeuchtundTemp(result):
    # we expect data[1:] to be: {"TYPE":"FEUTEMP","ti":"15:38:12 16/03/2023","temp":9.8,"hum":56.6}
    # take the ti part and split it in time and date:
    # and put it back in the datetime format of the MariaDB:
    # because updates are seldom we close the db.
    mtime = result["ti"][0:result["ti"].index(' ')]
    mdate = result["ti"][result["ti"].index(' ')+1:]
    # turn mdate into mariaDB YYYY/MM/DD format from DD/MM/YYYY
    mdate=datetime.datetime.strptime(mdate,"%d/%m/%Y").strftime("%Y/%m/%d")
    result["ti"]=mdate+" "+mtime
    # prepare the string to execute on the DB:
    execute_string="INSERT INTO feutemp (mdatetime,temperature,humidity)"
    execute_string=execute_string+" VALUES('"+result['ti']+"',"
    execute_string=execute_string+str(result['temp'])+","+str(result['hum'])+")"   
    # and execute the insert into the DB
    Query_Database(execute_string)
    Close_Database()
    return()

def FeuchtundTemp2(result):
    # this one is for the sensor duo in the Wintergarten
    # we expect data[1:] to be: {"TYPE":"EUTEMP","ti":"15:38:12 16/03/2023","temp":9.8,"hum":56.6}
    # take the ti part and split it in time and date:
    # and put it back in the datetime format of the MariaDB:
    # because updates are seldom we close the db.
    mtime = result["ti"][0:result["ti"].index(' ')]
    mdate = result["ti"][result["ti"].index(' ')+1:]
    # turn mdate into mariaDB YYYY/MM/DD format from DD/MM/YYYY
    mdate=datetime.datetime.strptime(mdate,"%d/%m/%Y").strftime("%Y/%m/%d")
    result["ti"]=mdate+" "+mtime
    # prepare the string to execute on the DB:
    execute_string="INSERT INTO feutemp2 (mdatetime,temperature,humidity)"
    execute_string=execute_string+" VALUES('"+result['ti']+"',"
    execute_string=execute_string+str(result['temp'])+","+str(result['hum'])+")"   
    # and execute the insert into the DB
    Query_Database(execute_string)
    Close_Database()
    return() 

def Temperatur(result):
    # we expect data[1:] to be: {"TYPE":"TEMP","ti":"15:38:12 16/03/2023","temp":9.8}
    # take the ti part and split it in time and date:
    # and put it back in the datetime format of the MariaDB:
    mtime = result["ti"][0:result["ti"].index(' ')]
    mdate = result["ti"][result["ti"].index(' ')+1:]
    # turn mdate into mariaDB YYYY/MM/DD format from DD/MM/YYYY
    mdate=datetime.datetime.strptime(mdate,"%d/%m/%Y").strftime("%Y/%m/%d")
    result["ti"]=mdate+" "+mtime
    # prepare the string to execute on the DB:
    execute_string="INSERT INTO temperature1 (mdatetime,temperature)"
    execute_string=execute_string+" VALUES('"+result['ti']+"',"
    execute_string=execute_string+str(result['temp'])+")"   
    # and execute the insert into the DB
    Query_Database(execute_string)
    Close_Database()
    return()

def Switch_query(result):
  # a query comes from a switch upon startup or if it wants to refresh its data.
  # send all the data on times etc. to the ip-address given. Find it through
  # looking up the name passed in the DB.

  def send_query_row(row): 
    # first send the add-new record, with the record-id:  row[3] is the ip-address
    if (debug>2) : print (f"in send_query_row")
    # first check whether the received earliest_on and latest_off are in good shape.
    e_on=row[5]
    l_off=row[6]
    check_dict={"e_on":e_on,"l_off":l_off}
    ret=Check_validity_of_dates(check_dict)
    e_on=check_dict["e_on"]
    l_off=check_dict["l_off"]
    if (ret>0) :  # we changed something, write it back to the DB:
      execute_string="UPDATE switches SET earliest_on='"+str(e_on)+"', latest_off='"+str(l_off)
      execute_string=execute_string+" ' WHERE record_id="+str(row[0])
      Query_Database(execute_string)
    # the new method sends one json-record:
    # {"TYPE":"ADD","record_id":<number>,"mode":"<mode>","earliest_on":"<datetime>","latest_off":
    #  "<datetime>","rand":"<datetime","light":<number>,"rand_e_on":"<datetime>","rand_l_off":"<datetime>"}
    data_to_send="{\"TYPE\":\"ADD\",\"record_id\":"+str(row[0])+",\"mode\":"
    if (row[4][2:3]=='1') : data_to_send=data_to_send+"\"auto\","
    if (row[4][2:3]=='2') : data_to_send=data_to_send+"\"man\","
    if (row[4][2:3]=='3') : data_to_send=data_to_send+"\"off\","
    data_to_send=data_to_send+"\"earliest_on\":\""+str(e_on)+"\",\"latest_off\":\""+str(l_off)+"\","
    data_to_send=data_to_send+"\"rand\":\""+str(row[7])+"\",\"light\":"+str(row[8])+","
    data_to_send=data_to_send+"\"rand_e_on\":\""+str(row[9])+"\",\"rand_l_off\":\""+str(row[10])+"\"}"
    rc=send_signal(data_to_send,row[3])
    return()

  # find all records in the DB with switchname=result['SWITCH']
  if (debug>2) : print(f"Process Switch-query: {result}")
  execute_string="SELECT * FROM switches WHERE switchname='"
  execute_string=execute_string+result['SWITCH']+"'"
  if (Query_Database(execute_string)) :
    # process the result of the SELECT stmt.
    if (The_Database["cursor"].rowcount > 0) :
      records_db=The_Database["cursor"].fetchall()
      for row in records_db:
        send_query_row(row)
      return()
    else :
      return()

def Switch_backup(result): 
  # back-up  light, e_on, l_off, r_e_on and r_l_off to the raspberry.  
  # Use the index to backup the right recordid  
  # we expect: {id:<recordid>,light:<lightvalue>,eo:<e_on>,lo:<latest_off>,reo:<rand_e_on>,rlo:<rand_l_off>}  
  execute_string="UPDATE switches SET earliest_on='"+str(result['eo'])+"', latest_off='"+str(result['lo'])
  execute_string=execute_string+"', rand_e_on='"+str(result['reo'])+"', rand_l_off='"+str(result['rlo'])
  execute_string=execute_string+"', rand='"+str(result['ro'])
  execute_string=execute_string+"', light='"+str(result['light'])+" ' WHERE record_id="+str(result['id'])
  if (debug>2) : print(execute_string)
  Query_Database(execute_string)

def Switches(result):
  # called when the esp8266 sends a switched ON or OFF signal. Update state in DB
  # use the passed ip-address to find the right record.
  # expect:  result= {"TYPE":"SWITCH","ip":"<ip-address>","state":<1 or 2>}
  # 1: set ON, 2: set OFF
  if (debug>2) : print("In Switches")
  execute_string="SELECT * FROM switches WHERE switchname='"
  execute_string=execute_string+result["switchname"]+"' AND bck_ptr=0"
  if (debug >2) : print(execute_string)
  if (Query_Database(execute_string)) : 
    if (The_Database["cursor"].rowcount>0) :
      records_db=The_Database["cursor"].fetchall()
      # here we assume there is just one row:
      new_state=records_db[0][4][0:4]
      if (result["state"] == '1') :  # ON
        new_state=new_state[0:3]+"1"
      if (result["state"] == '2') :  # OFF
        new_state=new_state[0:3]+"2"
      execute_string="UPDATE switches SET state='"+new_state+"' WHERE record_id="+str(records_db[0][0]) 
      if (debug>2) : print(execute_string)
      Query_Database(execute_string)  

def Process_received_data(data):
  print("Enter process received data at: "+str(datetime.datetime.now()))
  # each time we receive a piece of data from devices, do this work.
  if (debug>2) : print(f"Process received data: {data}")
  # we expect data[1:] to be: {"TYPE":"<type>", <type specific rest> }
  # print(data[1:])
  line_data=data[1:].decode()
  # parse the input data into dictionary 'result'.
  # print(line_data)
  result=json.loads(line_data[line_data.index('{'):line_data.index('}')+1])
  match result['TYPE']:
    case "LUFTE":
      Luftentfeuchter(result)   # I think this no longer exists

    case "TEMP":
      Temperatur(result) 

    case "FEUTEMP":
      FeuchtundTemp(result)

    case "EUTEMP":    
      FeuchtundTemp2(result)

    case "REGISTER":
      Register(result)

    case "LIGHTSENS":
      LightSens(result)    #  I think this does not exist anymore

    case "SWITCH":
      Switches(result)     

    case "QUERY":
      Switch_query(result)

    case "BACKUP":
      Switch_backup(result)

    case _:
      print("no matching for case:") 
      print(result['TYPE'])
      print("in received data:")
      print(result)
    #end match
  print("Exit process received data at: "+str(datetime.datetime.now()))  
  return(0)

def Check_validity_of_dates(idt) :

  # very essential - should probably also go into test_wsgi_script.py 

  # idt is a dict with e_on and l_off datetimes.
  # {"e_on":<datetime>,"l_off":<datetime>}
  # it is checked for the following:
  # it is assumed e_on as datetime and l_off as datetime are sequential: e_on earlier.
  #  Note: this holds for the datetime, not the times !
  # if l_off's date < now's date :
  #   l_off's date = now's date
  # if e_on's date < now's date :
  #   e_on's date = now's date 
  #   (once this is done, we can look at the times and correct the dates:)
  # if l_off's time < e_on's time  AND not (l_off's date > e_on's) date:
  #    l_off's date = e_on's date + 1 day.
  # if l_off's datetime < datetime.now     ( we have to add 1 day to both)
  #    l_off's date = l_off's date + 1 day
  #    e_on's date = e_on's date + 1 day 
  # if l_off's time > e_on's time AND l_off's date > e_on's date:
  #    l_off's date = e_on's date 
  changed = 0
  if (debug>2) : print(f"Enter Check_validity_of_dates: {idt}")
  now_dt=datetime.datetime.now()
  if (datetime.datetime.date(idt["l_off"]) < now_dt.date()) :
    changed=1
    k=idt["l_off"]
    idt["l_off"]=datetime.datetime(now_dt.year,now_dt.month,now_dt.day,k.hour,k.minute)
  if (datetime.datetime.date(idt["e_on"]) < now_dt.date()) :
    changed=1
    k=idt["e_on"]
    idt["e_on"]=datetime.datetime(now_dt.year,now_dt.month,now_dt.day,k.hour,k.minute)
  if ( (datetime.datetime.time(idt["l_off"]) < datetime.datetime.time(idt["e_on"])) and \
       not(datetime.datetime.date(idt["l_off"]) > datetime.datetime.date(idt["e_on"])) ) :
    changed=1
    k=idt["e_on"]+datetime.timedelta(hours=24)
    idt["l_off"]=datetime.datetime(k.year,k.month,k.day,idt["l_off"].hour,idt["l_off"].minute)
  if (idt["l_off"] < now_dt) :
    changed=1
    k=idt["l_off"]+datetime.timedelta(hours=24)
    idt["l_off"]=datetime.datetime(k.year,k.month,k.day,k.hour,k.minute)
    k=idt["e_on"]+datetime.timedelta(hours=24)
    idt["e_on"]=datetime.datetime(k.year,k.month,k.day,k.hour,k.minute)
  if (datetime.datetime.time(idt["l_off"])>datetime.datetime.time(idt["e_on"])) :
    changed=1
    k=idt["e_on"]
    idt["l_off"]=datetime.datetime(k.year,k.month,k.day,idt["l_off"].hour,idt["l_off"].minute)
  if (debug>2) : print(f"Exit Check_validity_of_dates: {idt}  changed: {changed}")
  return(changed)

def work():
   print("Enter work at: "+str(datetime.datetime.now()))
   try:
      # read from the TCP conection
      client_socket, client_address = serversocket.accept()
      data = client_socket.recv(1024)
      if (debug>2): print('In work...data received:'+str(data))
      Process_received_data(data)
   except TimeoutError:
      #print('Timeout')
      a='Timeout' 
      print("Timeout at: "+str(datetime.datetime.now()))
               #dummy statement - this communication timeout is not serious 
   s.enter(0,1,work)       #schedule next call to work  

#================== main program ====================================
# figure the debug level:
argumentlist=sys.argv[1:]
options="hd:"
long_options=["help","debug"]
arguments, values = getopt.getopt(argumentlist,options,long_options)
for currentArgument,currentValue in arguments:
  if currentArgument in ("-h","--help"):
    print(" optional argument: -d followed by a level 1..5 (5 is most verbose)") 
    exit()   
  elif currentArgument in ("-d","--debug"):
    debug=int(currentValue)  

serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
serversocket.bind(("192.168.1.199", 50001))
# a max. of 20 connections in parallel, and each 300.0seconds timeout on receive
# so we wait for something to arrive, but after 5 minutes we run through the 
# DB to see if anything needs to be done (i.e. light switch on or off)

serversocket.listen(20)
serversocket.settimeout(10.0)        # wait 10 sec for a message if not - fail..

s=sched.scheduler(time.time, time.sleep)
print("Starting at: "+str(datetime.datetime.now()))
if (debug>0) : print("debug level="+str(debug))

try:
   s.enter(0,1,work)
   s.enter(43200,1,Inactivate_entries)
   s.run() 
   if ( s.empty() ) :
     print("Scheduling Q empty")

except (ConnectionResetError, UnicodeDecodeError, KeyboardInterrupt) :
    if (serversocket) :
      serversocket.close()
    working=False  
    Close_Database()
    print("Nice shutdown due to error at: "+str(datetime.datetime.now()))
    exit()

解决方案

你只有对 serversocket.accept() 的超时设置。这意味着在没有客户端连接时,accept() 会在给定的超时后失败。

不过,你对 socket 返回的 accept() 并没有设置超时。这意味着对 recv() 实际上没有超时,与前面你所说的以及你很可能打算的那种相反。这意味着如果一个新客户端已经成功连接(accept() 返回),但该客户端没有发送任何数据,也没有关闭连接,它将一直阻塞下去。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章