В прошлых темах tcp-клиенты получали и отправляли сообщения упорядочено: отправляли запрос — получали ответ и повторяли этот цикл. Однако нередко встречается ситуация, когда получение и отправка сообщений не связаны друг с другом. Банальный пример — чат, где человек может написать множество сообщений без относительно того, получит ли он на них какой-либо ответ. И, наоборот, получить много сообщений без отправки запросов. Для рассмотрения примера подобного взаимодействия напишем небольшую программу — консольный tcp-чат.
Определение сервера
Вначале создадим простейший консольный проект сервера. Определим в нем следующий код:
using System.Net; using System.Net.Sockets; ServerObject server = new ServerObject();// создаем сервер await server.ListenAsync(); // запускаем сервер class ServerObject < TcpListener tcpListener = new TcpListener(IPAddress.Any, 8888); // сервер для прослушивания Listclients = new List(); // все подключения protected internal void RemoveConnection(string id) < // получаем по id закрытое подключение ClientObject? client = clients.FirstOrDefault(c =>c.Id == id); // и удаляем его из списка подключений if (client != null) clients.Remove(client); client?.Close(); > // прослушивание входящих подключений protected internal async Task ListenAsync() < try < tcpListener.Start(); Console.WriteLine(«Сервер запущен. Ожидание подключений. «); while (true) < TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync(); ClientObject clientObject = new ClientObject(tcpClient, this); clients.Add(clientObject); Task.Run(clientObject.ProcessAsync); >> catch (Exception ex) < Console.WriteLine(ex.Message); >finally < Disconnect(); >> // трансляция сообщения подключенным клиентам protected internal async Task BroadcastMessageAsync(string message, string id) < foreach (var client in clients) < if (client.Id != id) // если id клиента не равно id отправителя < await client.Writer.WriteLineAsync(message); //передача данных await client.Writer.FlushAsync(); >> > // отключение всех клиентов protected internal void Disconnect() < foreach (var client in clients) < client.Close(); //отключение клиента >tcpListener.Stop(); //остановка сервера > > class ClientObject < protected internal string Id < get;>= Guid.NewGuid().ToString(); protected internal StreamWriter Writer < get;>protected internal StreamReader Reader < get;>TcpClient client; ServerObject server; // объект сервера public ClientObject(TcpClient tcpClient, ServerObject serverObject) < client = tcpClient; server = serverObject; // получаем NetworkStream для взаимодействия с сервером var stream = client.GetStream(); // создаем StreamReader для чтения данных Reader = new StreamReader(stream); // создаем StreamWriter для отправки данных Writer = new StreamWriter(stream); >public async Task ProcessAsync() < try < // получаем имя пользователя string? userName = await Reader.ReadLineAsync(); string? message = $»вошел в чат»; // посылаем сообщение о входе в чат всем подключенным пользователям await server.BroadcastMessageAsync(message, Id); Console.WriteLine(message); // в бесконечном цикле получаем сообщения от клиента while (true) < try < message = await Reader.ReadLineAsync(); if (message == null) continue; message = $»: «; Console.WriteLine(message); await server.BroadcastMessageAsync(message, Id); > catch < message = $»покинул чат»; Console.WriteLine(message); await server.BroadcastMessageAsync(message, Id); break; > > > catch (Exception e) < Console.WriteLine(e.Message); >finally < // в случае выхода из цикла закрываем ресурсы server.RemoveConnection(Id); >> // закрытие подключения protected internal void Close() < Writer.Close(); Reader.Close(); client.Close(); >>
Весь код программы фактически разбивается на два класса: класс ServerObject представляет сервер, а класс ClientObject представляет подключение — отдельного клиента. Сначала рассмотрим код ClientObject.
Как создать мониторинг сети Ping с помощью Microsoft Excel?
Ты смотришь только Простые видео по программированию не просто так
Для создания объекта ClientObject вызывается конструктор, в котором устанавливаются поля и свойства класса:
protected internal string Id < get;>= Guid.NewGuid().ToString(); protected internal StreamWriter Writer < get;>protected internal StreamReader Reader < get;>TcpClient client; ServerObject server; // объект сервера public ClientObject(TcpClient tcpClient, ServerObject serverObject)
Каждое подключение будет уникальным образом идентифицировано с помощью свойства Id, которое хранит значение Guid в строчном виде. Через конструктор получаем объект TcpClient для взаимодействия с подключенным клиентом и родительский объект ServerObject. Для отправки и получения сообщений для простоты применяются свойства Writer и Reader, которые представляют соответственно классы StreamWriter и StreamReader.
Основные действия происходят в методе Process() , в котором реализован простейший протокол для обмена сообщениями с клиентом. Так, в начале получаем имя подключенного пользователя, а затем в цикле получаем все остальные сообщения. Для трансляции этих сообщений всем остальным клиентам будет использоваться метод BroadcastMessageAsync() класса ServerObject.
Класс ServerObject представляет сервер. Он определяет две переменных: переменная tcpListener хранит объект TcpListener для прослушивания входящих подключений, а переменная clients представляет список, в который добавляются все подключенные клиенты.
TcpListener tcpListener = new TcpListener(IPAddress.Any, 8888); // сервер для прослушивания List clients = new List(); // все подключения
Основной метод — ListenAsync() , в котором будет осуществляться прослушивание всех входящих подключений:
protected internal async Task ListenAsync() < try < tcpListener.Start(); Console.WriteLine(«Сервер запущен. Ожидание подключений. «); while (true) < TcpClient tcpClient = await tcpListener.AcceptTcpClientAsync(); ClientObject clientObject = new ClientObject(tcpClient, this); clients.Add(clientObject); Task.Run(clientObject.ProcessAsync);
При получении подключения создаем для него объект ClientObject, добавляем его в список clients и запускаем новую задачу, в которой будет выполняться метод Process объекта ClientObject.
Для передачи сообщений всем клиентам, кроме отправившего, предназначен метод BroadcastMessageAsync() :
protected internal async Task BroadcastMessageAsync(string message, string id) < foreach (var client in clients) < if (client.Id != id) // если id клиента не равно id отправителя < await client.Writer.WriteLineAsync(message); //передача данных await client.Writer.FlushAsync(); >> >
Таким образом разделяются сущность подключенного клиента и сущность сервера.
И после определения классов ServerObject и ClientObject надо запустить прослушивание:
ServerObject server = new ServerObject();// создаем сервер await server.ListenAsync(); // запускаем сервер
Здесь просто запускается новый поток, который обращается к методу ListenAsync() объекта ServerObject.
Это не идеальный проект сервера, но достаточный для базовой демонстрации организации кода и взаимодействия с клиентом.
Создание клиента
Теперь создадим новый консольный проект для клиента, который будет подключать к выше определенному серверу. Определим для клиента следующий код:
using System.Net.Sockets; string host = «127.0.0.1»; int port = 8888; using TcpClient client = new TcpClient(); Console.Write(«Введите свое имя: «); string? userName = Console.ReadLine(); Console.WriteLine($»Добро пожаловать, «); StreamReader? Reader = null; StreamWriter? Writer = null; try < client.Connect(host, port); //подключение клиента Reader = new StreamReader(client.GetStream()); Writer = new StreamWriter(client.GetStream()); if (Writer is null || Reader is null) return; // запускаем новый поток для получения данных Task.Run(()=>ReceiveMessageAsync(Reader)); // запускаем ввод сообщений await SendMessageAsync(Writer); > catch (Exception ex) < Console.WriteLine(ex.Message); >Writer?.Close(); Reader?.Close(); // отправка сообщений async Task SendMessageAsync(StreamWriter writer) < // сначала отправляем имя await writer.WriteLineAsync(userName); await writer.FlushAsync(); Console.WriteLine(«Для отправки сообщений введите сообщение и нажмите Enter»); while (true) < string? message = Console.ReadLine(); await writer.WriteLineAsync(message); await writer.FlushAsync(); >> // получение сообщений async Task ReceiveMessageAsync(StreamReader reader) < while (true) < try < // считываем ответ в виде строки string? message = await reader.ReadLineAsync(); // если пустой ответ, ничего не выводим на консоль if (string.IsNullOrEmpty(message)) continue; Print(message);//вывод сообщения >catch < break; >> > // чтобы полученное сообщение не накладывалось на ввод нового сообщения void Print(string message) < if (OperatingSystem.IsWindows()) // если ОС Windows < var position = Console.GetCursorPosition(); // получаем текущую позицию курсора int left = position.Left; // смещение в символах относительно левого края int top = position.Top; // смещение в строках относительно верха // копируем ранее введенные символы в строке на следующую строку Console.MoveBufferArea(0, top, left, 1, 0, top + 1); // устанавливаем курсор в начало текущей строки Console.SetCursorPosition(0, top); // в текущей строке выводит полученное сообщение Console.WriteLine(message); // переносим курсор на следующую строку // и пользователь продолжает ввод уже на следующей строке Console.SetCursorPosition(left, top + 1); >else Console.WriteLine(message); >
Код клиента также фактически состоит из двух функциональных частей: метод SendMessageAsync для отправки данных и метод ReceiveMessageAsync для получения данных.
Метод SendMessageAsync в качестве параметра получает объект StreamWriter, который, используя NetworkStream клиента, будет отправлять строку на сервер. А метод ReceiveMessageAsync получает объект StreamReader, через который будет считывать из NetworkStream присланное сообщение в виде строки.
В качестве бонусного костыля приведен метод Print, который позволяет избежать вывода полученного сообщения в той же строке консоли, где пользователь вводит свое сообщение — в этом случае ввод просто переносится на следующую строку. Правда, это доступно на данный момент только на Windows.
Чтобы не блокировать ввод сообщений в главном потоке, для получения сообщений создается новая задача, которая обращается к методу ReceiveMessageAsync:
client.Connect(host, port); //подключение клиента Reader = new StreamReader(client.GetStream()); Writer = new StreamWriter(client.GetStream()); if (Writer is null || Reader is null) return; // запускаем новый поток для получения данных Task.Run(()=>ReceiveMessageAsync(Reader)); await SendMessageAsync(Writer);
В конце запустим сервер и пару копий приложений клиента и протестируем их:
Источник: metanit.com
Создайте простую комнату чата в локальной сети с помощью Python
Всем привет, сегодня мы собираемся создать действительно простую чат-комнату на Python 3 с помощью встроенных модулей socket и threading.
Наше приложение для чата будет состоять из сервера и нескольких клиентов. Клиенты собираются установить соединение с сервером через сокет. Чтобы соединение через сокет работало, и сервер, и клиент должны быть либо на одном компьютере, либо в одной сети. Так что чат будет работать в локальной сети (Local Area Network).
Понимание сокетов Python
Прежде чем переходить к коду, нам нужно понять, как работают сокеты. Необязательно знать, как сокеты работают внутри, просто интересно знать, что модуль сокетов python использует библиотеку сокетов C для обмена данными по сети. Прежде всего, необходимо инициализировать сервер сокетов, затем клиент будет подключаться напрямую к серверу. Как только соединение установлено, сокет может как прослушивать, так и отправлять сообщения, если один сокет отправляет, а другой слушает, мы сможем обмениваться данными между ними.
Шаг 1. Кодирование сервера
После того, как вы создали свой server.py файл, мы можем приступить к кодированию:
Для начала импортируем нужный нам модуль:
import socket
Затем давайте создадим наш объект сокета:
my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket.AF_INET и socket.SOCK_STREAM — это просто константы, определенные в модуле сокета для выбора типа сокета, который нам нужен, вам не нужно понимать, что они означают, чтобы их использовать.
Теперь сокет создан, мы можем привязать его к порту и, при желании, к адресу. Мы создаем две константы с именами PORT и ADDRESS для хранения порта и адреса, который мы собираемся использовать. Мы пока сообщаем адрес «0.0.0.0» , поскольку не знаем, каким будет адрес клиента.
PORT = 8000 ADDRESS = «0.0.0.0» my_socket.bind((ADDRESS, PORT))
Теперь наш сокет готов, мы можем начать прослушивание соединения. Затем нам нужно принять входящее соединение.
my_socket.listen() client, client_address = my_socket.accept()
Метод listen () просто разрешает входящие соединения. Метод accept () возвращает кортеж, первый элемент — это соединение, теперь мы будем использовать его для взаимодействия с клиентом, а второй — кортеж, содержащий IP-адрес клиента, а также используемый порт (это может быть отличается от порта, который вы настроили).
Теперь мы готовы начать прослушивание сообщений от клиента!
message = client.recv(1024) #bytes print(message.decode())
Метод recv () принимает только один необязательный аргумент, размер буфера, то есть максимальное пространство, доступное для приема сообщения. Не забывайте, что этот метод возвращает байтовый объект, поэтому вам нужно декодировать его в строку.
Хорошо, теперь мы должны закончить с самым простым серверным сценарием, который возможен, а клиентский сценарий еще более прост, как вы увидите. Перед тем, как перейти к клиентскому коду, давайте убедимся, что у нас такой же серверный код. Если вы хорошо следовали этому руководству, ваш код должен выглядеть примерно так:
#server.py import socket my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) PORT = 8000 ADDRESS = «0.0.0.0» my_socket.bind((ADDRESS, PORT)) my_socket.listen() client, client_address = my_socket.accept() result = client.recv(1024) print(result.decode())
Шаг 2. Кодирование клиента
Клиентский сценарий почти такой же, как и серверный, за исключением того, что вместо того, чтобы слушать соединение и сообщение, мы фактически собираемся подключаться и отправлять сообщения.
Сначала импортируем модуль и создаем наш объект сокета:
import socket my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Теперь нам нужно определить порт и адрес сервера. Если вы используете тот же порт, что и я, он должен быть 8000 . Для адреса вам просто нужно ввести адрес сервера, если вы запускаете сервер и клиент на одном компьютере, адрес сервера должен быть localhost , иначе просто найдите IP-адрес сервера (я не буду объяснять как это сделать здесь, но вы сможете легко найти информацию).
PORT = 8000 ADDRESS = «localhost» # Same as «127.0.1.1»
Затем мы можем подключиться к серверу:
my_socket.connect((ADDRESS, PORT)
Теперь мы готовы отправлять сообщения! Просто введите:
my_socket.send(«The message you want to send».encode())
Не забывайте кодировать строку в байты, как мы видели ранее, сокеты обмениваются байтами, а не строками.
Итак, теперь ваш клиентский скрипт должен выглядеть примерно так:
#client.py import socket my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = «localhost» # «127.0.1.1» port = 8000 my_socket.connect((host, port)) my_socket.send(«hello».encode())
Теперь вы должны иметь возможность использовать сценарий, запускать серверный сценарий, а после его запуска запускать клиентский сценарий. Если все прошло хорошо, вы должны увидеть выбранное вами сообщение, отображаемое в консоли сервера консоли.
Улучшение нашей базовой демонстрации
Теперь вы, вероятно, протестировали нашу небольшую демонстрацию и, возможно, говорите себе, что это не похоже на чат. И как вы говорите, это была просто демонстрация для понимания сокетов, теперь мы действительно можем реализовать наш небольшой чат.
Во-первых, поскольку это чат, нам, вероятно, потребуется отправить несколько сообщений, а не только одно, поэтому мы собираемся отредактировать наш серверный скрипт так, чтобы он всегда слушал, а в клиенте мы позволим пользователю вводить некоторые сообщение для отправки на сервер.
#server.py import socket my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) PORT = 8000 ADDRESS = «0.0.0.0» my_socket.bind((ADDRESS, PORT)) my_socket.listen() client, client_address = my_socket.accept() # Instead of receiving only one message, let’s make an infinite loop while True: result = client.recv(1024) print(result.decode())
Как видите, я добавил бесконечный цикл, чтобы никогда не переставать слушать сообщения. Теперь поработаем над клиентским скриптом:
#client.py import socket my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = «localhost» # «127.0.1.1» port = 8000 my_socket.connect((host, port)) while True: message_to_send = input(«Enter your message : «) my_socket.send(message_to_send.encode())
И здесь, как вы можете видеть, теперь пользователь может ввести сообщение, которое он хочет отправить, и я также добавил сюда бесконечный цикл. Я рекомендую вам попробовать этот код, чтобы убедиться, что он работает.
Шаг 3. Разрешите нашему клиенту получить
Хорошо, теперь мы можем отправлять несколько сообщений на сервер, но как насчет получения сообщений с сервера? Это то, над чем мы будем работать в этой части.
Итак, нам нужно будет одновременно прослушивать ввод пользователя и сообщения сервера, как мы можем это сделать?
Одним из самых простых решений является использование встроенного модуля потоковой передачи Python, благодаря которому мы сможем запускать несколько потоков одновременно, я не буду вдаваться в подробности здесь, просто посмотрите на потоки как рабочие, которые выполняют свою работу в фоновом режиме, но могут взаимодействовать с основной программой Python. Если вы не знаете, как работают потоки, я рекомендую вам найти дополнительную информацию о них, поскольку это действительно полезно для сетей и программирования в целом.
Теперь мы знаем, как решить проблему, нам нужен модуль потоковой передачи, поэтому давайте импортируем его:
import threading
Теперь нам нужно определить две функции: одну для прослушивания, а другую для отправки. Что касается отправляющей части, нам просто нужно поместить наш цикл while в функцию, примерно так:
def thread_sending(): while True: message_to_send = input() my_socket.send(message_to_send.encode())
И функция прослушивания усложнять не собирается:
def thread_receiving(): while True: message = my_socket.recv(1024).decode() print(message)
Когда у нас есть две функции, мы можем создать два потока, по одному для каждой функции, и запустить их. Потоки действительно просты в использовании, нам просто нужно создать два объекта потоков и указать, какую функцию они будут выполнять:
thread_send = threading.Thread(target=thread_sending) thread_receive = threading.Thread(target=thread_receiving)
Нам просто нужно их запустить, и на этом пока все для клиента:
thread_send.start() thread_receive.start()
Вы можете попробовать запустить клиентский скрипт, и вы не увидите никакой разницы, потому что мы не заставляли сервер отправлять какие-либо сообщения, так что давайте поработаем над этим. Для простоты мы просто заставим сервер отправлять что-то каждый раз при получении сообщения, примерно так:
while True: result = client.recv(1024) print(result.decode()) client.send(«Message received !».encode())
Теперь вы можете отправить сообщение и получить ответ от сервера. Как всегда, прежде чем переходить к следующему шагу, давайте убедимся, что мы работаем с одним и тем же кодом:
#server.py import socket my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) PORT = 8000 ADDRESS = «0.0.0.0» my_socket.bind((ADDRESS, PORT)) my_socket.listen() client, client_address = my_socket.accept() # Instead of receiving only one message, let’s make an infinite loop while True: result = client.recv(1024) print(result.decode()) client.send(«Message received !».encode())
#client.py import socket import threading my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = «localhost» # «127.0.1.1» port = 8000 my_socket.connect((host, port)) def thread_sending(): while True: message_to_send = input() my_socket.send(message_to_send.encode()) def thread_receiving(): while True: message = my_socket.recv(1024).decode() print(message) thread_send = threading.Thread(target=thread_sending) thread_receive = threading.Thread(target=thread_receiving) thread_send.start() thread_receive.start()
Шаг 4. Добавление нескольких клиентов
Хорошо, теперь мы можем подключаться к серверу с помощью клиента, мы можем отправлять и получать сообщения с сервера, мы почти закончили, но разве чат не состоит из нескольких человек? Да, конечно, теперь нам нужно иметь возможность подключить более одного клиента к чату, и это то, над чем мы будем работать в этой части.
Итак, сначала мы могли просто подумать, что для создания чата нам просто нужно запустить несколько клиентов, но после тестирования этого метода вы поймете, что в чате клиенту необходимо взаимодействовать с другими клиентами, а не только с ними. сервер. Чтобы решить эту проблему, нам нужно будет вести список всех подключенных клиентов, и каждый раз, когда кто-то отправляет сообщение, мы будем отправлять это сообщение всем клиентам в списке. Наш сервер будет передавать сообщения от одного клиента к другому.
Первый шаг — иметь возможность обрабатывать несколько соединений, как вы можете видеть в коде, метод accept () вызывается только один раз, что означает, что мы можем получить только одно соединение, давайте изменим это, и мы собираемся написать код в функциях, чтобы иметь возможность использовать потоки:
def thread_accept(): while True: my_socket.listen() client, client_address = my_socket.accept()
Теперь мы можем добавить клиента в список рассылки, нам нужно сначала создать список (вне функции):
broadcast_list = []
И добавляем клиента в список (в цикле):
broadcast_list.append(client)
И последнее, нам нужно настроить поток, который будет прослушивать сообщения от клиента:
start_listenning_thread(client)
Как видите, мы еще не закодировали эту функцию, мы сделаем это за одну минуту, прежде чем убедиться, что у нас есть такая же функция:
def accept_loop(): while True: my_socket.listen() client, client_address = my_socket.accept() broadcast.append(client) start_listenning_thread(client)
Хорошо, теперь у нас точно такой же код, поэтому давайте создадим функцию start_listenning_thread . Эта функция должна создать выделенный поток для этого клиента, который будет прослушивать сообщения и транслировать их:
def start_listenning_thread(client): client_thread = threading.Thread( target = listen_thread, args = (client,) #the list of argument for the function ) client_thread.start()
Теперь нам нужно реализовать нашу listen_thread функцию:
def listen_thread(client): while True: message = client.recv(1024).decode() print(f»Received message : «) broadcast(message)
И последняя функция, которую нужно написать, broadcast просто отправит сообщение каждому клиенту в списке рассылки:
def broadcast(message): for client in broadcast: client.send(message.encode())
Теперь мы можем просто запустить функцию, чтобы начать принимать соединения. Теперь ваш код должен выглядеть так:
#server.py import socket my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) PORT = 8000 ADDRESS = «0.0.0.0» broadcast_list = [] my_socket.bind((ADDRESS, PORT)) def accept_loop(): while True: my_socket.listen() client, client_address = my_socket.accept() broadcast_list.append(client) start_listenning_thread(client) def start_listenning_thread(client): client_thread = threading.Thread( target=listen_thread, args=(client,) #the list of argument for the function ) client_thread.start() def listen_thread(client): while True: message = client.recv(1024).decode() print(f»Received message : «) broadcast(message) def broadcast(message): for client in broadcast_list: client.send(message.encode()) accept_loop()
Если вы все написали правильно, теперь у вас должна быть возможность запускать сервер и подключать к нему несколько клиентов, отправлять сообщения и т. Д.
Шаг 5. Несколько вопросов, которые необходимо решить
Если вы протестировали чат, вы должны были заметить несколько проблем: во-первых, когда сообщение отправлено, у нас нет способа узнать, кто отправил сообщение, а во-вторых, (этот менее заметен), но мы не создали никакого способа обработать остановку соединения между клиентом и сервером, мы рассмотрим это позже.
Добавьте никнеймы в наше приложение
Итак, у нас есть два варианта реализации псевдонимов: мы можем реализовать это на стороне сервера или на стороне клиента. Для простоты мы выберем второй вариант. (Имейте в виду, что наш чат совсем не защищен, существует множество эксплойтов, но нас это не волнует.)
Клиентский сценарий должен будет попросить пользователя выбрать псевдоним, а затем отправить псевдоним вместе с сообщением. Это не очень сложно:
nickname = input(«Choose your nickname : «).strip()
И мы можем убедиться, что ник не пустой:
while not nickname: nickname = input(«Your nickname should not be empty : «).strip()
И теперь у нас есть ник пользователя, мы можем отправить его с сообщением, наша функция thread_sending теперь должна выглядеть так:
def thread_sending(): while True: message_to_send = input() message_with_nickname = nickname + » : » + message_to_send my_socket.send(message_with_nickname.encode())
А теперь у пользователей появился ник, все было не так сложно. Ваш client.py должен выглядеть так:
#client.py import socket import threading nickname = input(«Choose your nickname : «).strip() while not nickname: nickname = input(«Your nickname should not be empty : «).strip() my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = «localhost» # «127.0.1.1» port = 8000 my_socket.connect((host, port)) def thread_sending(): while True: message_to_send = input() message_with_nickname = nickname + » : » + message_to_send my_socket.send(message_with_nickname.encode()) def thread_receiving(): while True: message = my_socket.recv(1024).decode() print(message) thread_send = threading.Thread(target=thread_sending) thread_receive = threading.Thread(target=thread_receiving) thread_send.start() thread_receive.start()
Теперь сервер должен обрабатывать, если клиент отключается.
Вы могли заметить, что когда клиент отключается (используя Ctrl + C), сервер ведет себя очень странно, когда он никогда не прекращает печатать received message : .
Итак, сначала давайте убедимся, что клиент отправляет сообщение, только если оно не пустое:
message_to_send = input() if message_to_send: message_with_nickname = nickname + » : » + message_to_send my_socket.send(message_with_nickname.encode())
И что сервер печатает сообщение, только если оно не пустое, если оно есть, это означает, что клиент был отключен, поэтому мы можем остановить прослушивающий поток (чтобы остановить поток, мы можем просто выйти из функции):
message = client.recv(1024).decode() if message: print(f»Received message : «) broadcast(message) else: return
Теперь ваш код должен выглядеть так:
#server.py import socket import threading my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) PORT = 8000 ADDRESS = «0.0.0.0» broadcast_list = [] my_socket.bind((ADDRESS, PORT)) def accept_loop(): while True: my_socket.listen() client, client_address = my_socket.accept() broadcast_list.append(client) start_listenning_thread(client) def start_listenning_thread(client): client_thread = threading.Thread( target=listen_thread, args=(client,) #the list of argument for the function ) client_thread.start() def listen_thread(client): while True: message = client.recv(1024).decode() if message: print(f»Received message : «) broadcast(message) else: print(f»client has been disconnected : «) return def broadcast(message): for client in broadcast_list: try: client.send(message.encode()) except: broadcast_list.remove(client) print(f»Client removed : «) accept_loop()
И клиентский скрипт:
#client.py import socket import threading nickname = input(«Choose your nickname : «).strip() while not nickname: nickname = input(«Your nickname should not be empty : «).strip() my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) host = «localhost» # «127.0.1.1» port = 8000 my_socket.connect((host, port)) def thread_sending(): while True: message_to_send = input() if message_to_send: message_with_nickname = nickname + » : » + message_to_send my_socket.send(message_with_nickname.encode()) def thread_receiving(): while True: message = my_socket.recv(1024).decode() print(message) thread_send = threading.Thread(target=thread_sending) thread_receive = threading.Thread(target=thread_receiving) thread_send.start() thread_receive.start()
Если ваш код выглядит так, чат должен работать. Очевидно, что вы можете добавить к нему много вещей, но этот урок был посвящен созданию очень простого чата.
Источник: digitrain.ru
Как написать программу для мониторинга сети на C#
Если тебе надоели постоянные обрывы связи и косяки провайдера, но субъективные оценки типа «подвисает» не внушают доверия, лучший выбор — записать состояние сети в автоматическом режиме. Причем для этого необязательно гонять Nagios, который к тому же не так прост в настройке. Сегодня мы напишем утилиту для мониторинга сети, которая легко настраивается и сохраняет в журнал RTT до заданных хостов, packet loss и скорость соединения (опционально), а логи летят прямо в Telegram.
Виновником появления этой статьи стал уже несколько месяцев сбоящий интернет, который мне предоставляет единственный в округе провайдер. Увы, в мою деревню ничего, кроме ADSL, не завезли, и, судя по качеству связи, и тот не дошел без многочисленных скруток. Packet loss порой доходит до 60–70%, что уже ни в какие ворота не лезет. Поэтому я решил сам измерить качество связи, дабы ткнуть провайдеру под нос логи вместе с заявлением о расторжении договора.
Как написать программу для мониторинга сети на C#
Наша цель — написать простой сетевой монитор, чтобы в фоновом режиме отслеживать главные показатели в сети и сохранять их для анализа. Думаю, сбора следующих параметров хватит с головой, а если тебе понадобится что-то еще, всегда можно добавить (не забудь рассказать об этом мне).
- Пинг для заданных хостов. Просто маст-хэв для любой диагностической утилиты. Измеряя пинг, можно узнать также и процент потерь пакетов (packet loss), и коды ошибок, позволяющие узнать, что именно не так с сетью. Например, Destination Prohibited означает, что сеть вроде и есть, но администратор какого-то из промежуточных устройств не пропускает пакет. В общем, анализировать статус-коды ответов обязательно.
- Реальная возможность подключений по TCP. Возможна ситуация, когда хосты вроде живы и откликаются на пинг, DNS работает, а доступ в интернет закрыт за неоплату. Этот тест потенциально позволит нам выявить недобросовестного провайдера, который подделывает ответы на пинги, но не обеспечивает реальный коннект.
- Уведомления о времени даунтайма в Telegram. Они должны отправляться, как только соединение восстановится. Сообщение по-хорошему должно включать расширенную инфу о пинге и потерях пакетов после сбоя, а также состояние HTTP-клиента.
- Доступ к роутеру. Для домашней сети с нестабильным Wi-Fi это особенно актуально. Роутер может просто упасть от перегрузки (например, очередной школохакер ломится на дырявый WPS, но вместо взлома получается DoS) или попросту не выдержать всех клиентов, которых в ином «умном доме» может быть и 15, и 20. Короче, роутер в любой момент может уйти в перезагрузку, а мы будем грешить на провайдера. Это нехорошо, поэтому при потере связи с роутером мы не будем тестировать дальше, а просто подождем, пока починят.
Цели обрисованы. Теперь детали реализации.
- Программа предназначена для длительной работы в фоновом режиме. Оформим программу как системный сервис Windows.
- Если мы работаем в фоновом режиме, ни консольный интерфейс, ни тем более GUI нам не нужен. Тем лучше — меньше кода.
- Проверки не должны сильно нагружать канал, ведь будет некомфортно работать. Так что постоянно флудить пингами мы не станем. Отправим очередь из десятка пакетов раз в минуту-две, и хватит. Реже отправлять не имеет смысла — большинство неполадок устраняются в течение нескольких минут, а мы хотим знать о каждом сбое.
- Возможность хранить отчет в JSON и выгружать CSV для изучения в Excel — с фильтрацией по дате создания.
- Неплохо бы прикрутить возможность забирать логи по сети и скидывать статистику на центральный сервер, но в рамках демо я этого делать не буду.
Из этого следует, что нам понадобится работа с JSON. Писать я буду на C# и воспользуюсь модулем Json.NET.
Json.NET — популярная и простая библиотека для работы с JSON. Скачать ее можно с NuGet, а примеры использования лежат на сайте проекта.
Написание программы
Для начала скачай Visual Studio с сайта Microsoft, если у тебя ее еще нет. Нужна поддержка языка C# и NuGet (с вкладки «Дополнительные компоненты»).
Первым делом создаем новый проект типа «Консольное приложение». Можно было, конечно, реализовать его в качестве «Службы Windows», тогда не нужно было бы городить костыли для регистрации нашего монитора как системной службы. Бонусом получили бы автозапуск. Жаль, что в случае «шаблонного» сервиса мы теряем ту гибкость и управляемость, что имеем при ручном управлении.
Готово. Теперь — алгоритм. Алгоритм работы программы будет прост. Во-первых, нужно прочитать настройки. Они у нас будут в файле JSON рядом с исполняемым файлом. Во-вторых, надо создать и запустить таймер, чтобы неожиданные задержки канала не мешали нам производить замеры через равные промежутки времени.
И в-третьих, надо написать код сохранения результатов замеров. Поехали!
Сперва определим, что именно мы сможем настраивать. Я выбрал следующие параметры:
- хост и порт, до которых будет проходить проверка работоспособности HTTP;
- количество пакетов пинга и их тайм-аут;
- задержка перед отправлением следующего пакета пинга;
- задержка между соседними измерениями (та, которая определяет, раз в сколько минут проверка);
- включить или выключить вывод сообщений в консоль (для отладки);
- хосты, которые будем пинговать;
- IP роутера (чтобы узнавать, не завис ли он). Ты спросишь, зачем отдельно IP роутера, если его можно указать в общем списке адресов для проверки, и будешь прав. Разница в том, что, если программа не обнаружит связи с роутером, остальные хосты проверяться не будут, чтобы не тратить ресурсы;
- тайм-аут для подключения по HTTP;
- максимальный уровень packet loss, при котором подключение считается нормальным. Мне пришлось поставить себе 10%, так как 5–7% совсем не редкость для моей деревни;
- выходной формат строки для CSV, если ты вдруг решишь отключить вывод ненужных столбцов. Признаюсь, я уже забыл, зачем мне это понадобилось;
- выходной файл CSV, в который будут дописываться результаты и возможность отключить запись.
Источник: tech-geek.ru