Бэкенд, распределенные системы, микросервисы — все это крутится вокруг простой идеи: кто-то запрашивает данные, кто-то их отдает. Python позволяет создать клиент-серверное приложение хоть сегодня, причем разными способами. В этой статье мы не просто напишем эхо-сервер на сокетах, но и замахнемся на gRPC — технологию, которую используют Google и современные стартапы. Поехали!
Введение: Что такое клиент-сервер простыми словами
Представьте ресторан. Клиент — это вы (посетитель), а сервер — это кухня и официант. Вы делаете заказ (запрос), официант передает его на кухню, а вам приносят готовое блюдо (ответ). В мире Python таких "официантов" несколько: от простых сокетов до мощных фреймворков .
Сегодня разберем два подхода:
- Классика (сокеты) — чтобы понять, как все устроено под капотом.
- 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 круче сокетов?
- Контракт. Вы описываете API в файле .proto, и по нему генерируется код и для сервера, и для клиента. Ошибки отпадают на этапе компиляции.
- Производительность. Бинарный формат легче и быстрее парсится, чем JSON.
- Стриминг. 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.
Что выбрать для своего проекта?
- Сокеты (socket) — идеально для обучения, сетевых утилит или когда нужно передать что-то специфичное поверх TCP/UDP. Но для веб-приложений лучше брать что-то уровнем выше .
- gRPC — выбор #1 для внутренних микросервисов, когда важна скорость и строгий контракт. Отлично подходит для распределенных систем .
- REST (Flask, FastAPI) — если вам нужно общаться с браузером или внешними разработчиками. (Но это тема отдельной статьи).
Совет
Не изобретайте велосипед. Если у вас стартап и вы делаете монолит на Django, вам достаточно встроенных возможностей фреймворка. Если же у вас 10 микросервисов на Python и Go — присмотритесь к gRPC