249 lines
15 KiB
Python
249 lines
15 KiB
Python
# -*- coding: utf-8 -*-
|
|
import struct
|
|
import socket
|
|
import time
|
|
import random
|
|
import math
|
|
import threading
|
|
|
|
# ------------------------ 全局队列和锁 ------------------------
|
|
vdes_message_queue = []
|
|
message_queue_lock = threading.Lock()
|
|
|
|
# ------------------------ 船只初始化 ------------------------
|
|
SHIP_MMSIS = [random.randint(336295939, 336295969) for _ in range(10)]
|
|
SHIP_STATUS = {}
|
|
SHIP_LIST = [
|
|
(336295939 + i, round(random.uniform(30.0, 34.0), 6),
|
|
round(random.uniform(123.5, 126.0), 6), random.randint(0, 359))
|
|
for i in range(30)
|
|
]
|
|
for ship in SHIP_LIST:
|
|
mmsi, init_lat, init_lon, heading = ship
|
|
SHIP_STATUS[mmsi] = {
|
|
'current_lat': init_lat,
|
|
'current_lon': init_lon,
|
|
'heading': heading,
|
|
'speed': 280.24 # 加速100倍用于演示
|
|
}
|
|
|
|
# ------------------------ 当前坐标 ------------------------
|
|
YANGTZE_ESTUARY_LAT = 31.23
|
|
YANGTZE_ESTUARY_LON = 123.87
|
|
current_lat = YANGTZE_ESTUARY_LAT
|
|
current_lon = YANGTZE_ESTUARY_LON
|
|
own_heading = 90 # 固定航向
|
|
|
|
# ------------------------ 工具函数 ------------------------
|
|
def generate_random_position():
|
|
global current_lat, current_lon, own_heading
|
|
speed_knots = 300.24
|
|
speed_kmh = speed_knots * 1.852
|
|
speed_kmps = speed_kmh / 3600
|
|
distance_km = speed_kmps * 1
|
|
heading_rad = math.radians(own_heading)
|
|
delta_lat = distance_km / 111.0 * math.cos(heading_rad)
|
|
delta_lon = distance_km / (111.0 * math.cos(math.radians(current_lat))) * math.sin(heading_rad)
|
|
current_lat += delta_lat
|
|
current_lon += delta_lon
|
|
return int(current_lon * 10000000), int(current_lat * 10000000)
|
|
|
|
def generate_ship_position(mmsi):
|
|
ship = SHIP_STATUS[mmsi]
|
|
speed_kmh = ship['speed'] * 1.852
|
|
distance_km = speed_kmh / 3600
|
|
heading_rad = math.radians(ship['heading'])
|
|
delta_lat = distance_km / 111.0 * math.cos(heading_rad)
|
|
delta_lon = distance_km / (111.0 * math.cos(math.radians(ship['current_lat']))) * math.sin(heading_rad)
|
|
ship['current_lat'] += delta_lat
|
|
ship['current_lon'] += delta_lon
|
|
return ship['current_lon'], ship['current_lat']
|
|
|
|
def create_tcp_packet(packet_type, content):
|
|
header = b'####'
|
|
type_field = struct.pack('>I', packet_type)
|
|
length_field = struct.pack('>I', len(content))
|
|
return header + type_field + length_field + content
|
|
|
|
def create_gps_packet():
|
|
lon, lat = generate_random_position()
|
|
utc_time = int(time.time())
|
|
content = struct.pack('>QiiiHHHBBIII',
|
|
utc_time, lon, lat, 50, 10, 8, 7, 1, 3, 2550, 1024, own_heading)
|
|
return content
|
|
|
|
def create_ais_vdes_info_packet(demod_content, Demodulator_id):
|
|
snr = 25.75
|
|
snr_int = int(snr)
|
|
snr_frac = int((snr - snr_int) * 100)
|
|
snr_packed = (snr_int & 0xFF) << 8 | (snr_frac & 0xFF)
|
|
content = struct.pack('>BBBHHH', 12, 1, Demodulator_id, 1234, 5678, snr_packed)
|
|
return content + demod_content
|
|
|
|
def encode_ais_demod_fields(SHIP_MMSI, lat, lon, heading):
|
|
bits = []
|
|
bits.append(format(1, '06b'))
|
|
bits.append(format(0, '02b'))
|
|
bits.append(format(SHIP_MMSI, '030b'))
|
|
bits.append(format(8, '04b'))
|
|
bits.append(format(128 & 0xFF, '08b'))
|
|
bits.append(format(102, '010b'))
|
|
bits.append(str(1))
|
|
bits.append(format(int(lon * 60 * 10000) & 0x0FFFFFFF, '028b'))
|
|
bits.append(format(int(lat * 60 * 10000) & 0x07FFFFFF, '027b'))
|
|
bits.append(format(min(heading, 3600), '012b'))
|
|
bits.append(format(min(heading, 511), '09b'))
|
|
bits.append(format(30, '06b'))
|
|
bits.append(format(0, '02b'))
|
|
bits.append(format(0, '03b'))
|
|
bits.append(str(0))
|
|
bits.append(format(0, '019b'))
|
|
bit_string = ''.join(bits)
|
|
binary_int = int(bit_string, 2)
|
|
byte_length = (len(bit_string) + 7) // 8
|
|
return binary_int.to_bytes(byte_length, 'big')
|
|
|
|
def encode_vdes_demod_fields(Type, Fragment_num, MMSI, Payload):
|
|
source_id = 12345678
|
|
satellite_id = 1
|
|
session_id = 100
|
|
dest_id = MMSI
|
|
fragment_num = Fragment_num
|
|
binary_payload = Payload
|
|
payload_size = 4 + 1 + 1 + 4 + 2 + len(binary_payload)
|
|
content = struct.pack('>B H I B B I H',
|
|
Type, payload_size, source_id, satellite_id, session_id, dest_id, fragment_num)
|
|
return content + binary_payload
|
|
|
|
# ------------------------ 线程函数 ------------------------
|
|
def send_messages(conn):
|
|
global frame_index
|
|
try:
|
|
while True:
|
|
with message_queue_lock:
|
|
if vdes_message_queue:
|
|
packet = vdes_message_queue.pop(0)
|
|
else:
|
|
packet = None
|
|
if packet:
|
|
try:
|
|
conn.sendall(packet)
|
|
except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
|
|
print("发送失败,客户端断开")
|
|
break
|
|
else:
|
|
if ais_frames1:
|
|
# print(ais_frames, type(ais_frames))
|
|
ais_frames = [b'####\x00\x00\x00\x0c\x00\x00\x04\x1b\x0c\x02\x00\x03\x83e\x8d+\xc9\xfeM\x1e\x04\t\x12\xa5)\xec\x02\x02\x12\xa5)\xec\x00\x01\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00`\x00`\x00\x00\xff\xdb\x00C\x00\x10\x0b\x0c\x0e\x0c\n\x10\x0e\r\x0e\x12\x11\x10\x13\x18(\x1a\x18\x16\x16\x181#%\x1d(:3=<9387@H\\N@DWE78PmQW_bghg>Mqypdx\\egc\xff\xdb\x00C\x01\x11\x12\x12\x18\x15\x18/\x1a\x1a/cB8Bcccccccccccccccccccccccccccccccccccccccccccccccccc\xff\xc0\x00\x11\x08\x00\xa9\x00d\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xc4\x00\x1f\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4\x00\xb5\x10\x00\x02\x01\x03\x03\x02\x04\x03\x05\x05\x04\x04\x00\x00\x01}\x01\x02\x03\x00\x04\x11\x05\x12!1A\x06\x13Qa\x07"q\x142\x81\x91\xa1\x08#B\xb1\xc1\x15R\xd1\xf0$3br\x82\t\n\x16\x17\x18\x19\x1a%&\'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xc4\x00\x1f\x01\x00\x03\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\xff\xc4\x00\xb5\x11\x00\x02\x01\x02\x04\x04\x03\x04\x07\x05\x04\x04\x00\x01\x02w\x00\x01\x02\x03\x11\x04\x05!1\x06\x12AQ\x07aq\x13"2\x81\x08\x14B\x91\xa1\xb1\xc1\t#3R\xf0\x15br\xd1\n\x16$4\xe1%\xf1\x17\x18\x19\x1a&\'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x92\x93\x94\x95\x96\x97\x98\x99\x9a\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xd9\xa7\nJZ\xb1\x0bE\x14\xb4\xc0J)h\xa0AKIK@\x05\x14Q@\x05\x14Q@\x05\x14Q@\x11\xd0(\xa5\xa41E-%-0\n(\xa5\xa0\x04\xa5\xa2\x8a\x04\x14QE\x00\x14QE\x00\x14QE\x00GKM\xa7R\x18\xa2\x94RQL\x05\xa2\x8a(\x01h\xa4\xa5\xa0AE%\x14\x00\xb4RQ@\x0bE\x14P\x04t\xb4\xdaZ\x91\x8bKH)i\x80Ux\xae\xd5\xe7x\x88\xc6\x1b\x00\xfa\xd5\x8a\xc6\xb8c\x1d\xdc\x8a\xa3\xabf\xa2rq4\x84T\xaemSd\x91cB\xed\xd0VP\xbf\xb8\x8f*v\xb7\xa6G"\xa2\x96\xeak\x8eI\x18\x1d\x06*\x1dx\xf4\x1f\xb2w\xd4\xd2\xb4\xb9i\xa4up\x01\x1c\x8czU\x9c\xd6N\x94\xcc\xd3\xca[\xa8\x185\xa9\x9a\xb8J\xebRjF\xd2\xb2\x1d\x9a3L\xcd8\x1a\xbb\x90:\x8aJ(\x01\x94\xb4\x94\xb4\x86-\x14\x94P!\xd9\xac{\x82\x8bw,\x8c\xc7\x81\xc8\xf6\x15\xaf\x9a\xc6\xbf>]\xd3\xe1yl|\xd9\xfd+*\xdb\x1a\xd2\xdc\x82\x0b\x88\xae\x8b\x98I\x05\x08\xceG\xd6\x98\xf7\x91[\xdd\xac\x066b\xd8\xe4v\xcf\xf9\xcd\n\x8bi!t@\xaaG\xcf\x81Dhd\x91\xa5`7\x1eT\x902\x07\xf3\x15\xcd\xa7c{\x17\xad\n\xad\xdbaHb0}\xebG5\x9bc\x92\xd9\xc0\xc2\xf45wutR\xf8L*nK\x9ar\x9a\x88\x1ax\xadL\xc93E%\x14\xc0h\xa5\xa4\xa2\x80\x16\x8c\xd2g\x8ej\x8d\xcd\xd38)\x1bm\x1e\xbe\xb52\x92\x8a\x1cb\xd95\xcd\xf4Pdgs\x8e\xc2\x81\xf0\xf0\x0e\xff\xff\xff\xf9\xff\xff\xff\xf8\xff\x00\x00\xf7', b'####\x00\x00\x00\x0c\x00\x00\x04\x1b\x0c\x02\x00\x03\x92e\x8d,+\xfeF \x03\xf0\x12\xa5)\xec\x02\x02\x12\xa5)\xec\x00\x01\xb3\xa6}\xcb\xb9\xb9\xcf&\xa9\xe0\xb4\x99\xc0\xfb\xc753n\xc6F8\xed\\\xb2\xa8\xe4tF\n$FR>B\xbb\xd4q\xd7\x06\xa6\x8a2\xfc\x05\xda\x9e\x99\xcdBY;\xf0\xd5v\x0f\x99A\xcdE\xcbd\xf0a\x17j\xd5\x80sU#l\x1d\xbe\x9d\xea\xca\x9a\xe8\xa6\xf49\xe6\xb5%Z\x91j5\xa9\x16\xb6D\x0f\x14P\x05\x14\x08e\x19\xa4\xcd&h\x02\xbd\xf4\xa5P \xea\xddj\x9b\x11\xe5\x9fjt\x8ed\x97$\xe4\x1e\x94\xd6\x03\xcb?J\xe5\x9c\xb9\x99\xd1\x15dT]\xb9\xc0\x04\xe4\xe4\xd5\x81\x9f/\xe6\\qU\x11\x99f\xe0g\xd2\xac\xfe\xf3fw|\xc7\xf8Md\x8d\n\xce\xe9\xb8\x12s\x8es\x8a\xb5\x0c\xe0\xa8\xe3\x1e\x95RX\x82\x82pW\xfa\x9aX\x0e\xe6\x00t\xc50f\x92\xb6zU\x84\xaa\xd1\x8c\x009\xfcj\xcaWL69\xe4\xf5&Z\x95j5\x15*\xd6\xa8\x868QIE1\x10\xd3\\\xfc\x8d\xf4\xa34\xc9\x18\x08\xd8\x9e\x80R{\rng\xc5\x8d\x9f)\xc8\x1d\xe9\xb2\xb0Pp\x7fJX\x8a\xa2c\x80)\x19\x81\xfcMq3\xa8\xa8d\xc4\xa82>q\xebNx\xdd\xa5\x01n\x06=\x07j\xabx\xa1b\xf3\x06p\x8c\x0e=\xbb\xd4\xd0\x82\xc1\x1a\x14Q\xe8A\xcd&4>\xe71\x82\x1eL\x8a\x86\xd2#4\xf8\xc9TQ\xd4w5\x16\xad$\xc2X\xd5\x93%\x87\x00U\x8d2\x1b\xbd\xa1\x9fhS\xfc4\xecM\xcdH\x94/\x03\xa0\xabQ\x8a\xad\x1fZ\xb5\x1du\xc5hs\xb7\xa9:\x8ax\xa6\xad<U\x92-\x14Q@\x8a\x99\xa8\xe6\xc9\x85\xc0\xeb\xb4\xe2\x9d\x9aJL\xa4c\xf9\x80\xaf=}\xe9\x15\x81#5Z\xf8\x98\xaedQ\x8e\x0f\x1cUu\xbbr\xa5\x98`/\x06\xb8\xecut/\xce\x12H\xd9\t\x19+\x8a\x87N\x00\xc5\xf3!fS\x82P\xf5\xa8c\x9f{|\x91;g\xbdZ\xd2\xf8\x96d\xdaS\x07v\r$\x81\xe8&\xa6\x18}\x9e|}\xc6!\xbd\xb3ZP\x13\xe5\x83\xedT\xf5\x06V\xb5p9*A U\xabi\x15\xe0\\\x1e\xabO\xb1\x0c\x9e:\xb5\x1dU\x8b\x8e*\xca\x1a\xebG9ajAQ\xad<qT!\xd4Rf\x8av\x02\x86h\xcd34f\xa4\xb2\x8c\xfaz]\x8f1\xdc\xab0\xceG\x15]|>\xa7\x94\x9b\x07=\xc6k^\x10\x08\x19\x1d\x00\xa9p\x01\\z\xd6J)\xeaW32\xe2\xd2g\x8d6\xf9\xb1?=y\x1f\xd0\xd2G\xa7^\xa5\xebL\xdeIB\xbbp\xacs\xfa\x81Z\x1flA\xf7\xa3\x91\x7f\xdeB(\xfb}\xbf\xfc\xf5Q\xf54rD9\xe4WKk\x90Nb\xceO\xf7\x97\x8f\xd6\xab\xdb\xe9\xf7\xd0\xb3\xb1\x11\x91\x93\xb4\x03\xd0~U\xab\x1c\xf1\xcb\xfe\xad\xc3})\xc6D\x07\x05\x80>\x99\xa5\xec\xe2\x1c\xec\xa7\x1cr\xa2\x01$NH\xee\xa0\xd3\x84\xce\xa7\x98\x9c~\x9f\xccU\xbd\xeaz0\xfc\xe9\xc1\x8f\xf7\x8dh\x92J\xc4=H#\x9d\xd8\xf1\x13\x1f\xf3\xf4\xab\x01\xc8\x19| \xf74ooSHH=B\x9f\xa8\xaa\xb8\xb9D\xfbTC\xfeZ)\xfaQF\xd8\xfb\xc7\x19\xff\x00\x80\x8a(\xb8\xb9J[\xb095\x0b^\xc4\xbf\xc5\x9eqIs\xfe\xa4\xd61\xff\x00R\x9fS\xfc\xeb\x1a\x93q\xd8\xde\x10R\xdc\xe9!\x91\x19\x03)\x04\x1aqnW\xeb\xfd*\x8e\x9f\xff\x00\x1e\x89V\xc7U\xfa\xd5\xc5\xdd\\\xcd\xab;\x10\x86\x97\'m\xd4m\xecS\xff\x00\xafN\x0fs\xd8\xc2\x7f\xe0G\xfc+\x16o\xf8\xfao\xf7\xcf\xf3\xad(\xbe\xe0\xa1\x81v\x03&\xf2^4^>\xf2\x9c\xd4s\x85\xf3X\xb5\xa1l\xff\x00\xcbE\x03\'\xf2\xe6\x9dm\xf7\x8f\xd2\xa7n\xa2\x98\x14\x08\x83\xf8\xa1\x9c{\xfc\xf4\x06\xb6\x1f\xf2\xf1"\x0fv\x1f\xd6\xaf\x8aQ\xd4P\x03$?\xb8\xe2]\x9c\x0c=C\xfb\xdf\xe1\xb9C\xf5_\xfe\xbd.\xa3\xff\x00\x1e\x13}?\xadb\xd9\x7f\xac\xfch\x04mn\xb8\xfe\xfcg\xf1\xff\x00\xebQP\xafJ)\x0e\xc7\xff\xd9\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1a\xe7F\t\xff\xff\xff\xf9\xff\xff\xff\xf8\xff\x00\x00\xf7']
|
|
|
|
frame = ais_frames[frame_index]
|
|
try:
|
|
conn.sendall(frame)
|
|
print(f"发送报文帧[{frame_index}]:", frame.hex())
|
|
except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
|
|
print("发送失败,客户端断开")
|
|
break
|
|
|
|
# 轮流取下一帧
|
|
frame_index = (frame_index + 1) % len(ais_frames)
|
|
time.sleep(5)
|
|
|
|
# pass
|
|
|
|
|
|
|
|
# 生成并发送AIS报文
|
|
# selected_ship = random.choice(SHIP_LIST)
|
|
# mmsi = selected_ship[0]
|
|
# lon, lat = generate_ship_position(mmsi)
|
|
# heading = SHIP_STATUS[mmsi]['heading']
|
|
# ais_demod_content = encode_ais_demod_fields(mmsi, lat, lon, heading)
|
|
# ais_data = create_ais_vdes_info_packet(ais_demod_content, 4)
|
|
# ais_packet = create_tcp_packet(12, ais_data)
|
|
# #生成并发送GPS报文
|
|
# gps_content = create_gps_packet()
|
|
# gps_packet = create_tcp_packet(7, gps_content)
|
|
# try:
|
|
# conn.sendall(ais_packet)
|
|
# conn.sendall(gps_packet)
|
|
# except (ConnectionResetError, ConnectionAbortedError, BrokenPipeError):
|
|
# print("发送失败,客户端断开")
|
|
# break
|
|
time.sleep(0.5)
|
|
finally:
|
|
print("发送线程退出")
|
|
|
|
def handle_client(conn, addr):
|
|
print(f"客户端连接: {addr}")
|
|
fragment_num = 0
|
|
send_thread = threading.Thread(target=send_messages, args=(conn,))
|
|
send_thread.daemon = True
|
|
send_thread.start()
|
|
try:
|
|
while True:
|
|
try:
|
|
data = conn.recv(65536)
|
|
if not data:
|
|
print("客户端关闭连接")
|
|
break
|
|
# 解析报文并加入发送队列
|
|
frame_type=1
|
|
message = '1'
|
|
mmsi = 11111111
|
|
Type = 30 if frame_type == 0 else 31 if frame_type == 1 else 32
|
|
vdes_demod_content = encode_vdes_demod_fields(Type, fragment_num, mmsi, message)
|
|
vdes_data = create_ais_vdes_info_packet(vdes_demod_content, 0)
|
|
vdes_packet = create_tcp_packet(12, vdes_data)
|
|
fragment_num += 1
|
|
with message_queue_lock:
|
|
vdes_message_queue.append(vdes_packet)
|
|
except (ConnectionResetError, ConnectionAbortedError):
|
|
print("客户端断开连接")
|
|
break
|
|
except Exception as e:
|
|
print(f"解析报文错误: {e}")
|
|
finally:
|
|
conn.close()
|
|
print("客户端连接关闭")
|
|
|
|
# ------------------------ 服务端启动 ------------------------
|
|
def send_data_continuously():
|
|
host = '172.16.0.70'
|
|
port = 10
|
|
with open('./config.txt', 'r') as f:
|
|
lines = f.readlines()
|
|
# 第一行是IP地址
|
|
host = lines[0].strip()
|
|
# 第二行是端口号
|
|
port = int(lines[1].strip())
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
s.bind((host, port))
|
|
s.listen(5)
|
|
print(f"服务端启动 {host}:{port}")
|
|
try:
|
|
while True:
|
|
conn, addr = s.accept()
|
|
client_thread = threading.Thread(target=handle_client, args=(conn, addr))
|
|
client_thread.daemon = True
|
|
client_thread.start()
|
|
except KeyboardInterrupt:
|
|
print("服务端停止")
|
|
def load_frames(filepath):
|
|
with open(filepath, "rb") as f:
|
|
data = f.read()
|
|
|
|
delimiter = b"####" # 帧起始标记
|
|
frames = data.split(delimiter)
|
|
|
|
# 加回起始符,过滤掉空的
|
|
frames = [delimiter + frame for frame in frames if frame]
|
|
return frames
|
|
|
|
if __name__ == "__main__":
|
|
# 全局变量:预加载所有帧
|
|
# ais_frames1 = load_frames("./tcp_data/TCP_VDES报文消息.bin")
|
|
ais_frames1 = True
|
|
frame_index = 0 # 当前取到第几帧
|
|
send_data_continuously()
|