tcp客户端重连例子 python

#!/usr/bin/env python2.7
# coding:utf-8
# -*- coding: utf-8 -*-
"""
2021.12.31 底盘控制客户端
2022.1.6v
2022.1.7 增加手动转弯
"""
from socket import *
import json
import struct
import threading
import time
from serial_control_class import ser_c, con_s, ana
from correct_speed import Correct_val
import platform
import ctypes
import inspect
from status_total_data import s_data
from _chassis_logging_init import c_l,logger_motor
python_version = platform.python_version()[0:1]

"""2022 . 1.7  修改为主动检查掉线模式"""
"""底盘的客户端参数设置"""
class Client_link():
    """用于初始化。tcp客户端"""
    def __init__(self,HOST = '127.0.0.1',PORT = 8081):
        # self.HOST = HOST
        # self.PORT = PORT
        #self.HOST = "10.168.1.161"  # 控制服务器地址
        self.HOST = "10.168.1.147" # 控制服务器地址
        # self.HOST = "127.0.0.1"  # 控制服务器地址
        self.PORT = 8888
        self.repeat_num = 10 #设置当没有接收到完整数据帧的时候,重复连接的次数
        self.init_motor()



        #重连故障参数准备
        self.no_action_bool = False  # 用于控制重连不上的时候切换为遥控器模式
        self.fail_link_num = 0 # 用于在主动线程中统计,设定的单位时间内没有发送成功的次数,越大说明当前越没有发出去
        self.send_bool = False # 发送前置为假,发送后置为真
        self.recycle_bool = False # 在成功拉起发送与接收线程后置为真
        self.time_num = 5 # 5*0.2 即为设定为 1秒 堵塞等待时间

    def init_motor(self):
        """用于初始化,关节电机控制参数"""
        self.m1_angle_speed = 0
        self.m1_cnt_speed = 0
        self.m1_angle_dis = 0
        self.m1_cnt_dis= 0

        self.m2_angle_speed = 0
        self.m2_cnt_speed = 0
        self.m2_angle_dis = 0 
        self.m2_cnt_dis = 0

    def infor_analysis(self,json_buf):
        """通过分析接收到的,下位机发送的json数据,对现有控制数据进行更新"""

        if json_buf["status"] == "floor":
            self.move_floor(json_buf)
        elif json_buf["status"] == "Manu_m":
            self.manual_control(json_buf)
        else:
            c_l.warning("!!!接收的不是底盘的控制命令(检测各客户端的地址分配是否正确!),当前控制命令为: " + str(json_buf))
            #self.save_motor_c(json_buf["dict"]) 预留接口,方便后续该巡检项目的二次开发
        # elif json_buf["Dete_m"] == "Odom_m":

    def move_floor(self, json_buf={"status": "floor", "dict": {"routeinfo": [(1, 1), (4, 1), (4, 2)]}}):
        """解析路径规划的地面数据进行运动"""
        location_list = json_buf["dict"]["routeinfo"]
        #
        # location_list = [(9,1),(4,1),(4,2)]
        # location_list = [(1, 1), (4, 1), (4, 2),(7, 2)]
        c_l.warning("________________低盘模式:" +str(location_list))
        max_num = len(location_list)
        self.time_num = 0.2/0.015
        for num, val in enumerate(location_list):
            if (num + 2) <= max_num:
                if val[0] == location_list[num + 1][0]:
                    self.move_f_b(location_list[num + 1][1] - val[1])
                else:
                    self.move_l_r(location_list[num + 1][0] - val[0])

    def move_f_b(self, dis_num):
        if dis_num > 0:
            con_s.x = 0.015
            con_s.y = 0
            time.sleep(dis_num * self.time_num)
            # 说明向前
        else:
            con_s.x = -0.015
            con_s.y = 0
            time.sleep(-dis_num * self.time_num)

        con_s.x = 0
        con_s.y = 0

    def move_l_r(self, dis_num):
        if dis_num > 0:
            con_s.x = 0
            con_s.y = 0.015
            time.sleep(dis_num * self.time_num)
            # 说明向前
        else:
            con_s.x = 0
            con_s.y = -0.015
            time.sleep(-dis_num * self.time_num)

        con_s.x = 0
        con_s.y = 0

    def chesis_move_stop(self):
        con_s.x = 0
        con_s.y = 0
        con_s.p = 0
    


    def manual_control(self, json_buf):
        """用于手动控制(通过对接的客户端的数据更新),小车底盘的行动,目前只提供,简单的单一边行走"""
        json_buf_dict = json_buf["dict"]
        if json_buf_dict['Car_forward'] or json_buf_dict['Car_back'] or json_buf_dict['Car_left'] or json_buf_dict['Car_right']:
            c_l.warning(str(json_buf_dict))
        print("666")

        if json_buf_dict["Car_forward"] == True:

            c_l.warning("发送向前")
            con_s.x = 0.015  # min:0.015  nor: 0.03   max:1.26  m/s
            con_s.y = 0  # -0.1
            #
        elif json_buf_dict["Car_back"] == True:
            c_l.warning("发送向后")
            con_s.x = - 0.015 # min:0.015  nor: 0.03   max:1.26  m/s
            con_s.y = 0  # -0.1

        elif json_buf_dict["Car_left"] == True:
            c_l.warning("发送向左")
            con_s.x = 0  # min:0.015  nor: 0.03   max:1.26  m/s
            con_s.y = 0.015 # -0.1

        elif json_buf_dict["Car_right"] == True:
            c_l.warning("发送向右")
            con_s.x = 0  # min:0.015  nor: 0.03   max:1.26  m/s
            con_s.y = -0.015 # -0.1

        elif json_buf_dict["Car_turnleft"] == True:
            c_l.warning("发送向左转")
            con_s.x = 0  # min:0.015  nor: 0.03   max:1.26  m/s
            con_s.y = 0 # -0.1
            con_s.p = 0.028

        elif json_buf_dict["Car_turnright"] == True:
            c_l.warning("发送向右转")
            con_s.x = 0  # min:0.015  nor: 0.03   max:1.26  m/s
            con_s.y = 0  # -0.1
            con_s.p = -0.028

        elif json_buf_dict["Car_stop"] == True:
            c_l.warning("发送-停止")
            con_s.x = 0  # min:0.015  nor: 0.03   max:1.26  m/s
            con_s.y = 0  # -0.1
            con_s.p = 0

        else:
            c_l.warning("底盘停止!")
            con_s.x = 0  # min:0.015  nor: 0.03   max:1.26  m/s
            con_s.y = 0  # -0.1
            con_s.p = 0


    def save_motor_c(self, data_dict):
        """预留接口用于显示等"""

        s_data.m1_angle_speed = data_dict["m1_a_speed"]
        s_data.m1_cnt_speed = data_dict["m1_cnt_speed"]
        s_data.m1_angle_dis = data_dict["m1_a_dis"]
        s_data.m1_cnt_dis = data_dict["m1_cnt_dis"]

        s_data.m2_angle_speed = data_dict["m2_a_speed"]
        s_data.m2_cnt_speed = data_dict["m2_cnt_speed"]
        s_data.m2_angle_dis = data_dict["m2_a_dis"]
        s_data.m2_cnt_dis = data_dict["m2_cnt_dis"]


    def start_client(self):
        """开启客户端"""



        # 先拉起一个测量通讯时间阻塞的线程用于帮助重连
        self.t_recycle = threading.Thread(target=self.recycle)
        self.t_recycle.setDaemon(True)  # 当发送模块长时间无法发送对接收与发送线程主动抛出异常,并调整底盘到遥控器模式
        self.t_recycle.start()

        self.link()


    def pull_send_and_recv(self):

        self.t_client_recv = threading.Thread(target=self.run_recv)
        self.t_client_recv.setDaemon(True) # 当断开后,通通过主动抛出异常自动回收
        self.t_client_recv.start()

        self.t_client_send = threading.Thread(target=self.listen_send)
        self.t_client_send.setDaemon(True) # 当断开后,通过主动抛出异常自动回收
        self.t_client_send.start()


    def recycle(self):
        """通过 送入异常引发线程奔溃控制 进行控制"""
        while True:
            if self.recycle_bool:

                if self.send_bool:
                    self.fail_link_num = 0
                else:
                    self.fail_link_num = self.fail_link_num + 1

                time.sleep(0.2)
                if self.fail_link_num > self.time_num: # 即2s内没有

                    try:
                        logger_motor.warning("堵塞超过"+str(self.time_num*0.2)+"秒,回收当前两个连接线程,重新连接服务端")
                        self._async_raise(self.t_client_recv.ident,SystemExit)
                        print("回收完成接收线程")
                        self._async_raise(self.t_client_send.ident, SystemExit)
                        print("回收完成发送线程")
                    except:
                        print("上次重连失败-----")

                    self.no_action_bool = True

                    self.recycle_bool = False
                    self.client.close()
                    print("开始重连......")
                    self.link()
            else:
                time.sleep(0.5)



    def _async_raise(self,tid, exctype):
        # todo 强制退出线程  忽略掉报错
        """raises the exception, performs cleanup if needed"""
        tid = ctypes.c_long(tid)
        if not inspect.isclass(exctype):
            print(".." * 20)
            exctype = type(exctype)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
        if res == 0:
            print("0" * 10)
            raise ValueError("通过检测延时函数主动抛入 变量异常 进入,用于终止当前线程")
        elif res != 1:
            print("1" * 10)
            # """if it returns a number greater than one, you're in trouble,
            # and you should call it again with exc=NULL to revert the effect"""
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
            raise SystemError("通过检测延时函数主动抛入 系统异常 进入,用于终止当前线程")

    def link(self):
        """为防止连接服务端失败,这里拉起一个连接线程持续守护,当客户端收发线程,由于无法连接服务端失效的时候执行重新连接"""



        if self.no_action_bool:
            logger_motor.warning("准备重连为避免服务器损坏无法遥控控制小车底盘,现调整为 可被遥控模式")
            ser_c.movement_moudle = "无动作模式"

        try:
            self.client = socket(AF_INET, SOCK_STREAM)
            self.client.bind(('', 4449))
            self.client.connect((self.HOST, self.PORT))
            c_l.warning("成功连接服务端,准备拉起发送与监听线程...................")
            # self.feed_back_control_bool = True # 用于控制,接收与发送线程能否反馈解锁当前的守护拉起线程
            self.pull_send_and_recv()

        except OSError as e:
            if e.errno == 10048:
                c_l.warning("已成功连接底盘端口:4441,自动跳过该重复连接")

            else:
                self.recycle_bool = True  # 为真开启统计连接阻塞,如果发送线程的阻塞过长则回收掉两个线程
                self.send_bool = False
                print("重连失败......")
                print(ser_c.movement_moudle)
                print("*"*50)




    def run_recv(self):
        """客户端监听程序"""
        print('开始连接下位机服务端......')  # 等待cilent的连接
        while True:
            try:

                length = self.receive_real(4)
                len_data = struct.unpack('i', length)
                len_data = len_data[0]

                buf = self.receive_real(len_data)

                json_buf = json.loads(buf)
                self.infor_analysis(json_buf)

            except:
                self.chesis_move_stop()
                self._async_raise(self.t_client_send.ident, SystemExit)
                print("回收完成发送线程313")


                self.no_action_bool = True
                self.recycle_bool = True
                self.send_bool = False # 在上面回收了发送线程后,确保控制判断的重连的变量为假

                logger_motor.error('主动抛出接收模块异常,终止接收线程,并通知守护线程重新拉起接收线程模块,'
                                   '此时切换底盘模式为无动作模式,可以被遥控器控制')

                # 主动抛出接收模块异常,引发接收异常,使得系统回收该监听线程
                raise RuntimeError('主动抛出接收模块异常,终止接收线程,并通知守护线程重新拉起接收线程模块,'
                                   '此时切换底盘模式为无动作模式,可以被遥控器控制')








    def receive_real(self,len_data):
        """接收真实数据"""
        """ len_data 为预先发过来的真实数据长度 """
        buf = b''
        recv_size = 0
        recv_num = 0  # 接收次数

        buf = self.client.recv(len_data)

        while (len(buf) <len_data and recv_num < self.repeat_num ):
            buftmp = self.client.recv(len_data - len(buf))
            buf = buf + buftmp
            recv_num = recv_num + 1

        if len(buf) != len_data:
            logger_motor("服务端应发送长度为" + str(len_data) +", 实际接收长度为: " + str(len(buf))  )
            con_s.x = 0
            con_s.y = 0
            logger_motor.error("!!!!!!!!!!!返回的接收数据为错误数据,可能会引起错误!!!!!!!!!!!!!!")

        return buf

    def listen_send(self):
        """用于发送底盘里程计数值,注意这里为两组数据,一组为累加完成后的里程计数值,另一组为规定的清空缓冲区的数值。"""

        data_dict = {"status": "chessis_s", "dict": {"point_x": 0, "point_y": 0, "point_p": 0, "point_x": 0, "point_y": 0, "point_p": 0}}
        print("准备开启发送数据线程------")

        while True:

            data_dict["dict"]["point_x"] = ana.point_x
            data_dict["dict"]["point_y"] = ana.point_y
            data_dict["dict"]["point_p"] = ana.point_p
            data_dict["dict"]["point_x1"] = ana.point_x1
            data_dict["dict"]["point_y1"] = ana.point_y1
            data_dict["dict"]["point_p1"] = ana.point_p1


            
            self.send_data(data_dict)
            ser_c.movement_moudle = "差速模式" # 数据发送到服务端成功切换为差速模式

            self.send_bool = True #注意这里  self.send_bool 变量的位置,需要将为的部分处于延时发送时间占用最多的部分

            time.sleep(0.3)
            
            self.send_bool = False



    def send_data(self,data):
          """用于发送数据"""

          try:

              data = json.dumps(data)
              length = len(data)
              len_data = struct.pack('i', length)
              # 固定字节长度,告知数据长度为多少

              if (len_data != b'') and (data.encode('utf-8') != b''):
                  self.client.sendall(len_data)
                  self.client.sendall(data.encode('utf-8'))

                  #2022 修改为 sendall 用于避免数据完整性,如需要交互高效选择send
                  # self.client.send(len_data)
                  # self.client.send(data.encode('utf-8'))



          except:
              self.chesis_move_stop()
              self._async_raise(self.t_client_recv.ident, SystemExit)
              print("回收完成接收线程420")

              self.no_action_bool = True
              self.recycle_bool = True
              self.send_bool = False  # 在上面回收了发送线程后,确保控制判断的重连的变量为假

              logger_motor.error('主动抛出发送模块异常,终止发送线程,并通知守护线程重新拉起发送线程模块,'
                                 '此时切换底盘模式为无动作模式,可以被遥控器控制')

              # 主动抛出发送模块异常,引发接收异常,使得系统回收该监听线程
              raise RuntimeError('主动抛出发送模块异常,终止发送线程,并通知守护线程重新拉起发送线程模块,'
                                 '此时切换底盘模式为无动作模式,可以被遥控器控制')




def init_chessis():
    """用于初始化,底盘与里程计的,初始化参数"""

    correct_v = Correct_val(ana=ana, con_s=con_s)
    correct_v.time_interval = 0.2  # 设置瞬时速度的间隔时间,程序通过解析间隔时间内码盘移动的平均度数给出速度值
    correct_v.ignore_num = 5  # 由于加速度的存在,在启动阶段的实时速度测量由于硬件原因会出现误差,设置忽略前多少次的速度计算值

    con_s.x = 0
    con_s.y = 0
    con_s.p = 0

    ser_c.interval_dis = 200000  # 里程计清零距离 单位 (毫米)
    ser_c.set_safe_area(200000, 200000)  # 设置一个矩形安全范围
    ser_c.x_deviation_param = 5  # 当平移时x轴方向偏差超过该值开始进行纠偏
    ser_c.y_deviation_param = 5  # 当平移时y轴方向偏差超过该值开始进行纠偏
    ser_c.p_deviation_param = 5  # 当平移时角度方向偏差超过该值开始进行纠偏

    ser_c.set_port('COM4') # 对应是拿铁熊猫左上角的那个usb口
    ser_c.movement_moudle = "无动作模式" # 在连接服务端成功后会切换为差数模式,差速模式下无法被遥控器控制,连接断线自动切换为无动作模式
    ser_c.open_serial_control()

    # 设置差速模式下的角度纠偏,最大的角度转动速度
    # ser_c.max_correct_p = 0.05  # rad/s #左右
    # ser_c.turn_hint_rectifying_bool = True #开启运动纠偏模型
    ser_c.error_x_y_min_num = 5  # mm
    ser_c.error_p_min_num = 10
    ser_c.max_correct_p = 0.3  # rad/s
    ser_c.max_correct_x = 0.03  # m/s
    ser_c.max_x_y_rang_change_num = 50  # mm(分母x y
    ser_c.max_p_rang_change_num = 10  # mm(分母x y

    print("..........")
    print("打印当前时刻码盘的返回值")
    print(ana.point_x)
    print(ana.point_y)
    print(ana.point_p)
    print("..........")




if __name__ == '__main__':

    # #初始化底盘驱动程序
    # init_chessis()

    #准备拉起 客户端
    c = Client_link()
    c.start_client()
    c_l.warning("开启tcp客户端线程拉起完成")

    while True:
        time.sleep(20)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员进化不脱发!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值