Часть 3: Приём GPS координат и сохранение их в БД MariaDB.

Во второй части был описан клиент, который отправляет данные на внешний сервер.

Теперь опишем сервис, который на внешнем сервере принимает данные.

Я настраивал сервис на Debian Bullseye. Чистая установка. Наборы пакетов: SSH-сервер и Стандартные системные утилиты.

Сервис будет запущен от отдельного пользователя gps. Для этого добавим нового пользователя:

useradd -m -s /usr/sbin/nologin gps

В домашней директории нового пользователя создадим поддиректории, в которых будут лежать наш скрипт, файл конфигурации и логи:

mkdir /home/gps/gpsTracksReceiver
mkdir /home/gps/gpsTracksReceiver/conf
mkdir /home/gps/gpsTracksReceiver/log

Для записи логов нужны права на запись для нового пользователя gps или он должен быть владельцем директории:

chown gps:gps /home/gps/gpsTracksReceiver/log

Сам скрипт server.py:

import base64
import socket
import os
import pymysql
import logging
import logging.handlers
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from _thread import *
from socket import error, SHUT_RDWR

from conf import config as cfg

log_level = logging.INFO # уровень логирования. пока реализован только INFO

# Загрузка данных в SQL базу
def insettData2SQL(clname, db, poid, plon, plat, palt, pspeed, podate, potime):
  mydb = pymysql.connect(
    host=cfg.mysql_server,
    user=cfg.mysql_user,
    passwd=cfg.mysql_pass,
    database=cfg.mysql_db
  )
  mycursor = mydb.cursor()

  query = "INSERT INTO " + cfg.mysql_table + " (client, dbid, pid, lon, lat, alt, speed, pdate, ptime) "
  query = query + "VALUES ('" + clname + "', '" + db + "', " + poid + ", " + plon + ", " + plat + ", " + palt + ", " + pspeed + ", " + podate + ", " + potime + ");"

  mycursor.execute(query)
  mydb.commit()
  mydb.close()

# Шифрование отправляемых данных
def encryptData(clientName, rawData):
  kdf = PBKDF2HMAC(
    algorithm=hashes.SHA256(),
    length=32,
    salt=bytes(cfg.salt, encoding='utf8'),
    iterations=390000,
  )
  key = base64.urlsafe_b64encode(kdf.derive(bytes(cfg.clients[clientName], encoding='utf8')))
  cipher = Fernet(key)
  return cipher.encrypt(bytes(rawData, encoding='utf8'))

# Расшифровка полученных данных
def decryptData(clientName, encdata):
  kdf = PBKDF2HMAC(
    algorithm=hashes.SHA256(),
    length=32,
    salt=bytes(cfg.salt, encoding='utf8'),
    iterations=390000,
  )
  key = base64.urlsafe_b64encode(kdf.derive(bytes(cfg.clients[clientName], encoding='utf8')))
  cipher = Fernet(key)
  # Расшифровываем данные. Если не расшифровывается, то возвращаем error.
  try:
    decrData = cipher.decrypt(bytes(encdata, encoding='utf8'))
    return decrData
  except Exception as e:
    return bytes('error', encoding='utf8')

# Подключение клиента к серверу
def clientConnected(client, claddr):
  while True:
  # Получаем данные вида: клиент;зашифрованные-данные
    try:
      encData = str(client.recv (4096), encoding = 'utf8')
    except Exception as e:
      encData = 'Непонятный набор символов'
    # Если нет ";" считаем, что пришли некорректные данные
    if encData.find(';')<0:
      if encData == 'bye':
        logging.info('Клиент '+str(claddr)+' отключился')
        client.shutdown(SHUT_RDWR)
        client.close()
        break

      if encData:
        logging.info('Клиент '+str(claddr)+' передал неверные данные:'+encData)
        break

    # Отделяем имя клиента от зашифрованных данных
    clientname, Encdata = encData.split(';',1)

    # Я решил, что имя клиента не должно превышать 32 символа.
    if len(clientname)>32:
      logging.info('Клиент '+str(claddr)+' передал слишком длинное имя')
      break

    # Расшифруем данные
    data = str(decryptData(clientname, Encdata), encoding = 'utf8')

    # Если вернулось error, то считаем, что клиент передал неверные данные.
    if data == 'error':
      logging.info('Клиент '+str(claddr)+' передал неверные данные')
      break

    #track, id, longitude, latitude, altitude, speed, data, vremya, upload
    if data.count(',')!=8:
      logging.info('Клиент '+str(claddr)+' передал неверное количество параметров')
      break

    dbid, pid, lon, lat, alt, speed, pdate, ptime, upload = data.split(',')
    dbid = str(dbid).replace('(','')
    pid = str(pid).strip()
    lon = str(lon).strip()
    lat = str(lat).strip()
    alt = str(alt).strip()
    speed = str(speed).strip()
    pdate = str(pdate).strip()
    ptime = str(ptime).strip()
    insettData2SQL(str(clientname), dbid, pid, lon, lat, alt, speed, pdate, ptime)

    client.send(encryptData(clientname, "OK"))

  logging.info('Клиент '+str(claddr)+' отключён')
  client.shutdown(SHUT_RDWR)
  client.close()

def log_setup():
  log_handler = logging.handlers.WatchedFileHandler(cfg.log_file)
  formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
  log_handler.setFormatter(formatter)
  logger = logging.getLogger()
  logger.addHandler(log_handler)
  logger.setLevel(log_level)

#START
log_setup()
serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serv.bind((cfg.server_address, cfg.server_port))
serv.listen(5)
logging.info('Сервер запущен. Ожидает подключений на: '+cfg.server_address+':'+str(cfg.server_port))

try:
  while True: 
    conn, addr = serv.accept()
    logging.info('Подключен клиент с адресом: '+str(addr))
    start_new_thread(clientConnected, (conn, addr))

except (KeyboardInterrupt, SystemExit):
  logging.info("Остановка сервера")
  serv.close()

finally:
  logging.info("Сервер остановлен")

Конфиг /home/gps/gpsTracksReceiver/conf/config.py:

clients = {"client": "58_btTe_ZJMm41gFP_XL6HgX7kT7vo9ywQzY_xRg6ZybrAfd", "client1": "I741xNK89PJ7SEtauZPnCsOsfeuWxS4akW-XIPbL68U9rg5i"} # Клиенты и их пароли
salt = "Ld0GH14L1pv5itZb" # соль — это просто случайные данные, которые вы используете в качестве дополнения в вашем хеше, с целью усложнения расшифровки пароля

mysql_server = "127.0.0.1" # адрес mysql сервера
mysql_user = "dbuser" # пользователь с правами на запись в базу
mysql_pass = "dbpass" # пароль этого пользователя
mysql_db = "gpsdb" # имя базы
mysql_table = "track" # таблица в базе

server_address = "127.0.0.1" # адрес, на котором сервер принимает подключения от клиентов
server_port = 3333 # порт, на котором сервер принимает подключения от клиентов

log_file = "/home/gps/gpsTracksReceiver/log" # лог файл

clients — это myname и mykey из второй части. salt тоже из второй части.

Параметры mysql_ — это настройки подключения к Mysql (MariaDB) серверу, где будут храниться получаемые данные.

server_address и server_port — это, соответственно, адрес и порт на котором вышеописанный скрипт будет ожидать данные.

log_file — в какой файл будет писаться лог. Скрипт сам создаст файл, но нужно создать директорию (/var/log/gps/) с правами на запись для пользователя, от которого будет запускаться вышеописанный скрипт (в нашем случае пользователь gps).

Для запуска скрипта нужно доустановить пакет:

apt install python3-pymysql

он подтянет за собой python3-cryptography

Настройку SQL сервера MariaDB описывать не буду. В интернете полно мануалов.

Таблица в базе создаётся так:

CREATE TABLE track (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
client VARCHAR(32) NOT NULL,
dbid VARCHAR(14) NOT NULL,
pid INT NOT NULL,
lon VARCHAR(32) NOT NULL,
lat VARCHAR(32) NOT NULL,
alt FLOAT NOT NULL,
speed FLOAT NOT NULL,
pdate DATE NOT NULL,
ptime TIME NOT NULL,
upload DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP()
);

Создаём сервис для автоматического запуска server.py от пользователя gps.

Файл /etc/systemd/system/gpsTracksReceiver.service:

[Unit]
Description=GPS Tracks Receiver
After=multi-user.target
[Service]
User=gps
Type=simple
Restart=always
ExecStart=/usr/bin/python3 /home/gps/gpsTracksReceiver/server.py
[Install]
WantedBy=multi-user.target

Установка сервиса:

systemctl daemon-reload
systemctl enable gpsTracksReceiver.service

Запуск сервиса:

systemctl start gpsTracksReceiver.service

Остановка сервиса:

systemctl stop gpsTracksReceiver.service
Запись опубликована в рубрике gps, linux. Добавьте в закладки постоянную ссылку.

Добавить комментарий