20240809-python实现TCP通信

python实现TCP通讯

1.0 版本(备份)
import socket
from logUtils import log
from api import Api


def handle_client(client_socket, addr):
    log.info(f"客户端的ip地址和端口号: {addr}")
    try:
        while True:
            # 接收客户端发送的数据, 这次接收数据的最大字节数是1024
            recv_data = client_socket.recv(1024)
            if not recv_data:
                # 如果没有接收到数据,说明客户端可能已经关闭了连接
                break
                # 对二进制数据进行解码
            recv_content = recv_data #.decode("utf-8")
            log.info(f'-----------------------------------------begin-------------------------------------')
            log.info(f"接收客户端的数据为: {recv_content}")
            api = Api()
            send_data = api.Message_parsing(recv_data)
            # 准备发送的数据
            if send_data:
                print(f'要发送的数据:{send_data}')
                # send_data = b'h\x19\x00hK\x01\x00T\x07\x00\x02`\x00\x01\x00cE\x86A:3\x12\nXQ\x18\x10q!\x06{\x16'
                # 发送数据给客户端
                client_socket.send(send_data)
                print('发送成功!')
            log.info(f'-----------------------------------------end-------------------------------------')
    except Exception as e:
        print(f"处理客户端时发生错误: {e}")
        log.info(f'-----------------------------------------error-------------------------------------')
    finally:
        # 关闭与客户端的套接字
        client_socket.close()
        print(f"与客户端 {addr} 的连接已关闭")


if __name__ == '__main__':
    # 创建tcp服务端套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口号复用,让程序退出端口号立即释放
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 给程序绑定端口号
    tcp_server_socket.bind(("0.0.0.0", 6999))
    # 设置监听
    tcp_server_socket.listen(128)
    log.info("服务器开始监听端口号: 6999")

    try:
        while True:
            # 等待客户端建立连接的请求
            service_client_socket, ip_port = tcp_server_socket.accept()
            log.info(f"客户端的IP地址和端口号: {ip_port}")

            # 在单独的线程或进程中处理客户端连接,这里简化为直接调用处理函数
            handle_client(service_client_socket, ip_port)


    except KeyboardInterrupt:
        print("服务器接收到停止信号,正在关闭...")
    finally:
        # 关闭服务端的套接字(通常在实际应用中,这一步会在所有客户端连接都关闭后执行,但在这里我们直接响应中断信号)
        tcp_server_socket.close()

我想把他修改成可以服务端主动发报文的,进行了这样的操作:

import socket
from logUtils import log
from api import Api


def handle_client(client_socket, addr):
    log.info(f"客户端的ip地址和端口号: {addr}")
    # status = False
    try:
        while True:
            api = Api()
            # if status:
            #     print(f'04:\n20:插座启停')
            #     number = input("请输入你要调用的接口编号:")
            #
            #     avtive_send_data , statu = api.get_active_send_data(str(number))
            #     status = statu
            #     if avtive_send_data:
            #         print(f'要发送的数据:{avtive_send_data}')
            #         # send_data = b'h\x19\x00hK\x01\x00T\x07\x00\x02`\x00\x01\x00cE\x86A:3\x12\nXQ\x18\x10q!\x06{\x16'
            #         # 发送数据给客户端
            #         client_socket.send(avtive_send_data)
            #         print('发送成功!')

            # 接收客户端发送的数据, 这次接收数据的最大字节数是1024
            recv_data = client_socket.recv(1024)
            if not recv_data:
                # 如果没有接收到数据,说明客户端可能已经关闭了连接
                break
                # 对二进制数据进行解码
            recv_content = recv_data #.decode("utf-8")
            log.info(f'-----------------------------------------begin-------------------------------------')
            log.info(f"接收客户端的数据为: {recv_content}")

            send_data = api.Message_parsing(recv_data)
            # 准备发送的数据
            if send_data:
                log.info(f'要发送的数据:{send_data}')
                print(f'要发送的数据:{send_data}')
                # send_data = b'h\x19\x00hK\x01\x00T\x07\x00\x02`\x00\x01\x00cE\x86A:3\x12\nXQ\x18\x10q!\x06{\x16'
                # 发送数据给客户端
                client_socket.send(send_data)
                log.info('发送成功!')
                print('发送成功!')
            log.info(f'-----------------------------------------end-------------------------------------')
    except Exception as e:
        log.info(f"处理客户端时发生错误: {e}")
        print(f"处理客户端时发生错误: {e}")
        log.info(f'-----------------------------------------error-------------------------------------')
    finally:
        # 关闭与客户端的套接字
        client_socket.close()
        print(f"与客户端 {addr} 的连接已关闭")


if __name__ == '__main__':
    # 创建tcp服务端套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口号复用,让程序退出端口号立即释放
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 给程序绑定端口号
    tcp_server_socket.bind(("0.0.0.0", 6999))
    # 设置监听
    tcp_server_socket.listen(128)
    log.info("服务器开始监听端口号: 6999")

    try:
        while True:
            # 等待客户端建立连接的请求
            service_client_socket, ip_port = tcp_server_socket.accept()
            log.info(f"客户端的IP地址和端口号: {ip_port}")

            # 在单独的线程或进程中处理客户端连接,这里简化为直接调用处理函数
            handle_client(service_client_socket, ip_port)


    except KeyboardInterrupt:
        print("服务器接收到停止信号,正在关闭...")
    finally:
        # 关闭服务端的套接字(通常在实际应用中,这一步会在所有客户端连接都关闭后执行,但在这里我们直接响应中断信号)
        tcp_server_socket.close()

具体就是增加了一个状态变量,如果心跳了,就把状态设置成true,状态为true时,每次发送、接收报文都先判断一下是否需要主动发送。目前原理上感觉是可行的,我也写了一个测试的服务端-客户端,测试结果:可行。

但是这样子,在api.py里增加返回状态时,出现了报错。

具体是SIM卡上报信息是报错了:

cannot unpack non-iterable NoneType object

这个报错是对一个NoneType进行了数据操作,我找了半天也没找到SIM卡接口里有什么数据操作。

最后恢复一下代码,又能运行了。我再一步一步看看,到底是改了哪里出现了错误。

排查错误:
import socket
from logUtils import log
from api import Api


def handle_client(client_socket, addr):
    log.info(f"客户端的ip地址和端口号: {addr}")
    status = False
    try:
        while True:
            api = Api()
            if status:
                print(f'04:\n20:插座启停')
                number = input("请输入你要调用的接口编号:")

                avtive_send_data , statu = api.get_active_send_data(str(number))
                status = statu
                if avtive_send_data:
                    print(f'要发送的数据:{avtive_send_data}')
                    # send_data = b'h\x19\x00hK\x01\x00T\x07\x00\x02`\x00\x01\x00cE\x86A:3\x12\nXQ\x18\x10q!\x06{\x16'
                    # 发送数据给客户端
                    client_socket.send(avtive_send_data)
                    print('发送成功!')

            # 接收客户端发送的数据, 这次接收数据的最大字节数是1024
            recv_data = client_socket.recv(1024)
            if not recv_data:
                # 如果没有接收到数据,说明客户端可能已经关闭了连接
                break
                # 对二进制数据进行解码
            recv_content = recv_data #.decode("utf-8")
            log.info(f'-----------------------------------------begin-------------------------------------')
            log.info(f"接收客户端的数据为: {recv_content}")

            send_data = api.Message_parsing(recv_data)
            # 准备发送的数据
            if send_data:
                log.info(f'要发送的数据:{send_data}')
                print(f'要发送的数据:{send_data}')
                # send_data = b'h\x19\x00hK\x01\x00T\x07\x00\x02`\x00\x01\x00cE\x86A:3\x12\nXQ\x18\x10q!\x06{\x16'
                # 发送数据给客户端
                client_socket.send(send_data)
                log.info('发送成功!')
                print('发送成功!')
            log.info(f'-----------------------------------------end-------------------------------------')
    except Exception as e:
        log.info(f"处理客户端时发生错误: {e}")
        print(f"处理客户端时发生错误: {e}")
        log.info(f'-----------------------------------------error-------------------------------------')
    finally:
        # 关闭与客户端的套接字
        client_socket.close()
        print(f"与客户端 {addr} 的连接已关闭")


if __name__ == '__main__':
    # 创建tcp服务端套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口号复用,让程序退出端口号立即释放
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    # 给程序绑定端口号
    tcp_server_socket.bind(("0.0.0.0", 6999))
    # 设置监听
    tcp_server_socket.listen(128)
    log.info("服务器开始监听端口号: 6999")

    try:
        while True:
            # 等待客户端建立连接的请求
            service_client_socket, ip_port = tcp_server_socket.accept()
            log.info(f"客户端的IP地址和端口号: {ip_port}")

            # 在单独的线程或进程中处理客户端连接,这里简化为直接调用处理函数
            handle_client(service_client_socket, ip_port)


    except KeyboardInterrupt:
        print("服务器接收到停止信号,正在关闭...")
    finally:
        # 关闭服务端的套接字(通常在实际应用中,这一步会在所有客户端连接都关闭后执行,但在这里我们直接响应中断信号)
        tcp_server_socket.close()

把注释加回来,可以心跳。

func = Function_mapping[AFN].get(Fn)
log.info(f"接口匹配成功!")
data , status = func(dict_data)
log.info(f'type:{type(data)}')
return data, status

在接收数据这里加上status后,连登录都报错了。有两种报错

  1. non-hexadecimal number found in fromhex() arg at position 63

  2. too many values to unpack (expected 2):

    这通常意味着你尝试将一个可迭代对象(如列表、元组等)解包到比该对象实际包含的元素数量更少的变量中。a, b = (1, 2, 3) # 这里会引发错误

意料之中,因为我还没修改函数返回值。

#先返回False,返回True后就会直接让输入接口编号了
return byte_resp_data, False

send_data , status = api.Message_parsing(recv_data)
# 准备发送的数据
if send_data:
	log.info(f'要发送的数据:{send_data}')
	print(f'要发送的数据:{send_data}')

	# 发送数据给客户端
	client_socket.send(send_data)
    log.info('发送成功!')
    print('发送成功!')

把登录和接收的数据修改后,可以成功登录了!

所以登录后的登录验证开始报错了,接下来把登录验证修改一下

return byte_resp_data, True

果然,修改后能成功进行登录验证了。

接下来试试能不能请求主动发送的接口。

试了一下,能发出去,但是收到的信息很。。。乱,SIM后面跟了两条心跳。


20240812

主动请求的接口发出去后,下一条是SIM卡信息,报错了

cannot unpack non-iterable NoneType object

SIM卡信息里我没有响应数据,只是解析了一下接收的数据,而且解析成功了,为什么会报这个错??

紧接着是重新发送了登录和登录验证的报文。

接下来试试把返回True放在SIM卡信息后

这样貌似是可行的,接收了一个上报插座实时状态的报文:

 -----------------------------------------begin-------------------------------------
2024-08-12 11:25:36,611|root|2463|INFO| 接收客户端的数据为: b'h\x16\x00h`\x08\x08\x00\x10\x01\x0e`\x00\x03\x00\x01\x00\x01\xef\x1f\x00\x00\x00\x00\x00\x00[H\x16'
2024-08-12 11:25:36,611|root|2463|INFO| 接收的数据:b'h\x16\x00h`\x08\x08\x00\x10\x01\x0e`\x00\x03\x00\x01\x00\x01\xef\x1f\x00\x00\x00\x00\x00\x00[H\x16'
2024-08-12 11:25:36,611|root|2463|INFO| 十六进制:681600686008080010010E60000300010001EF1F0000000000005B4816
2024-08-12 11:25:36,611|root|2463|INFO| split_data:68 16 00 68 60 08 08 00 10 01 0E 60 00 03 00 01 00 01 EF 1F 00 00 00 00 00 00 5B 48 16
2024-08-12 11:25:36,611|root|2463|INFO| 数据解析:{'head_str': {'head_str': '68160068', 'head1': '68', 'length': 22, 'head2': '68'}, 'user_data_region': '6008080010010E60000300010001EF1F000000000000', 'control_region': {'control_region': '60', 'control_region_bin': '01100000', 'DIR': '0', 'PRM': '1', 'function_code': 0}, 'address_region': '08080010', 'app_region': {'app_region': '010E60000300010001EF1F000000000000', 'app_region_function_code': '010E', 'app_region_SEQ': {'app_region_SEQ': '60', 'app_region_SEQ_bin': '01100000', 'SEQ_TPV': '0', 'SEQ_FIR': '1', 'SEQ_FIN': '1', 'PSEQ_RSEQ': '0000'}, 'Data_unit_identification': {'Data_unit_identification': '000300', 'DA_Pn': '00', 'DT_Fn': '0300'}, 'Specific_data': '010001EF1F000000000000'}, 'crc_str': '5B48', 'real_crc': '485B', 'tail_str': '16'}
2024-08-12 11:25:36,611|root|2463|INFO| 地址域已保存:08080010
2024-08-12 11:25:36,612|root|2463|INFO| AFN: 0EH, Fn: 3
2024-08-12 11:25:36,612|root|2463|INFO| CRC16: 5B48
2024-08-12 11:25:36,612|root|2463|INFO| crc校验通过:计算crc:5B48, 接收crc:485B
2024-08-12 11:25:36,612|root|2463|INFO| 接口匹配成功!
2024-08-12 11:25:36,612|root|2463|INFO| 处理客户端时发生错误: 'NoneType' object is not callable
2024-08-12 11:25:36,612|root|2463|INFO| -----------------------------------------error-------------------------------------

由于没有写这个接口,所以默认返回了NoneType,报错了。然后它就重新进入了登录-登录验证-SIM卡信息上报的流程。插座可以用,貌似只有一分钟。

我又重新发送了启动报文,启动成功,又接收到了插座实时状态的报文:

2024-08-12 11:34:51,820|root|2463|INFO| -----------------------------------------begin-------------------------------------
2024-08-12 11:34:51,820|root|2463|INFO| 接收客户端的数据为: b'h\x16\x00h`\x08\x08\x00\x10\x01\x0e`\x00\x03\x00\x01\x00\x01\xef\x1f\x00\x00\x01\x00\x00\x00\xa7I\x16'
2024-08-12 11:34:51,822|root|2463|INFO| 接收的数据:b'h\x16\x00h`\x08\x08\x00\x10\x01\x0e`\x00\x03\x00\x01\x00\x01\xef\x1f\x00\x00\x01\x00\x00\x00\xa7I\x16'
2024-08-12 11:34:51,822|root|2463|INFO| 十六进制:681600686008080010010E60000300010001EF1F000001000000A74916
2024-08-12 11:34:51,822|root|2463|INFO| split_data:68 16 00 68 60 08 08 00 10 01 0E 60 00 03 00 01 00 01 EF 1F 00 00 01 00 00 00 A7 49 16
2024-08-12 11:34:51,822|root|2463|INFO| 数据解析:{'head_str': {'head_str': '68160068', 'head1': '68', 'length': 22, 'head2': '68'}, 'user_data_region': '6008080010010E60000300010001EF1F000001000000', 'control_region': {'control_region': '60', 'control_region_bin': '01100000', 'DIR': '0', 'PRM': '1', 'function_code': 0}, 'address_region': '08080010', 'app_region': {'app_region': '010E60000300010001EF1F000001000000', 'app_region_function_code': '010E', 'app_region_SEQ': {'app_region_SEQ': '60', 'app_region_SEQ_bin': '01100000', 'SEQ_TPV': '0', 'SEQ_FIR': '1', 'SEQ_FIN': '1', 'PSEQ_RSEQ': '0000'}, 'Data_unit_identification': {'Data_unit_identification': '000300', 'DA_Pn': '00', 'DT_Fn': '0300'}, 'Specific_data': '010001EF1F000001000000'}, 'crc_str': 'A749', 'real_crc': '49A7', 'tail_str': '16'}
2024-08-12 11:34:51,822|root|2463|INFO| 地址域已保存:08080010
2024-08-12 11:34:51,822|root|2463|INFO| AFN: 0EH, Fn: 3
2024-08-12 11:34:51,822|root|2463|INFO| CRC16: A749
2024-08-12 11:34:51,824|root|2463|INFO| crc校验通过:计算crc:A749, 接收crc:49A7
2024-08-12 11:34:51,824|root|2463|INFO| 接口匹配成功!
2024-08-12 11:34:51,824|root|2463|INFO| 处理客户端时发生错误: 'NoneType' object is not callable
2024-08-12 11:34:51,824|root|2463|INFO| -----------------------------------------error-------------------------------------

第一次接收,没来的及插电就断电了。第二次用充电桩烧了一壶水,四分多钟。两次接收的报文有一个差别:

第一次:68 16 00 68 60 08 08 00 10 01 0E 60 00 03 00 01 00 01 EF 1F 00 00 00 00 00 00 5B 48 16

第二次:68 16 00 68 60 08 08 00 10 01 0E 60 00 03 00 01 00 01 EF 1F 00 00 01 00 00 00 A7 49 16

订单号后面的两个个字节,从0000变成了0100,这个字节代表功率。

按照接口文档来说,我请求了开启插座,它应该给我发一个执行是否成功的报文,然而并没有发。

我怀疑是他们的示例报文有问题,示例报文和接口文档有很多不符。

接下来修改一下发送报文,按照接口文档的重新发送一个。

在修改报文时,发现终端地址域后边的字节是主机地址,不是AFN,修改了这个bug后,对发送的地址域中的主机地址MSA进行修改,随便赋值了'AB',发现数据可以发送,不过一直卡在登录。(后续经过验证,和MSA无关)

修改了MSA为00后,依旧有此问题,查看发送报文,发现数据标识没了,原因是我把字典中的键名改了。。。

按照接口文档修改报文后,能成功发送,但是插座不能启动。

发送45秒后,接收到心跳请求,报错:

too many values to unpack (expected 2),这个报错是因为我没有在心跳接口上返回状态‘True’

接下来排查一下为什么按照接口文档发送报文,插座不能启动:

1. 修改数据单元标识为:001400

这个数据单元标识是他示例报文里的,接口文档里,控制插座的应该是002000

结果:不行

2.修改订单号为:00FE1F00

这个订单号是他示例报文里的

结果:不行

3.修改地址域

最后发现没有获取到正确的地址域,我在api类里定义了self.address_term = '',每次分析接收报文时,都把地址域存一下,但是在调用它的时候,显示为空。

解决了,每次循环时,我都实例化了一下api类,导致每次都置空,把实例化放循环外边就行了

这个问题解决了,就是地址域和数据单元标识的问题,不能按接口文档里来。

2024-08-12 17:58:06,471|root|14442|INFO| -----------------------------------------begin-------------------------------------
2024-08-12 17:58:06,471|root|14442|INFO| 接收客户端的数据为: b'h\x16\x00h`\x08\x08\x00\x10\x01\x0e`\x00\x03\x00\x01\x00\x01\x00\xfe\x1f\x00\x00\x00\x00\x002\xf5\x16'
2024-08-12 17:58:06,471|root|14442|INFO| 接收的数据:b'h\x16\x00h`\x08\x08\x00\x10\x01\x0e`\x00\x03\x00\x01\x00\x01\x00\xfe\x1f\x00\x00\x00\x00\x002\xf5\x16'
2024-08-12 17:58:06,471|root|14442|INFO| 十六进制:681600686008080010010E6000030001000100FE1F000000000032F516
2024-08-12 17:58:06,471|root|14442|INFO| split_data:68 16 00 68 60 08 08 00 10 01 0E 60 00 03 00 01 00 01 00 FE 1F 00 00 00 00 00 32 F5 16
2024-08-12 17:58:06,472|root|14442|INFO| 数据解析:{'head_str': {'head_str_': '68160068', 'head1': '68', 'length': 22, 'head2': '68'}, 'user_data_region': '6008080010010E6000030001000100FE1F0000000000', 'control_region': {'control_region': '60', 'control_region_bin': '01100000', 'DIR': '0', 'PRM': '1', 'function_code': 0}, 'address_region': {'address_region_': '0808001001', 'address_term': '08080010', 'address_MSA': '01'}, 'app_region': {'app_region_': '0E6000030001000100FE1F0000000000', 'app_region_function_code': '0E', 'app_region_SEQ': {'app_region_SEQ': '60', 'app_region_SEQ_bin': '01100000', 'SEQ_TPV': '0', 'SEQ_FIR': '1', 'SEQ_FIN': '1', 'PSEQ_RSEQ': '0000'}, 'Data_unit_identification': {'Data_unit_identification_': '000300', 'DA_Pn': '00', 'DT_Fn': '0300'}, 'Specific_data': '01000100FE1F0000000000'}, 'crc_str': '32F5', 'real_crc': 'F532', 'tail_str': '16'}
2024-08-12 17:58:06,472|root|14442|INFO| 地址域已保存:08080010
2024-08-12 17:58:06,472|root|14442|INFO| AFN: 0EH, Fn: 3
2024-08-12 17:58:06,472|root|14442|INFO| CRC16: 32F5
2024-08-12 17:58:06,472|root|14442|INFO| crc校验通过:计算crc:32F5, 接收crc:F532
2024-08-12 17:58:06,472|root|14442|ERROR| 接口匹配失败,找不到接口!
2024-08-12 17:58:06,472|root|14442|INFO| 处理客户端时发生错误: 'NoneType' object is not callable
2024-08-12 17:58:06,472|root|14442|INFO| -----------------------------------------error-------------------------------------

总结

现在这个tcp通信算是实现了,接下来开始写充电桩的接口。

posted @ 2024-08-09 18:06  marverdol  阅读(67)  评论(0)    收藏  举报