Найти в Дзене

Клиент-сервер на Python: от простого чата до промышленного gRPC за 20 минут

Бэкенд, распределенные системы, микросервисы — все это крутится вокруг простой идеи: кто-то запрашивает данные, кто-то их отдает. Python позволяет создать клиент-серверное приложение хоть сегодня, причем разными способами. В этой статье мы не просто напишем эхо-сервер на сокетах, но и замахнемся на gRPC — технологию, которую используют Google и современные стартапы. Поехали!
Представьте ресторан. Клиент — это вы (посетитель), а сервер — это кухня и официант. Вы делаете заказ (запрос), официант передает его на кухню, а вам приносят готовое блюдо (ответ). В мире Python таких "официантов" несколько: от простых сокетов до мощных фреймворков . Сегодня разберем два подхода: Сокеты — это самый низкоуровневый способ заставить компьютеры общаться. Их плюс — полный контроль над процессом. Минус — приходится вручную обрабатывать многие вещи. Сервер будет слушать порт, принимать сообщения и отвечать на них. В примере используем многопоточность, чтобы обслуживать несколько клиентов сразу . imp
Оглавление

Бэкенд, распределенные системы, микросервисы — все это крутится вокруг простой идеи: кто-то запрашивает данные, кто-то их отдает. Python позволяет создать клиент-серверное приложение хоть сегодня, причем разными способами. В этой статье мы не просто напишем эхо-сервер на сокетах, но и замахнемся на gRPC — технологию, которую используют Google и современные стартапы. Поехали!

Введение: Что такое клиент-сервер простыми словами

Представьте ресторан. Клиент — это вы (посетитель), а сервер — это кухня и официант. Вы делаете заказ (запрос), официант передает его на кухню, а вам приносят готовое блюдо (ответ). В мире Python таких "официантов" несколько: от простых сокетов до мощных фреймворков .

Сегодня разберем два подхода:

  1. Классика (сокеты) — чтобы понять, как все устроено под капотом.
  2. gRPC — современный стандарт для микросервисов.

Часть 1. База: Пишем простое приложение на сокетах (TCP)

Сокеты — это самый низкоуровневый способ заставить компьютеры общаться. Их плюс — полный контроль над процессом. Минус — приходится вручную обрабатывать многие вещи.

Сервер (server.py)

Сервер будет слушать порт, принимать сообщения и отвечать на них. В примере используем многопоточность, чтобы обслуживать несколько клиентов сразу .

import socket
import threading

def handle_client(conn, addr):
print(f"Подключен клиент: {addr}")
while True:
data = conn.recv(1024).decode()
if not data or data.lower() == 'exit':
break
print(f"Получено от {addr}: {data}")
# Отправляем обратно (эхо)
conn.send(f"Сервер принял: {data}".encode())
conn.close()
print(f"Клиент {addr} отключился")

def start_server():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 9999))
server.listen(5)
print("Сервер запущен на порту 9999...")

while True:
conn, addr = server.accept()
thread = threading.Thread(target=handle_client, args=(conn, addr))
thread.start()

if __name__ == "__main__":
start_server()

Комментарий: Здесь используется threading — на каждый новый запрос создается отдельный поток. Для простых проектов этого достаточно.

Клиент (client.py)

Клиент подключается к серверу и отправляет сообщения, которые мы вводим с клавиатуры .

import socket

def start_client():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 9999))
print("Подключен к серверу. Введите 'exit' для выхода.")

while True:
message = input("Вы: ")
client.send(message.encode())

if message.lower() == 'exit':
break

response = client.recv(1024).decode()
print(f"Сервер: {response}")

client.close()

if __name__ == "__main__":
start_client()

Запускаем сервер, потом клиент — готово! Это простейший чат.

Часть 2. Взрослый подход: gRPC и Protocol Buffers

Если вы пишете серьезный проект, где важны скорость, типы данных и документация API, лучше использовать gRPC. Это фреймворк от Google, который работает поверх HTTP/2 и использует бинарный формат данных (Protobuf) вместо текстового JSON .

Почему gRPC круче сокетов?

  1. Контракт. Вы описываете API в файле .proto, и по нему генерируется код и для сервера, и для клиента. Ошибки отпадают на этапе компиляции.
  2. Производительность. Бинарный формат легче и быстрее парсится, чем JSON.
  3. Стриминг. gRPC умеет держать соединение открытым и передавать данные потоком (например, видео или котировки акций).

Шаг 1. Устанавливаем библиотеки

pip install grpcio grpcio-tools

Шаг 2. Описываем контракт (service.proto)

Создадим простой сервис для проверки доступности товара (как в примерах лабораторных работ ).

syntax = "proto3";

package shop;

// Запрос: проверяем товар по ID
message ProductRequest {
int32 product_id = 1;
}

// Ответ: есть ли в наличии и цена
message ProductReply {
string product_name = 1;
bool available = 2;
float price = 3;
}

// Описание сервиса
service ProductService {
rpc CheckAvailability (ProductRequest) returns (ProductReply);
}

Шаг 3. Генерируем код Python

Выполняем команду в терминале:

python -m grpc_tools.protoc -I. --python_out=. --grpc_python_out=. service.proto

После выполнения у вас появятся файлы service_pb2.py (классы данных) и service_pb2_grpc.py (классы сервера/клиента) .

Шаг 4. Пишем сервер (server.py)

Сервер будет реализовывать нашу логику проверки.

import grpc
from concurrent import futures
import service_pb2
import service_pb2_grpc

class ProductServicer(service_pb2_grpc.ProductServiceServicer):
def CheckAvailability(self, request, context):
# Простая логика: товары с четным ID есть, с нечетным - нет
product_id = request.product_id
available = (product_id % 2 == 0)
name = f"Товар-{product_id}"
price = 100.0 if available else 0.0

return service_pb2.ProductReply(
product_name=name,
available=available,
price=price
)

def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
service_pb2_grpc.add_ProductServiceServicer_to_server(ProductServicer(), server)
server.add_insecure_port('[::]:50051')
print("gRPC сервер запущен на порту 50051")
server.start()
server.wait_for_termination()

if __name__ == '__main__':
serve()

Шаг 5. Пишем клиент (client.py)

Клиент подключается к серверу и вызывает метод так, будто это локальная функция.

import grpc
import service_pb2
import service_pb2_grpc

def run():
channel = grpc.insecure_channel('localhost:50051')
stub = service_pb2_grpc.ProductServiceStub(channel)

# Проверяем товар с ID=4
response = stub.CheckAvailability(service_pb2.ProductRequest(product_id=4))
print(f"Товар: {response.product_name}")
print(f"В наличии: {'Да' if response.available else 'Нет'}")
print(f"Цена: {response.price}")

# Проверяем товар с ID=3
response = stub.CheckAvailability(service_pb2.ProductRequest(product_id=3))
print(f"\nТовар: {response.product_name}")
print(f"В наличии: {'Да' if response.available else 'Нет'}")

if __name__ == '__main__':
run()

Запускаем сервер, затем клиент. Клиент получит строго типизированный ответ, а обмен данными произойдет в бинарном протоколе по HTTP/2.

Что выбрать для своего проекта?

  1. Сокеты (socket) — идеально для обучения, сетевых утилит или когда нужно передать что-то специфичное поверх TCP/UDP. Но для веб-приложений лучше брать что-то уровнем выше .
  2. gRPC — выбор #1 для внутренних микросервисов, когда важна скорость и строгий контракт. Отлично подходит для распределенных систем .
  3. REST (Flask, FastAPI) — если вам нужно общаться с браузером или внешними разработчиками. (Но это тема отдельной статьи).

Совет

Не изобретайте велосипед. Если у вас стартап и вы делаете монолит на Django, вам достаточно встроенных возможностей фреймворка. Если же у вас 10 микросервисов на Python и Go — присмотритесь к gRPC