简体   繁体   English

Python多线程套接字侦听器错误,线程未释放

[英]Python Multi-threaded socket listener error with threads not releasing

I have 500+ units in the world that connects to my server and dump their data. 我在世界上有500多个装置连接到服务器并转储其数据。 Up to now i have been using a PHP script to act as a socket listener, but I need to go multi-threaded as the load is increasing and PHP can not keep up. 到目前为止,我一直在使用PHP脚本充当套接字侦听器,但是随着负载的增加和PHP无法跟上,我需要使用多线程。 I am quite new to Python and decided to use it for my new platform, over the past few days i have struggled and tried many examples to no avail. 我对Python还是很陌生,因此决定将其用于新平台,在过去的几天里,我一直在努力并尝试了许多示例,但都无济于事。 Through my search i came across some questions trying to answer this problem, but none did. 通过搜索,我遇到了一些试图回答此问题的问题,但没有一个。 I will attach my code. 我将附上我的代码。

The problem : as the units connect to the server and the server accepts it, the server creates a new thread to handle the connection, the problem comes when the unit drops the connection the thread stays open and active and the total thread count grows, and this is linked to the system limit : "number of open files", i can increase this limit but this only make it a time bomb , it does not solve this. 问题:当单元连接到服务器并且服务器接受它时,服务器将创建一个新线程来处理连接,问题出在单元断开连接时,该线程保持打开和活动状态,并且线程总数增加,并且这链接到系统限制:“打开文件数”,我可以增加此限制,但这只能使其成为定时炸弹,而不能解决此问题。

Please help. 请帮忙。

#! /usr/bin/python

import multiprocessing
import socket
import sys
import pprint
import datetime
import MySQLdb
import time
import datetime
import re
import select
import resource
import threading

max_connections = 1024
max_connections_set = max_connections
resource.setrlimit(resource.RLIMIT_NOFILE, (max_connections_set, max_connections_set))



#the incomming port
the_global_port = xxxx #(any port)

#display id
the_global_id = "UNIT TYPE"




class ConnectionObject(object):
  the_id = None

  the_socket = None
  the_socket_address = None
  the_socket_address_ip = None
  the_socket_address_port = None

  the_server = None

  the_process = None
  the_process_id = None
  the_process_name = None

  the_imei = None
  identifier = ""

  # The class "constructor" - It's actually an initializer 
  def __init__(self, in_process_nr, in_process , in_socket , in_socket_address, in_server):
    self.the_id = in_process_nr
    self.the_process = in_process
    self.the_process_id = self.the_process.exitcode
    self.the_process_name = self.the_process.name


    self.the_socket = in_socket
    self.the_socket_address = in_socket_address
    self.the_socket_address_ip = self.the_socket_address[0]
    self.the_socket_address_port = self.the_socket_address[1]


    self.the_server = in_server

    self.identifier = str(self.the_id) + " " + str(self.the_process_name) + " " + str(self.the_socket_address_ip) + " " + str(self.the_socket_address_port) + " "
  #end def init
#end def class  

def processData(the_connection_object , the_data):
  def mysql_open_connection_inside():
    try:
      the_conn = MySQLdb.connect(host= "127.0.0.1",
                          user="user",
                          passwd="password",
                          db="mydb")
    except MySQLdb.Error, e:
      print "Error %d: %s" % (e.args[0], e.args[1])
      time.sleep(30)
      try:
        the_conn = MySQLdb.connect(host= "127.0.0.1",
                            user="user",
                            passwd="password",
                            db="mydb")
      except MySQLdb.Error, e:
        print "Error %d: %s" % (e.args[0], e.args[1])
        print "Unexpected error:", sys.exc_info()[0]
        raise
        sys.exit(0) 
      #end time 2  
    #end try except

    return the_conn
  #end def mysql_open_connection                    



  conn = mysql_open_connection_inside()
  x = conn.cursor()

  add_rawdata = ("INSERT INTO RawData"
                 "(ID,RawData,Type) "
                 "VALUES (%s, %s, %s)")

  data_raw = ('123456', 'ABCD','')



  records_inserted = 0


  the_connection_object.the_imei = ""


  #global clients
  imei = ""
  try:
    thedata = ""
    thedata = " ".join("{:02x}".format(ord(c)) for c in the_data)

    record_to_save = ' '.join(thedata)


    seqtoreply = ""
    seqtoreply = "OK"

    #reply part
    if (seqtoreply != ""): #reply to unit
      try:
        the_connection_object.the_socket.sendall(seqtoreply)
        #echoout(the_connection_object.identifier+"We Replyed With : " + seqtoreply)
      except:
        echoout(the_connection_object.identifier+"Send Reply Error : " + str(sys.exc_info()[1]))
      #end except
    #end of if 


    the_id = "some generated id"
    data_raw  = (the_id, werk_data, 'UNIT')
    try:
      x.execute(add_rawdata, data_raw)
      conn.commit()
      echoout(the_connection_object.identifier+"Raw Data Saved.")
    except:
      conn.rollback()
      echoout(the_connection_object.identifier+" Raw Data NOT Saved : " + str(sys.exc_info()[1]))
    #end of data save insert  
    #echoout("=============================")
    endme = 1
    echoout("")
    conn.close()
  #end try
  except:
    conn.close()
    echoout(the_connection_object.identifier+"Error : " + str(sys.exc_info()[1]))
  #end  try except
#end  def handel function



def handle_p(processnr, server, connection, address):
  this_connection = ConnectionObject(processnr,multiprocessing.current_process(), connection, address, server)
  thisprocess = multiprocessing.current_process()
  this_connection.the_id = ""
  the_address = this_connection.the_socket_address_ip
  the_port = this_connection.the_socket_address_port

  try:
    echoout("New connection from : "+str(the_address)+" on port "+str(the_port))
    close_the_socket = False
    while True:
      #--------------------- recive part -------------------------------------------------
      data = connection.recv(512)
      thedata = ""
      thedata = " ".join("{:02x}".format(ord(c)) for c in data)
      if ((thedata == "") or (thedata == " ") or (data == False)):
        echoout("Socket Closed Remotely : No Data")
        close_the_socket = True
        break
      #end - if data blank
      else :
        processData(this_connection, data)
      #end there is data  
      echoout("=============================")
    #end if while true
  #end try
  except:
    print "handling request, Error : " + str(sys.exc_info()[1])
    close_the_socket = True
    connection.close()
  finally:
    close_the_socket = True
    echoout("Closing socket")
    connection.close()
  #end  try finally
#end  def handel function

def mysql_update(update_statement, update_values):
  conn_update = MySQLdb.connect(host= "127.0.0.1",
                    user="user",
                    passwd="password",
                    db="mydb")
  x_update = conn_update.cursor(MySQLdb.cursors.DictCursor)

  rawdata_data = (update_statement)
  data_rawdata = (update_values)
  allupdateok = False
  #end if there is more  
  try:
    x_update.execute(rawdata_data, data_rawdata)
    conn_update.commit()
    allupdateok = True
    conn_update.close()
  except:
    conn_update.rollback()
    allupdateok = False
    conn_update.close()
    print "Unexpected error:", sys.exc_info()[0]
    raise
  #end of data save insert  
  if (allupdateok == False):
    echoout("Update Raw Data Table Error")
  #end if update
  return allupdateok
#end def mysqlupdate



def echoout(thestring):
  datestring = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  if (thestring != ""):
    outstring = datestring + " " + thestring
    print outstring
  else :
    outstring = thestring
    print outstring
#end - def echoout




class Server(object):
  threads = []
  all_threads = []
  high_proc = ""
  def __init__(self, hostname, port):
    self.hostname = hostname
    self.port = port

  def start(self):
    echoout("Listening for conncetions")
    self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    self.socket.bind((self.hostname, self.port))
    self.socket.listen(10)
    process_number = 1

    inputs = [self.socket]
    while True:
      inready, outready, excready = select.select(inputs, [], [], 30);
      for s in inready:
        if s == self.socket:
          conn, address = self.socket.accept()
          high_processname = ""
          echoout("Got a connection...")
          process = threading.Thread(target=handle_p, args=(process_number,self, conn, address))
          high_processname = process.name
          self.high_proc = high_processname
          process.daemon = True
          process.start()
          self.all_threads.append((process,conn))
          ts = time.time()
          st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
          self.threads.append((process_number,conn,st,0,process))
          process_number = process_number + 1

          print "ACTIVE Threads = " + str(threading.activeCount())

          the_total_count = 0 
          dead_count = 0
          alive_count = 0
          for the_thread in self.all_threads :
            if (the_thread[0].is_alive() == True):
              alive_count = alive_count + 1
            else :
              dead_count = dead_count + 1
              the_thread[1].close()
              the_thread[0].join(0.3)

              self.all_threads.pop(the_total_count)
            #end if alive else 
            the_total_count = the_total_count + 1
          #end for threads  

          print "LIVE Threads = " + str(alive_count)
          print "DEAD Threads = " + str(dead_count)
          print "TOTAL Threads = " + str(the_total_count)
          print ""


        #end if s = socke, new connection
      #end for loop
    #end while truw
    self.socket.close()
  #end def start

#main process part
if __name__ == "__main__":

  start_ip = "0.0.0.0"
  start_port = the_global_port

  #start server
  server = Server(start_ip, start_port)
  try:
    print "Listening on " , start_port
    server.start()
  except:
    print "unexpected, Error : " + str(sys.exc_info()[1])
  finally:
    print "shutting down"

    active_clients = 0
    for process in multiprocessing.active_children():
      try:
        active_clients = active_clients + 1
        process.terminate()
        #process.join()
      except:
        print "Process not killed = " + str(sys.exc_info()[1])
      #end try except 
    #close mysql connection
    print "Active clients  = " + str(active_clients)
  #end try finally
  server.socket.close()
  server.threads = []
  server = None
  print "All done."
#end def main

First of all, it is silly to use threads when you can have 500+ connected clients, you should go asynchronous - look at gevent for example for a very good library, or at least use select (see Python documentation). 首先,当您有500多个连接的客户端时使用线程是很愚蠢的,您应该异步使用-例如,在gevent中查看一个非常好的库,或者至少使用select (请参阅Python文档)。

Then, your code to close the socket in handle_p looks good, indeed when the recv() call comes back with an empty string it means the remote end is disconnected so you break the while , fine. 然后,关闭handle_p的套接字的handle_p看起来不错,确实,当recv()调用返回空字符串时,这意味着远程端已断开连接,因此您可以打断while ,很好。

However, it looks like the remote closed the connection but it is not detected on your side ( recv() doesn't return). 但是,看起来远程已关闭连接,但在您这一边未检测到连接( recv()不返回)。 The best would be then to have a kind of heartbeat to know when you can close the connection. 最好的办法就是知道何时可以关闭连接。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM