struct linger 用法

本文详细介绍了Linux环境下TCP连接断开时的优雅退出与强制退出方式,通过设置socket描述符的linger结构体属性来实现不同断开策略。解释了l_onoff与l_linger参数的不同作用及具体用法,提供了实际应用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux下tcp连接断开的时候调用close()函数,有优雅断开和强制断开两种方式。

那么如何设置断开连接的方式呢?是通过设置socket描述符一个linger结构体属性。

linger结构体数据结构如下: 

#include <arpa/inet.h>

struct linger {
  int l_onoff;
  int l_linger;
};

 

三种断开方式:

1. l_onoff = 0; l_linger忽略

close()立刻返回,底层会将未发送完的数据发送完成后再释放资源,即优雅退出。

 

2. l_onoff != 0; l_linger = 0;

close()立刻返回,但不会发送未发送完成的数据,而是通过一个REST包强制的关闭socket描述符,即强制退出。

 

3. l_onoff != 0; l_linger > 0;

close()不会立刻返回,内核会延迟一段时间,这个时间就由l_linger的值来决定。如果超时时间到达之前,发送完未发送的数据(包括FIN包)并得到另一端的确认,close()会返回正确,socket描述符优雅性退出。否则,close()会直接返回错误值,未发送数据丢失,socket描述符被强制性退出。需要注意的时,如果socket描述符被设置为非堵塞型,则close()会直接返回值。

 

具体用法:

struct linger ling = {0, 0};
setsockopt(socketfd, SOL_SOCKET, SO_LINGER, (void*)&ling, sizeof(ling));



import socket import struct import time # 定义要发送的数据的长度(32KB - 2个字节的起始值) data_length = 4095-3 # 将0x03和0x36分别打包为两个字节 byte1 = struct.pack('>B', 0x2E) # 打包0x03为大端字节序 byte2 = struct.pack('>B', 0xF0) # 打包0x36为大端字节序 byte3 = struct.pack('>B', 0x11) # 打包0x03为大端字节序 # 合并这两个字节 start_bytes = byte1 + byte2 + byte3 # 创建一个全为0的字节串,长度为data_length zero_bytes = b'\x00' * data_length # 将起始字节和填充的字节合并 full_data = start_bytes + zero_bytes # 配置ECU的IP地址和端口 ecu_ip = '192.168.69.8' ecu_port = 13400 bind_ip = '192.168.69.71' def setup_socket(): """创建并配置TCP socket""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(30) # 设置接收超时 # 关键配置:防止发送FIN/RST sock.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', 0, 0)) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) return sock # 构造DoIP路由激活请求 def create_doip_routing_activation_request(source_address, activation_type, extra_data): """ 构造DoIP路由激活请求报文 :param source_address: 客户端逻辑地址 (2字节) :param activation_type: 激活类型 (1字节) :param extra_data: 额外数据 (4字节) :return: DoIP路由激活请求报文 """ # DoIP头部 protocol_version = 0x03 # DoIP协议版本 inverse_protocol_version = 0xFC # 协议版本的反码 payload_type = 0x0005 # 路由激活请求的类型 payload_length = 0x0000000B # Payload长度为11字节 # Payload部分 source_address_bytes = struct.pack('>H', source_address) # 客户端逻辑地址 activation_type_byte = struct.pack('>B', activation_type) # 激活类型 reserved_bytes = b'\x00\x00\x00\x00' # 保留字段 extra_data_bytes = extra_data # 额外数据 # 构造完整的DoIP消息 doip_message = ( struct.pack('>B', protocol_version) + struct.pack('>B', inverse_protocol_version) + struct.pack('>H', payload_type) + struct.pack('>I', payload_length) + source_address_bytes + activation_type_byte + reserved_bytes + extra_data_bytes ) return doip_message # 构造DoIP诊断请求 def create_doip_diagnostic_request(source_address, target_address, diagnostic_data): """ 构造DoIP诊断请求报文 :param source_address: 客户端逻辑地址 (2字节) :param target_address: 目标ECU逻辑地址 (2字节) :param diagnostic_data: 诊断数据 (UDS请求) :return: DoIP诊断请求报文 """ # DoIP头部 protocol_version = 0x03 # DoIP协议版本 inverse_protocol_version = 0xFC # 协议版本的反码 payload_type = 0x8001 # 诊断消息的类型 payload_length = 4 + len(diagnostic_data) # Payload长度 = 4字节地址 + 诊断数据长度 # Payload部分 source_address_bytes = struct.pack('>H', source_address) # 客户端逻辑地址 target_address_bytes = struct.pack('>H', target_address) # 目标ECU逻辑地址 diagnostic_data_bytes = diagnostic_data # 诊断数据 # 构造完整的DoIP消息 doip_message = ( struct.pack('>B', protocol_version) + struct.pack('>B', inverse_protocol_version) + struct.pack('>H', payload_type) + struct.pack('>I', payload_length) + source_address_bytes + target_address_bytes + diagnostic_data_bytes ) return doip_message # 发送DoIP路由激活请求 source_address = 0x0F01 # 客户端逻辑地址 activation_type = 0x00 # 默认激活类型 extra_data = b'\xFF\xFF\xFF\xFF' # 自定义的4字节额外数据 doip_activation_request = create_doip_routing_activation_request(source_address, activation_type, extra_data) def monitor_alive_check(sock): """监听Alive Check消息""" print("开始监听Alive Check消息...") try: while True: try: # 设置非阻塞接收以避免长时间卡住 sock.settimeout(1.0) data = sock.recv(1024) if data: print(f"接收到数据: {data.hex()}") # 检查是否是Alive Check (Payload Type 0x0008) if len(data) >= 8 and data[2:4] == b'\x00\x08': print("检测到Alive Check消息") last_alive = time.time() else: print("连接已关闭") break except socket.timeout: # 正常超时,继续监听 continue except Exception as e: print(f"接收错误: {str(e)}") break except KeyboardInterrupt: print("用户中断监听") finally: # 不关闭socket,直接退出 print("保持连接打开状态退出") def main(): sock = setup_socket() try: sock.bind((bind_ip, 0)) # 随机端口 sock.connect((ecu_ip, ecu_port)) print(f"已连接到 {ecu_ip}:{ecu_port}") # 发送路由激活请求 sock.send(doip_activation_request) print(f"Sent DoIP Routing Activation Request: {doip_activation_request.hex()}") # 接收路由激活响应 activation_response = sock.recv(1024) print(f"Received DoIP Routing Activation Response: {activation_response.hex()}") # 发送DoIP诊断请求 target_address = 0x32A # 目标ECU逻辑地址 diagnostic_data = b'\x22\xF0\x11' # 示例诊断请求 (UDS请求: 0x10 0x01) doip_diagnostic_request = create_doip_diagnostic_request(source_address, target_address, full_data) #doip_diagnostic_request = create_doip_diagnostic_request(source_address, target_address, diagnostic_data) # 发送诊断请求 sock.send(doip_diagnostic_request) print(f"Sent DoIP Diagnostic Request: {doip_diagnostic_request.hex()}") # 接收诊断响应 diagnostic_response = sock.recv(1024) print(f"Received DoIP Diagnostic Response: {diagnostic_response.hex()}") # 开始监听Alive Check monitor_alive_check(sock) except Exception as e: print(f"发生错误: {str(e)}") finally: # 不调用close(),保持连接打开 print("程序退出,socket保持打开状态") # 如果需要完全避免任何关闭行为,可以取消下面这行注释 # os._exit(0) if __name__ == "__main__": main() 解释代码
最新发布
07-29
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值