Как написать серверное приложение на python

Оглавление

  • Что определяет хорошего разработчика ПО?
  • Что же такое веб-сервер?
  • Как общаться с клиентами по сети
  • Простейший TCP сервер
  • Простейший TCP клиент
  • Заключение
  • Cсылки по теме

Лирическое отступление: что определяет хорошего разработчика?

Доктор Манхэттен что-то собирает силой мысли

Разработка ПО — это инженерная дисциплина. Если вы хотите стать действительно профессиональным разработчиком, то необходимо в себе развивать качества инженера, а именно: системный подход к решению задач и аналитический склад ума. Для вас должно перестать существовать слово магия. Вы должны точно знать как и почему работают системы, с которыми вы взаимодействуете (между прочим, полезное качество, которое находит применение и за пределами IT).

К сожалениею (или к счастью, ибо благоприятно складывается на уровне доходов тех, кто осознал), существует огромное множество людей, которые пишут код без должного понимания важности этих принципов. Да, такие горе-программисты могут создавать работающие до поры до времени системы, собирая их из найденных в Интернете кусочков кода, даже не удосужившись прочитать, как они реализованы. Но как только возникает первая нестандартная проблема, решение которой не удается найти на StackOverflow, вышеупомянутые персонажи превращаются в беспомощных жертв кажущейся простоты современной разработки ПО.

Для того, чтобы не оказаться одним из таких бедолаг, необходимо постоянно инвестировать свое время в получение фундаментальных знаний из области Computer Science. В частности, для прикладных разработчиков в большинстве случаев таким фундаментом является операционная система, в которой выполняются созданные ими программы.

Веб-фреймворки и контейнеры приложений рождаются и умирают, а инструменты, которыми они пользуются, и принципы, на которых они основаны, остаются неизменными уже десятки лет. Это означает, что вложение времени в изучение базовых понятий и принципов намного выгоднее в долгосрочной перспективе. Сегодня мы рассмотрим одну из основных для веб-разработчика концепций — сокеты. А в качестве прикладного аспекта, мы разберемся, что же такое на самом деле веб-сервер и начнем писать свой.

Что такое веб-сервер?

Начнем с того, что четко ответим на вопрос, что же такое веб-сервер?

В первую очередь — это сервер. А сервер — это процесс (да, это не железка), обслуживающий клиентов. Сервер — фактически обычная программа, запущенная в операционной системе. Веб-сервер, как и большинство программ, получает данные на вход, преобразовывает их в соответствии с бизнес-требованиями и осуществляет вывод данных. Данные на вход и выход передаются по сети с использованием протокола HTTP. Входные данные — это запросы клиентов (в основном веб-браузеров и мобильных приложений). Выходные данные — это зачастую HTML-код подготовленных веб-страниц.

Клиент общается с сервером по сети

На данном этапе логичными будут следующие вопросы: что такое HTTP и как передавать данные по сети? HTTP — это простой текстовый (т.е. данные могут быть прочитаны человеком) протокол передачи информации в сети Интернет. Протокол — это не страшное слово, а всего лишь набор соглашений между двумя и более сторонами о правилах и формате передачи данных. Его рассмотрение мы вынесем в отдельную тему, а далее попробуем понять, как можно осуществлять передачу данных по сети.

Как компьютеры взаимодействуют по сети

В Unix-подобных системах принят очень удобный подход для работы с различными устройствами ввода/вывода — рассматривать их как файлы. Реальные файлы на диске, мышки, принтеры, модемы и т.п. являются файлами. Т.е. их можно открыть, прочитать данные, записать данные и закрыть.

ls /dev показывает список устройств Linux

При открытии файла операционной системой создается т.н. файловый дескриптор. Это некоторый целочисленный идентификатор, однозначно определяющий файл в текущем процессе. Для того, чтобы прочитать или записать данные в файл, необходимо в соответсвующую функцию (например, read() или write()) передать этот дескриптор, чтобы четко указать, с каким файлом мы собираемся взаимодействовать.

int fd = open("/path/to/my/file", ...);

char buffer[1024];
read(fd, buffer, 1024);
write(fd, "some data", 10);

close(fd);

Очевидно, что т.к. общение компьютеров по сети — это также про ввод/вывод, то и оно должно быть организовано как работа с файлами. Для этого используется специальный тип файлов, т.н. сокеты.

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

Berkeley Sockets напоминают собой всем известную электрическую розетку

Т.к. видов межпроцессных взаимодействий с помощью сокетов множество, то и сокеты могут иметь различные конфигурации: сокет характеризуется семейством протоколов (IPv4 или IPv6 для сетевого и UNIX для локального взаимодействия), типом передачи данных (потоковая или датаграммная) и протоколом (TCP, UDP и т.п.).

Далее будет рассматриваться исключительно клиент-серверное взаимодействие по сети с использованием сокетов и стека протоколов TCP/IP.

Предположим, что наша прикладная программа хочет передать строку «Hello World» по сети, и соответствующий сокет уже открыт. Программа осуществляет запись этой строки в сокет с использованием функции write() или send(). Как эти данные будут переданы по сети?

Т.к. в общем случае размер передаваемых программой данных не ограничен, а за один раз сетевой адаптер (NIC) может передать фиксировнный объем информации, данные необходимо разбить на фрагменты, не превышающие этот объем. Такие фрагменты называются пакетами. Каждому пакету добавляется некоторая служебная информация, в частности содержащая адреса получателя и отправителя, и они начинают свой путь по сети.

Компьютер отправляет данные по сети разделив на фрагменты

Адрес компьютера в сети — это т.н. IP-адрес. IP (Internet Protocol) — протокол, который позволил объединить множество разнородных сетей по всеми миру в одну общую сеть, которая называется Интернет. И произошло это благодаря тому, что каждому компьютеру в сети был назначен собственный адрес.

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

TCP reassembly - восстанавливаем порядок пакетов на принимающей стороне

Вообще говоря, требование получать пакеты в том же порядке, в котором они были отправлены, не всегда является обязательным (например, при передаче потокового видео). Но, когда мы загружаем веб-страницу в браузере, мы ожидаем, что буквы на ней будут расположены ровно в том же порядке, в котором их нам отправил веб-сервер. Именно поэтому HTTP протокол работает поверх надеждного протокола передачи данных TCP, который будет рассмотрен ниже.

Чтобы организовать доставку пакетов в порядке их передачи, необходимо добавить в служебную информацию каждого пакета его номер в цепочке пакетов и на принимающей стороне делать сборку пакетов не в порядке их поступления, а в порядке, определенном этими номерами. Чтобы избежать доставки поврежденных пакетов, необходимо в каждый пакет добавить контрольную сумму и пакеты с неправильной контрольной суммой отбрасывать, ожидая, что они будут отправлены повторно.

Этим занимается специальный протокол потоковой передачи данных — TCP.

TCP — (Transmission Control Protocol — протокол управления передачей) — один из основных протоколов передачи данных в Интернете. Используется для надежной передачи данных с подтверждением доставки и сохранением порядка пакетов.

TCP segment внутри IP пакета

В силу того, что передачей данных по сети по протоколу TCP на одном и том же компьютере может заниматься одновременно несколько программ, для каждого из таких сеансов передачи данных необходимо поддерживать свою последовательность пакетов. Для этого TCP вводит понятие соединения. Соединение — это просто логическое соглашение между принимающей и передающей сторонами о начальных и текущих значениях номеров пакетов и состоянии передачи. Соединение необходимо установить (обменявшись несколькими служебными пакетами), поддерживать (периодически передавать данные, чтобы не наступил таймаут), а затем закрыть (снова обменявшись несколькими служебными пакетами).

Итак, IP определяет адрес компьютера в сети. Но, в силу наличия TCP соединений, пакеты могут принадлежать различным соединениям на одной и той же машине. Для того, чтобы различать соединения, вводится понятие TCP-порт. Это всего лишь пара чисел (одно для отправителя, а другое для получателя) в служебной информации пакета, определяющая, в рамках какого соединения должен рассматриваться пакет. Т.е. адрес соединения на этой машине.

Простейший TCP сервер

Теперь перейдем к практике. Попробуем создать свой собственный TCP-сервер. Для этого нам понадобится модуль socket из стандартной библиотеки Python.

Основная проблема при работе с сокетами у новичков связана с наличием обязательного магического ритуала подготовки сокетов к работе. Но имея за плечами теоретические знания, изложенные выше, кажущаяся магия превращается в осмысленные действия. Также необходимо отметить, что в случае с TCP работа с сокетами на сервере и на клиенте различается. Сервер занимается ожиданием подключений клиентов. Т.е. его IP адрес и TCP порт известны потенциальным клиентам заранее. Клиент может подключиться к серверу, т.е. выступает активной стороной. Сервер же ничего не знает об адресе клиента до момента подключения и не может выступать инициатором соединения. После того, как сервер принимает входящее соединения клиента, на стороне сервера создается еще один сокет, который является симметричным сокету клиента.

Итак, создаем серверный сокет:

# python3

import socket

serv_sock = socket.socket(socket.AF_INET,      # задамем семейство протоколов 'Интернет' (INET)
                          socket.SOCK_STREAM,  # задаем тип передачи данных 'потоковый' (TCP)
                          proto=0)             # выбираем протокол 'по умолчанию' для TCP, т.е. IP
print(type(serv_sock))                         # <class 'socket.socket'>

А где же обещанные int fd = open("/path/to/my/socket")? Дело в том, что системный вызов open() не позволяет передать все необходимые для инициализации сокета параметры, поэтому для сокетов был введен специальный одноименный системный вызов socket(). Python же является объектно-ориентированным языком, в нем вместо функций принято использовать классы и их методы. Код модуля socket является ОО-оберткой вокрут набора системных вызовов для работе с сокетами. Его можно представить себе, как:

class socket:  # Да, да, имя класса с маленькой буквы :(
    def __init__(self, sock_familty, sock_type, proto):
      self._fd = system_socket(sock_family, sock_type, proto)

    def write(self, data):
        # на самом деле вместо write используется send, но об этом ниже
        system_write(self._fd, data)

    def fileno(self):
        return self._fd

Т.е. доступ к целочисленному файловому дескриптору можно получить с помощью:

print(serv_sock.fileno())  # 3 или другой int

Так мы работаем с серверным сокетом, а в общем случае на серверной машине может быть несколько сетевых адаптеров, нам необходимо привязать созданный сокет к одному из них:

serv_sock.bind(('127.0.0.1', 53210))  # чтобы привязать сразу ко всем, можно использовать ''

Вызов bind() заставляет нас указать не только IP адрес, но и порт, на котором сервер будет ожидать (слушать) подключения клиентов.

Далее необходимо явно перевести сокет в состояние ожидания подключения, сообщив об этом операционной системе:

backlog = 10  # Размер очереди входящих подключений, т.н. backlog
serv_sock.listen(backlog)

После этого вызова операционная система готова принимать подключения от клиентов на этом сокете, хотя наш сервер (т.е. программа) — еще нет. Что же это означает и что такое backlog?

Как мы уже выяснили, взаимодействие по сети происходит с помощью отправки пакетов, а TCP требует установления соединения, т.е. обмена между клиентом и сервером несколькими служебными пакетами, не содержащими реальных бизнес-данных. Каждое TCP соединение обладает состоянием. Упростив, их можно представить себе так:

СОЕДИНЕНИЕ УСТАНАВЛИВАЕТСЯ -> УСТАНОВЛЕНО -> СОЕДИНЕНИЕ ЗАКРЫВАЕТСЯ

Таким образом, параметр backlog определяет размер очереди для установленных, но еще не обработанных программой соединений. Пока количество подключенных клиентов меньше, чем этот параметр, операционная система будет автоматически принимать входящие соединения на серверный сокет и помещать их в очередь. Как только количество установленных соединений в очереди достигнет значения backlog, новые соединения приниматься не будут. В зависимости от реализации (GNU Linux/BSD), OC может явно отклонять новые подключения или просто их игнорировать, давая возможность им дождаться освобождения места в очереди.

Теперь необходимо получить соединение из этой очереди:

client_sock, client_addr = serv_sock.accept()

В отличие от неблокирующего вызова listen(), который сразу после перевода сокета в слушающее состояние, возвращает управление нашему коду, вызов accept() является блокирующим. Это означает, что он не возвращает управление нашему коду до тех пор, пока в очереди установленных соединений не появится хотя бы одно подключение.

На этом этапе на стороне сервера мы имеем два сокета. Первый, serv_sock, находится в состоянии LISTEN, т.е. принимает входящие соединения. Второй, client_sock, находится в состоянии ESTABLISHED, т.е. готов к приему и передаче данных. Более того, client_sock на стороне сервера и клиенсткий сокет в программе клиента являются одинаковыми и равноправными участниками сетевого взаимодействия, т.н. peer’ы. Они оба могут как принимать и отправлять данные, так и закрыть соединение с помощью вызова close(). При этом они никак не влияют на состояние слушающего сокета.

Пример чтения и записи данных в клиентский сокет:

while True:
    data = client_sock.recv(1024)
    if not data:
        break
    client_sock.sendall(data)

И опять же справедливый вопрос — где обещанные read() и write()? На самом деле с сокетом можно работать и с помощью этих двух функций, но в общем случае сигнатуры read() и write() не позволяют передать все возможные параметры чтения/записи. Так, например, вызов send() с нулевыми флагами равносилен вызову write().

Немного коснемся вопроса адресации. Каждый TCP сокет определяется двумя парами чисел: (локальный IP адрес, локальный порт) и (удаленный IP адрес, удаленный порт). Рассмотрим, какие адреса на данный момент у наших сокетов:

serv_sock:
  laddr (ip=<server_ip>, port=53210)
  raddr (ip=0.0.0.0, port=*)  # т.е. любой

client_sock:
  laddr (ip=<client_ip>, port=51573)  # случайный порт, назначенный системой
  raddr (ip=<server_ip>, port=53210)  # адрес слушающего сокета на сервере

Полный код сервера выглядит так:

# python3

import socket

serv_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto=0)
serv_sock.bind(('', 53210))
serv_sock.listen(10)

while True:
    # Бесконечно обрабатываем входящие подключения
    client_sock, client_addr = serv_sock.accept()
    print('Connected by', client_addr)

    while True:
        # Пока клиент не отключился, читаем передаваемые
        # им данные и отправляем их обратно
        data = client_sock.recv(1024)
        if not data:
            # Клиент отключился
            break
        client_sock.sendall(data)

    client_sock.close()

Подключиться к этому серверу можно с использованием консольной утилиты telnet, предназначенной для текстового обмена информацией поверх протокола TCP:

telnet 127.0.0.1 53210
> Trying 192.168.0.1...
> Connected to 192.168.0.1.
> Escape character is '^]'.
> Hello
> Hello

Простейший TCP клиент

На клиентской стороне работа с сокетами выглядит намного проще. Здесь сокет будет только один и его задача только лишь подключиться к заранее известному IP-адресу и порту сервера, сделав вызов connect().

# python3

import socket

client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_sock.connect(('127.0.0.1', 53210))
client_sock.sendall(b'Hello, world')
data = client_sock.recv(1024)
client_sock.close()
print('Received', repr(data))

Заключение

Запоминать что-то без понимания, как это работает — злое зло не самый разумный подход для разработчика. Работа с сокетами тому отличный пример. На первый взгляд может показаться, что уложить в голове последовательность приготовления клиентских и серверных сокетов к работе практически не возможно. Это происходит из-за того, что не сразу понятен смысл производимых манипуляций. Однако, понимая, как осуществляется сетевое взаимодействие, API сокетов сразу становится прозрачным и легко оседает в подкорке. А с точки зрения полезности полученных знаний, я считаю. что понимание принципов сетевого взаимодействия жизненно важно для разработки и отладки действительно сложных веб-проектов.

Другие статьи из серии:

  • Пишем свой веб-сервер на Python: процессы, потоки и асинхронный I/O
  • Пишем свой веб-сервер на Python: протокол HTTP
  • Пишем свой веб-сервер на Python: стандарт WSGI
  • Пишем свой веб-сервер на Python: фреймворк Flask

Ссылки по теме

Справочная информация:

  • Сокеты
  • Веб-сервер
  • Протокол
  • Файловый дескриптор
  • Межпроцессное взаимодействие
  • Пакет
  • IP
  • TCP
  • Порт
  • Модуль socket

Литература

  • Beej’s Guide to Network Programming — отличные основы
  • UNIX Network Programming — продвинутый уровень

Мой вебинар на данную тему можно посмотреть на сайте GeekBrains.Ru.

Сегодня мы рассмотрим пример программирования сокетов Python. Мы создадим серверные и клиентские приложения на Python.

Содержание

  1. Программирование сокетов
  2. Пример
  3. Сервер сокетов
  4. Клиент сокета
  5. Вывод

Программирование сокетов

Чтобы понять программирование сокетов Python, нам нужно знать о трех интересных темах – Socket Server, Socket Client и Socket.

Итак, что такое сервер? Сервер – это программное обеспечение, которое ожидает запросов клиентов и обслуживает или обрабатывает их соответственно.

С другой стороны, клиент запрашивает эту услугу. Клиентская программа запрашивает некоторые ресурсы к серверу, и сервер отвечает на этот запрос.

Socket – это конечная точка двунаправленного канала связи между сервером и клиентом. Сокеты могут обмениваться данными внутри процесса, между процессами на одной машине или между процессами на разных машинах. Для любого взаимодействия с удаленной программой мы должны подключаться через порт сокета.

Основная цель этого руководства по программированию сокетов – познакомить вас с тем, как сервер сокетов и клиент взаимодействуют друг с другом. Вы также узнаете, как написать программу сервера сокетов в Python.

Пример

Ранее мы говорили, что клиент сокета запрашивает некоторые ресурсы у сервера, и сервер отвечает на этот запрос.

Итак, мы разработаем и серверную, и клиентскую модель, чтобы каждый мог общаться с ними. Шаги можно рассматривать так:

  1. Программа сервера сокетов запускается сначала и ждет любого запроса.
  2. Клиентская программа сначала инициирует диалог.
  3. Затем серверная программа будет реагировать на запросы клиента соответственно.
  4. Клиентская программа будет завершена, если пользователь введет сообщение «до свидания». Серверная программа также завершится, когда завершится клиентская программа, это необязательно, и мы можем поддерживать выполнение серверной программы на неопределенный срок или завершить работу с помощью какой-либо конкретной команды в клиентском запросе.

Сервер сокетов

Мы сохраним программу сервера сокетов, как socket_server.py. Чтобы использовать соединение, нам нужно импортировать модуль сокета.

Затем последовательно нам нужно выполнить некоторую задачу, чтобы установить соединение между сервером и клиентом.

Мы можем получить адрес хоста с помощью функции socket.gethostname(). Рекомендуется использовать адрес порта пользователя выше 1024, поскольку номер порта меньше 1024 зарезервирован для стандартного интернет-протокола.

Смотрите приведенный ниже пример кода сервера:

import socket


def server_program():
    # get the hostname
    host = socket.gethostname()
    port = 5000  # initiate port no above 1024

    server_socket = socket.socket()  # get instance
    # look closely. The bind() function takes tuple as argument
    server_socket.bind((host, port))  # bind host address and port together

    # configure how many client the server can listen simultaneously
    server_socket.listen(2)
    conn, address = server_socket.accept()  # accept new connection
    print("Connection from: " + str(address))
    while True:
        # receive data stream. it won't accept data packet greater than 1024 bytes
        data = conn.recv(1024).decode()
        if not data:
            # if data is not received break
            break
        print("from connected user: " + str(data))
        data = input(' -> ')
        conn.send(data.encode())  # send data to the client

    conn.close()  # close the connection


if __name__ == '__main__':
    server_program()

Итак, наш сервер сокетов работает на порту 5000 и будет ждать запроса клиента. Если вы хотите, чтобы сервер не завершал работу при закрытии клиентского соединения, просто удалите условие if и оператор break. Цикл while используется для бесконечного запуска серверной программы и ожидания клиентского запроса.

Клиент сокета

Мы сохраним клиентскую программу сокета python как socket_client.py. Эта программа похожа на серверную, за исключением привязки.

Основное различие между серверной и клиентской программой состоит в том, что в серверной программе необходимо связать адрес хоста и адрес порта вместе.

Смотрите ниже пример кода клиента сокета:

import socket


def client_program():
    host = socket.gethostname()  # as both code is running on same pc
    port = 5000  # socket server port number

    client_socket = socket.socket()  # instantiate
    client_socket.connect((host, port))  # connect to the server

    message = input(" -> ")  # take input

    while message.lower().strip() != 'bye':
        client_socket.send(message.encode())  # send message
        data = client_socket.recv(1024).decode()  # receive response

        print('Received from server: ' + data)  # show in terminal

        message = input(" -> ")  # again take input

    client_socket.close()  # close the connection


if __name__ == '__main__':
    client_program()

Вывод

Чтобы увидеть результат, сначала запустите программу сервера сокетов. Затем запустите клиентскую программу. После этого напишите что-нибудь из клиентской программы. Затем снова напишите ответ от серверной программы.

Наконец, напишите «до свидания» из клиентской программы, чтобы завершить обе программы. Ниже короткое видео покажет, как это работало на моем тестовом прогоне примеров программ сервера сокетов и клиента.

python socket programming, python socket server

pankaj$ python3.6 socket_server.py 
Connection from: ('127.0.0.1', 57822)
from connected user: Hi
 -> Hello
from connected user: How are you?
 -> Good
from connected user: Awesome!
 -> Ok then, bye!
pankaj$
pankaj$ python3.6 socket_client.py 
 -> Hi
Received from server: Hello
 -> How are you?
Received from server: Good
 -> Awesome!
Received from server: Ok then, bye!
 -> Bye
pankaj$

Обратите внимание, что сервер сокетов работает на порту 5000, но клиенту также требуется порт сокета для подключения к серверу. Этот порт назначается случайным образом при вызове клиентского соединения. В данном случае это 57822.

( 3 оценки, среднее 3.67 из 5 )

В этой статье мы напишем очень простой консольный чат на популярном языке Python. Состоять он будет из двух частей. Первая чаcть это сервер, куда будут приходить сообщения клиентов которые подключены к серверу. Вторая чаcть это клиент, которые отправляет сообщения серверу и получает сообщения от сервера.

Постановка задачи.

  • Написать сервер для приема сообщений от клиента и отправки сообщений всем остальным клиентам подключенным к серверу. Будем использовать протокол TCP/IP.
  • Собственно сам клиент. Который коннектится к серверу по TCP/IP. Отправляет и получает сообщения от сервера.
  • Ну и реализуем какое нибудь простое шифрование. Что бы сообщения могли читать только клиенты.

Часть первая. Сервер.

Первым делом нам надо создать сокет, который будет принимать соединения скажем на порту 5050 . Для работы с сокет в Python есть модуль который так и называется socket. Подключим его :

import socket 

Создадим сам сокет:

sock = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)

socket.AF_INET — для сокета используем IPv4 . socket.SOCK_DGRAM — тип сокета. Датаграммный сокет — это сокет, предназначенный для передачи данных в виде отдельных сообщений (датаграмм). По сравнению с потоковым сокетом, обмен данными происходит быстрее, но является ненадёжным: сообщения могут теряться в пути, дублироваться и переупорядочиваться. Датаграммный сокет допускает передачу сообщения нескольким получателям (multicasting) и широковещательную передачу (broadcasting).

Теперь свяжем сокет с адресом(интерфейсом) и портом :

sock.bind (('',5050))

Пустые кавычки значат что сокет слушает все доступные интерфейсы.

Теперь нам надо как то принимать сообщения. Нам совершенно все равно от кого и что получать. Задача получить и отправить остальным известным клиентам. По этому, мы будем использовать функцию socket.recvfrom(bufsize)  которая нам вернет данные и адрес сокета с которого получены эти данные.

data , addres = sock.recvfrom(1024)  # Буфер в байтах

Для отправки данных будем использовать функцию socket.sendto( bytes, address ) :

 sock.sendto(data,addres) 

Итог у нас такой :

import socket
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock.bind (('94.250.252.115',5050))
client = [] # Массив где храним адреса клиентов
print ('Start Server')
while 1 :
         data , addres = sock.recvfrom(1024)
         print (addres[0], addres[1])
         if  addres not in client : 
                 client.append(addres)# Если такого клиента нету , то добавить
         for clients in client :
                 if clients == addres : 
                     continue # Не отправлять данные клиенту, который их прислал
                 sock.sendto(data,clients)

Клиентская часть.

С клиентом немного все посложней. Так как это чат, нам надо получать и отправлять сообщения одновременно. Или не зависимо друг от друга. Для этого нам потребуется многопоточное выполнение нашего кода. Для этого мы будем использовать модуль threading

import threading

Первым делом создадим функцию которая будет получать сообщения от сервера:

def read_sok():
while 1 :
data = sor.recv(1024)
print(data.decode('utf-8'))

Теперь нам надо создать поток и запустить в нем эту функцию:

potok = threading.Thread(target= read_sok)
potok.start()

Теперь весь код с комментариями :

import socket
import threading
def read_sok():
while 1 :
data = sor.recv(1024)
print(data.decode('utf-8'))
server = '192.168.0.1', 5050 # Данные сервера
alias = input() # Вводим наш псевдоним
sor = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sor.bind(('', 0)) # Задаем сокет как клиент
sor.sendto((alias+' Connect to server').encode('utf-8'), server)# Уведомляем сервер о подключении
potok = threading.Thread(target= read_sok)
potok.start()
while 1 :
mensahe = input()
sor.sendto(('['+alias+']'+mensahe).encode('utf-8'), server)

Шифрование .

У нас очень упрощенный вариант, думаю c шифрованием мудрить не будем. Возьмем самый простой симметричный алгоритм XOR. Основная идея алгоритма состоит в том, что если у нас есть некая величина, есть некий шифровальный ключ (другая величина), то можно зашифровать исходные данные через этот ключ, применив операцию XOR побитно. Т.е. если у нас есть исходная фраза a и ключ k, то x = a ^ k. Теперь, если к шифру x опять применить ключ, то получим исходную фразу, т.е. a = x ^ k .

key = 567  # Ключ шифрования

crypt = ''
for i in  message  :
    crypt += chr(ord(i)^key)
 message  = crypt

Я не рассчитываю на уникальность материала, сам учусь ) Строго не судите. Подсказки и доработки приветствуются.

Ошибка в тексте? Выделите её и нажмите «Ctrl + Enter»

Комната для чата — это интерфейс, который позволяет двум или более людям общаться в чате и отправлять сообщения всем, кто находится в комнате. Сегодня мы поговорим про то, как создать простое чат-приложение на Python и разрешить нескольким клиентам подключаться к нему с помощью сокетов.

Мы используем встроенный в Python сокет-модуль. Он дает возможность осуществлять операции с сокетами. Эти операции широко используются в Интернете: они стоят за любым подключением к любой сети.

Кроме того, для изменения цвета текста, нам понадобится пакет colorama. С помощью этого пакета мы сможем присвоить цвет каждому клиенту в нашем чате. Давайте установим этот модуль:

pip3 install colorama

Поскольку мы используем сокеты, нам нужен серверный и клиентский код. Давайте начнем с серверной части.

Серверная часть

В нашей архитектуре вся работа сервера заключается в выполнении двух основных операций:

  • Прослушивание клиентских подключений. Если подключается новый клиент, мы добавляем его в нашу коллекцию клиентских сокетов
  • Запуск нового потока для каждого подключенного клиента, прием сообщений, отправленных от клиента, и трансляция их всем другим пользователям.

Приведенный ниже код создает TCP-сокет и привязывает его к адресу сервера, а затем прослушивает поступающие соединения:

import socket
from threading import Thread

# server's IP address
SERVER_HOST = "0.0.0.0"
SERVER_PORT = 5002 # port we want to use
separator_token = "<SEP>" # we will use this to separate the client name & message

# initialize list/set of all connected client's sockets
client_sockets = set()
# create a TCP socket
s = socket.socket()
# make the port as reusable port
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# bind the socket to the address we specified
s.bind((SERVER_HOST, SERVER_PORT))
# listen for upcoming connections
s.listen(5)
print(f"[*] Listening as {SERVER_HOST}:{SERVER_PORT}")

Обратите внимание, что мы использовали 0.0.0.0 в качестве IP-адреса сервера. Это охватывает все адреса IPv4 на локальном компьютере. Вы можете задаться вопросом, почему мы просто не используем localhost или 127.0.0.1. У сервера может быть два IP адреса, допустим 192.168.1.2 в одной сети и 10.0.0.1 в другой. При указании адреса 0.0.0.0 сервер слушает обе сети.

[python_ad_block]

Мы еще не принимаем соединения, так как не вызывали метод accept(). Приведенный ниже код завершает наш бэкенд:

def listen_for_client(cs):
    """
    This function keep listening for a message from `cs` socket
    Whenever a message is received, broadcast it to all other connected clients
    """
    while True:
        try:
            # keep listening for a message from `cs` socket
            msg = cs.recv(1024).decode()
        except Exception as e:
            # client no longer connected
            # remove it from the set
            print(f"[!] Error: {e}")
            client_sockets.remove(cs)
        else:
            # if we received a message, replace the <SEP> 
            # token with ": " for nice printing
            msg = msg.replace(separator_token, ": ")
        # iterate over all connected sockets
        for client_socket in client_sockets:
            # and send the message
            client_socket.send(msg.encode())

while True:
    # we keep listening for new connections all the time
    client_socket, client_address = s.accept()
    print(f"[+] {client_address} connected.")
    # add the new connected client to connected sockets
    client_sockets.add(client_socket)
    # start a new thread that listens for each client's messages
    t = Thread(target=listen_for_client, args=(client_socket,))
    # make the thread daemon so it ends whenever the main thread ends
    t.daemon = True
    # start the thread
    t.start()

Как упоминалось ранее, мы добавляем подключенный клиентский сокет в коллекцию наших сокетов. Затем запускаем новый поток и устанавливаем его как поток демона (daemon thread), который выполняет определенную нами функцию listen_for_client(). При наличии клиентского сокета эта функция ожидает отправки сообщения с помощью метода recv() и затем отправляет это сообщение всем другим подключенным клиентам.

Наконец, давайте закроем все сокеты:

# close client sockets
for cs in client_sockets:
    cs.close()
# close server socket
s.close()

Хорошо, с серверным кодом покончено, теперь давайте углубимся в код клиентской части.

Клиентская часть

Клиент выполняет три основные операции:

  • Подключение к серверу
  • Прослушивание сообщений, поступающих с сервера и вывод их на консоль (чтобы от сервера поступило сообщение, клиент должен отправить его на сервер, а сервер — распространить)
  • Ожидание сообщений от пользователей для их дальнейшей отправки на сервер.

Ниже представлен код для первой операции:

import socket
import random
from threading import Thread
from datetime import datetime
from colorama import Fore, init, Back

# init colors
init()

# set the available colors
colors = [Fore.BLUE, Fore.CYAN, Fore.GREEN, Fore.LIGHTBLACK_EX, 
    Fore.LIGHTBLUE_EX, Fore.LIGHTCYAN_EX, Fore.LIGHTGREEN_EX, 
    Fore.LIGHTMAGENTA_EX, Fore.LIGHTRED_EX, Fore.LIGHTWHITE_EX, 
    Fore.LIGHTYELLOW_EX, Fore.MAGENTA, Fore.RED, Fore.WHITE, Fore.YELLOW
]

# choose a random color for the client
client_color = random.choice(colors)

# server's IP address
# if the server is not on this machine, 
# put the private (network) IP address (e.g 192.168.1.2)
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 5002 # server's port
separator_token = "<SEP>" # we will use this to separate the client name & message

# initialize TCP socket
s = socket.socket()
print(f"[*] Connecting to {SERVER_HOST}:{SERVER_PORT}...")
# connect to the server
s.connect((SERVER_HOST, SERVER_PORT))
print("[+] Connected.")

Заодно мы устанавливаем цвет для каждого клиента (увидеть можно будет в выводе). Кроме того, давайте установим имя для каждого клиента, чтобы мы могли различать клиентов между собой:

# prompt the client for a name
name = input("Enter your name: ")

Отлично, двигаемся дальше! Приведенный ниже код отвечает за вторую операцию — прослушивание сообщений с сервера и вывод их на консоль:

def listen_for_messages():
    while True:
        message = s.recv(1024).decode()
        print("n" + message)

# make a thread that listens for messages to this client & print them
t = Thread(target=listen_for_messages)
# make the thread daemon so it ends whenever the main thread ends
t.daemon = True
# start the thread
t.start()

Кроме того, мы хотим, чтобы прослушивание сообщений происходило в фоне, т.е. чтобы этот поток был потоком-демоном.

Переходим к последней операции — ожиданию сообщений от пользователей с последующей отправкой их на сервер. Сделаем это следующим образом:

while True:
    # input message we want to send to the server
    to_send =  input()
    # a way to exit the program
    if to_send.lower() == 'q':
        break
    # add the datetime, name & the color of the sender
    date_now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') 
    to_send = f"{client_color}[{date_now}] {name}{separator_token}{to_send}{Fore.RESET}"
    # finally, send the message
    s.send(to_send.encode())

# close the socket
s.close()

Мы добавляем цвет для каждого клиента, его имя, а также текущую дату и время к отправляемому сообщению. Дальше мы отправляем сообщение с помощью метода send(). Для выхода из программы нужно будет ввести «q» в качестве сообщения.

Демонстрация функционала

Хорошо, теперь, когда мы закончили и серверную, и клиентскую часть, давайте проверим, как всё работает. Во-первых, давайте запустим один экземпляр сервера:

Круто, сервер мониторит предстоящие подключения клиентов, давайте попробуем запустить один экземпляр клиента:

Отлично! Первый клиент подключен к серверу и ему предлагается ввести имя пользователя. Теперь, чтобы убедиться, что он подключен, вернитесь к консоли сервера:

Обратите внимание, что сейчас мы используем адрес localhost (127.0.0.1), так как это одна и та же машина. Но если вы хотите подключиться с других машин в той же сети, вы также можете это сделать, просто измените SERVER_HOST в клиентской части кода с 127.0.0.1 на частный IP-адрес сервера.

Давайте запустим еще один клиент, чтобы они могли поболтать:

Удивительно! Как видите, сообщения клиентов отличаются цветом, что позволяет различать пользователей. Что ж, давайте запустим третьего клиента для развлечения:

Заключение

Отлично, теперь каждое сообщение, отправленное одним клиентом, отправляется всем остальным. Обратите внимание, что цвета меняются при каждом повторном выполнении сценария client.py.

Пожалуйста, сверьтесь с полным кодом, чтобы вы могли легко запустить чат-приложение самостоятельно!

Мы также советуем добавить больше возможностей в эту программу. Например, можно сделать оповещение для всех пользователей при подключении нового клиента!

Итак, сегодня мы поговорили о том, как создать чат-приложение на Python. Надеемся, данная статья была вам полезна!

Успехов в написании кода!

Перевод статьи «How to Make a Chat Application in Python».

Table of Contents

В этой статье я бы хотел показать как написать простое приложение мессенджер менее чем в 150 строк.

Серверная часть

Начнём с сервера(наше приложение будет состоять из скриптов сервера и клиента), через который можно получать входящие запросы от клиентов, желающих общаться. Традиционно указываем путь до интерпретатора и импортируем необходимые модули. Конкретно socket и threading. Первый отвечает непосредственно за “общение” процесссов между собой, второй за многопоточность. О этих модулях подробно можно почитать например здесь — socket , threading.

Использование фреймворков, таких как Twisted и SocketServer, было возможным, но мне показалось это излишним для такого простого программного обеспечения, как наше.

#!/usr/bin/env python3
from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread

Давайте обозначим константы, отвечающие например за адрес порта и размер буфера.

clients = {}
addresses = {}
HOST = ''
PORT = 33000
BUFSIZ = 1024
ADDR = (HOST, PORT)
SERVER = socket(AF_INET, SOCK_STREAM)
SERVER.bind(ADDR)

Теперь мы разбиваем нашу задачу на прием новых соединений, рассылку сообщений и обработку определенных клиентов. Давайте начнем с принятия соединений:

def accept_incoming_connections():
    """Настраивает обработку для входящих клиентов."""
    while True:
        client, client_address = SERVER.accept()
        print("%s:%s присоединился к переписке" % client_address)
        client.send(bytes("Привет!"+
                          "Введи своё имя и нажми Enter", "utf8"))
        addresses[client] = client_address
        Thread(target=handle_client, args=(client,)).start()

Это просто цикл, который всегда ждет входящих соединений и, как только он его получает, регистрирует соединение (печатает некоторые сведения о соединении) и отправляет подключенному клиенту приветствие. Затем он сохраняет адрес клиента и позже запускает поток обработки для этого клиента. Конечно, мы еще не определили для этого целевую функцию handle_client (), но вот как мы это сделаем:

def handle_client(client):
     name = client.recv(BUFSIZ).decode("utf8")
    welcome = 'Добро пожаловать %s! если желаете покинуть чат то, нажмите {quit} чтобы выйти.' % name
    client.send(bytes(welcome, "utf8"))
    msg = "%s Теперь в переписке" % name
    broadcast(bytes(msg, "utf8"))
    clients[client] = name
     while True:
        msg = client.recv(BUFSIZ)
        if msg != bytes("{quit}", "utf8"):
            broadcast(msg, name+": ")
        else:
            client.send(bytes("{quit}", "utf8"))
            client.close()
            del clients[client]
            broadcast(bytes("%s покинул переписку." % name, "utf8"))
            break

Естественно, после того, как мы отправим новому клиенту приветственное сообщение, он ответит именем, которое он хочет использовать для дальнейшего общения. В функции handle_client () первая задача, которую мы делаем, — мы сохраняем это имя, а затем отправляем клиенту еще одно сообщение о дальнейших инструкциях. После этого идет основной цикл: здесь мы получаем дополнительные сообщения от клиента и, если сообщение не содержит инструкций для выхода, мы просто передаем сообщение другим подключенным клиентам (мы определим метод широковещания через минуту ). Если мы сталкиваемся с сообщением с инструкциями выхода (то есть клиент отправляет {quit}), мы возвращаем то же самое сообщение клиенту, а затем мы закрываем сокет подключения для него. Затем мы делаем очистку, удаляя запись для клиента, и, наконец, сообщаем другим связанным людям, что этот конкретный человек покинул чат.

Теперь пропишем функцию broadcast ():

def broadcast(msg, prefix=""):
     for sock in clients:
        sock.send(bytes(prefix, "utf8")+msg)

Эта функция просто отправляет сообщение всем подключенным клиентам и при необходимости добавляет дополнительный префикс. Мы передаем префикс для broadcast () в нашей функции handle_client () и делаем это так, чтобы люди могли точно знать, кто является отправителем конкретного сообщения.
Это были все необходимые функции для нашего сервера. Наконец, мы добавили код для запуска нашего сервера и прослушивания входящих соединений:

if __name__ == "__main__":
    SERVER.listen(5)
    print("Ожидание соединения")
    ACCEPT_THREAD = Thread(target=accept_incoming_connections)
    ACCEPT_THREAD.start()  # Бесконечный цикл.
    ACCEPT_THREAD.join()
    SERVER.close()

Мы присоединяемся к ACCEPT_THREAD, чтобы основной скрипт ожидал его завершения и не переходил на следующую строку, которая закрывает сервер.
Это завершает наш серверный скрипт.

В итоге получаем вот такой код для серверной части:

#!/usr/bin/env python3
from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread

def accept_incoming_connections():
    while True:
        client, client_address = SERVER.accept()
        print("%s:%s соединено" % client_address)
        client.send(bytes("Добро пожаловать , введите своё имя и нажмите Enter", "utf8"))
        addresses[client] = client_address
        Thread(target=handle_client, args=(client,)).start()


def handle_client(client):  
    name = client.recv(BUFSIZ).decode("utf8")
    welcome = 'Добро пожаловать %s! Если желаете выйти,то нажмите {quit} чтобы выйти.' % name
    client.send(bytes(welcome, "utf8"))
    msg = "%s вступил в переписку" % name
    broadcast(bytes(msg, "utf8"))
    clients[client] = name

    while True:
        msg = client.recv(BUFSIZ)
        if msg != bytes("{quit}", "utf8"):
            broadcast(msg, name+": ")
        else:
            client.send(bytes("{quit}", "utf8"))
            client.close()
            del clients[client]
            broadcast(bytes("%s покинул переписку" % name, "utf8"))
            break


def broadcast(msg, prefix=""):

    for sock in clients:
        sock.send(bytes(prefix, "utf8")+msg)

        
clients = {}
addresses = {}

HOST = ''
PORT = 33000
BUFSIZ = 1024
ADDR = (HOST, PORT)

SERVER = socket(AF_INET, SOCK_STREAM)
SERVER.bind(ADDR)

if __name__ == "__main__":
    SERVER.listen(5)
    print("ожидание соединения")
    ACCEPT_THREAD = Thread(target=accept_incoming_connections)
    ACCEPT_THREAD.start()
    ACCEPT_THREAD.join()
    SERVER.close()

Клиентская часть###

Теперь приступим к наиболее интересной части нашего приложения — к клиенту. В качестве gui будем использовать tkinter, т.к в нём довольно легко построить несложное приложение. Традиционно импортируем модуль tkinter, а также модули использовавшиеся ранее при написании серверной части программы.

#!/usr/bin/env python3

from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread
import tkinter

Теперь мы напишем функции для обработки отправки и получения сообщений. Начнем с получения:

def receive():
    """обработка получения сообщений"""
    while True:
        try:
            msg = client_socket.recv(BUFSIZ).decode("utf8")# декодируем,чтобы не получить кракозябры
            msg_list.insert(tkinter.END, msg)
        except OSError:
            break

Почему мы снова используем бесконечный цикл? Потому что мы будем получать сообщения совершенно независимо от того, как и когда мы отправляем сообщения. Мы не хотим, чтобы это было приложение для чата с функциональностью уровня рации. Мы хотим приложение в котором можно отправлять или получать сообщения одновременно; мы хотим получать сообщения, когда сами того пожелаем, и отправлять их, когда захотим.

Функциональность внутри цикла довольно проста; recv () является блокирующей частью. Он останавливает выполнение до тех пор, пока не получит сообщение, а когда это произойдет, мы продвигаемся вперед и добавляем сообщение в msglist. Затем мы определяем msg_list, который является функцией Tkinter для отображения списка сообщений на экране.
Далее мы определим функцию send ():

def send(event=None):
    """обработка отправленных сообщений"""
    msg = my_msg.get()
    my_msg.set("")  # очищаем поле.
    client_socket.send(bytes(msg, "utf8"))
    if msg == "{quit}":
        client_socket.close()
        top.quit()

my_msg — это поле ввода в графическом интерфейсе, и поэтому мы извлекаем сообщение для отправки с помощью msg = my_msg.get (). После этого мы очищаем поле ввода и затем отправляем сообщение на сервер, который, как мы видели ранее, передает это сообщение всем клиентам (если это не сообщение о выходе). Если это сообщение о выходе, мы закрываем сокет, а затем приложение с графическим интерфейсом (через top.close ())

Мы определяем еще одну функцию, которая будет вызываться, когда мы решим закрыть окно с GUI. Это своего рода функция очистки до закрытия, которая закрывает соединение с сокетом до закрытия графического интерфейса:

def on_closing(event=None):
    """Эта функция вызывается когда закрывается окно"""
    my_msg.set("{quit}")
    send()

Это устанавливает в поле ввода значение {quit}, а затем вызывает send (). Начнем с определения виджета верхнего уровня и установки его заголовка, как и в любой другой программе на tkinter:

top = tkinter.Tk()
top.title("TkMessenger")

Затем создаём фрейм со списком сообщений, поле для ввода сообщений и скроллбар для перемещения по истории переписки

messages_frame = tkinter.Frame(top)
my_msg = tkinter.StringVar()
my_msg.set("Введите ваше сообщение здесь.")
scrollbar = tkinter.Scrollbar(messages_frame)#скроллбар

“Упаковываем” наши элементы и размечаем их расположение в окне:

msg_list = tkinter.Listbox(messages_frame, height=15, width=50, yscrollcommand=scrollbar.set)
scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
msg_list.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
msg_list.pack()
messages_frame.pack()

После этого мы создаем поле ввода для пользователя, чтобы ввести свое сообщение, и привязать его к строковой переменной, определенной выше. Мы также привязываем его к функции send (), чтобы всякий раз, когда пользователь нажимает return, сообщение отправлялось на сервер.

Далее мы создаем кнопку отправки, если пользователь желает отправить свои сообщения, нажав на нее. Опять же, мы связываем нажатие этой кнопки с функцией send ().

И да, мы также упаковываем все то, что создали только сейчас. Кроме того, не забудьте использовать функцию очистки on_closing (), которая должна вызываться, когда пользователь хочет закрыть окно GUI:

entry_field = tkinter.Entry(top, textvariable=my_msg)
entry_field.bind("<Return>", send)
entry_field.pack()
send_button = tkinter.Button(top, text="отправить", command=send)
send_button.pack()
top.protocol("WM_DELETE_WINDOW", on_closing)

И вот мы подходим к завершению. Мы еще не написали код для подключения к серверу. Для этого мы должны запросить у пользователя адрес сервера. Я сделал это, просто используя input (), чтобы пользователь встретился с подсказкой командной строки, запрашивающей адрес хоста перед запуском окна с графическим интерфейсом. В будущем можно добавить виджет для этой цели. А пока вот так:

HOST = input('Введите хост: ')
PORT = input('Введите порт: ')
if not PORT:
    PORT = 33000  # Стандартный порт
else:
    PORT = int(PORT)
BUFSIZ = 1024
ADDR = (HOST, PORT)
client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect(ADDR)

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

chat_app_gif

receive_thread = Thread(target=receive)
receive_thread.start()
tkinter.mainloop()

Вот и всё! Теперь наш скрипт клиентской части выглядит вот так:

#!/usr/bin/env python3
from socket import AF_INET, socket, SOCK_STREAM
from threading import Thread
import tkinter


def receive():
    while True:
        try:
            msg = client_socket.recv(BUFSIZ).decode("utf8")
            msg_list.insert(tkinter.END, msg)
        except OSError:
            break


def send(event=None):
    msg = my_msg.get()
    my_msg.set("")
    client_socket.send(bytes(msg, "utf8"))
    if msg == "{quit}":
        client_socket.close()
        top.quit()


def on_closing(event=None):
    my_msg.set("{quit}")
    send()

top = tkinter.Tk()
top.title("TkMessenger")

messages_frame = tkinter.Frame(top)
my_msg = tkinter.StringVar()
my_msg.set("Введите ваше сообщение здесь")
scrollbar = tkinter.Scrollbar(messages_frame)
msg_list = tkinter.Listbox(messages_frame, height=15, width=50, yscrollcommand=scrollbar.set)
scrollbar.pack(side=tkinter.RIGHT, fill=tkinter.Y)
msg_list.pack(side=tkinter.LEFT, fill=tkinter.BOTH)
msg_list.pack()
messages_frame.pack()

entry_field = tkinter.Entry(top, textvariable=my_msg)
entry_field.bind("<Return>", send)
entry_field.pack()
send_button = tkinter.Button(top, text="отправить", command=send)
send_button.pack()

top.protocol("WM_DELETE_WINDOW", on_closing)


HOST = input('Введите хост: ')
PORT = input('Введите порт: ')
if not PORT:
    PORT = 33000
else:
    PORT = int(PORT)

BUFSIZ = 1024
ADDR = (HOST, PORT)

client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect(ADDR)

receive_thread = Thread(target=receive)
receive_thread.start()
tkinter.mainloop()

Да, наше приложение не может тягаться с такими гигантами как: telegram, viber, клиентами xmpp/jabber; однако нам удалось создать простой чат, который каждый может развить в что-то своё: сделать уклон в безопасность(например шифруя передаваемые пакеты) или в хороший ux/ui. Получилась своего рода база для чего-то большего и это круто. Спасибо за прочтение, буду рад любым замечаниям и пожеланиям. Традиционно исходный код программы доступен в моём репозитории на github.

Python provides two levels of access to network programming. These are – 

  • Low-Level Access: At the low level, you can access the basic socket support of the operating system. You can implement client and server for both connection-oriented and connectionless protocols.
  • High-Level Access: At the high level allows to implement protocols like HTTP, FTP, etc.

In this article, we will discuss Network Socket Programming. But before getting started let’s understand what are sockets.

What are Sockets?

Consider a bidirectional communication channel, the sockets are the endpoints of this communication channel. These sockets (endpoints) can communicate within a process, between processes on the same machine, or between the processes on the different machines. Sockets use different protocols for determining the connection type for port-to-port communication between clients and servers.

Sockets Vocabulary

Sockets have their own set of vocabulary, let’s have a look at them – 

Term Description
Domain The set of protocols used for transport mechanisms like AF_INET, PF_INET, etc.
Type Type of communication between sockets
Protocol Identifies the type of protocol used within domain and type. Typically it is zero
Port The server listens for clients calling on one or more ports. it can be a string containing a port number, a name of the service, or a Fixnum port
Hostname

Identifies a network interface. It can be a 

  • a string containing hostname, IPv6 address, or a double-quad address.
  • an integer
  • a zero-length string
  • a string “<broadcast>”

Socket Programming

Socket programming is a way of connecting two nodes on a network to communicate with each other. One socket(node) listens on a particular port at an IP, while the other socket reaches out to the other to form a connection. The server forms the listener socket while the client reaches out to the server. They are the real backbones behind web browsing. In simpler terms, there is a server and a client. We can use the socket module for socket programming. For this, we have to include the socket module – 

import socket

to create a socket we have to use the socket.socket() method. 

Syntax:

socket.socket(socket_family, socket_type, protocol=0)

Where, 

  • socket_family: Either AF_UNIX or AF_INET
  • socket_type: Either SOCK_STREAM or SOCK_DGRAM.
  • protocol: Usually left out, defaulting to 0.

Example:

Python3

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print(s)

Output:

<socket.socket fd=74, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘0.0.0.0’, 0)>

The socket module provides various methods for both client and server-side programming. Let’s see each method in detail.

Socket Server Methods

These methods are used on the server-side. Let’s see each method in detail – 

Function Name Description
s.bind() Binds address to the socket. The address contains the pair of hostname and the port number.
s.listen() Starts the TCP listener
s.accept() Passively accepts the TCP client connection and blocks until the connection arrives

Socket Client Methods

This method is used on the client side. Let’s see this method in detail – 

Function Name Description
s.connect() Actively starts the TCP server connection

Socket General Methods

These are the general methods of the socket module. Let’s see each method in detail.

Function Name Description
s.send() Sends the TCP message
s.sendto() Sends the UDP message
s.recv() Receives the TCP message
s.recvfrom() Receives the UDP message
s.close() Close the socket
socket.ghostname() Returns the host name

Simple Server Client Program

Server

A server has a bind() method which binds it to a specific IP and port so that it can listen to incoming requests on that IP and port. A server has a listen() method which puts the server into listening mode. This allows the server to listen to incoming connections. And last a server has an accept() and close() method. The accept method initiates a connection with the client and the close method closes the connection with the client. 

Example: Network Programming Server-Side

Python3

import socket

s = socket.socket()

print ("Socket successfully created")

port = 40674

s.bind(('', port))

print ("socket binded to %s" %(port))

s.listen(5)   

print ("socket is listening")

while True:

    c, addr = s.accept()

    print ('Got connection from', addr )

    c.send(b'Thank you for connecting')

    c.close()

Explanation:

  • We made a socket object and reserved a port on our pc.
  • After that, we bound our server to the specified port. Passing an empty string means that the server can listen to incoming connections from other computers as well. If we would have passed 127.0.0.1 then it would have listened to only those calls made within the local computer.
  • After that, we put the server into listening mode. 5 here means that 5 connections are kept waiting if the server is busy and if a 6th socket tries to connect then the connection is refused.
  • At last, we make a while loop and start to accept all incoming connections and close those connections after a thank you message to all connected sockets.

Client

Now we need something with which a server can interact. We could tenet to the server like this just to know that our server is working. Type these commands in the terminal:

# start the server
python server.py

# keep the above terminal open
# now open another terminal and type:

telnet localhost 12345

Output :

network programing server side

In the telnet terminal you will get this:

network programming telnet server

This output shows that our server is working. Now for the client-side: 

Example: Network Programming Client Side

Python3

import socket

s = socket.socket()

port = 40674

s.connect(('127.0.0.1', port))

print(s.recv(1024))

s.close()

Output:

python network programming server clientpython network programming client side

Explanation:

  • We connect to localhost on port 40674 (the port on which our server runs) and lastly, we receive data from the server and close the connection.
  • Now save this file as client.py and run it from the terminal after starting the server script.

Note: For more information on Socket Programming refer to our Socket Programming in Python Tutorial

Понравилась статья? Поделить с друзьями:
  • Как написать сервер гта 5 рп
  • Как написать сервер входящей почты
  • Как написать сеошный текст
  • Как написать сео текст для сайта
  • Как написать сео оптимизированную статью