Как написать чат для локальной сети

Время на прочтение
8 мин

Количество просмотров 40K

Несколько месяцев назад понадобилось разработать чат для локальной сети одного офиса, а также выступить с этой программой на научной конференции. Делать его я решил в среде разработки Builder C++ 2006. При написании статьи у меня возникла одна самая главная проблема — полное отсутствие опыта в работе с сетями в билдере, поэтому статью пишу для таких же «программистов», как я. Отмечу сразу, в интернете найдется множество программ, которые, несомненно, будут лучше моей, но задание было не найти программу, а разработать. Статья получится большая, поэтому разделю ее на 2 части — серверную и клиентскую.

Первым делом надо было решить, что это будет за приложение.

Идея

Первое, что пришло в голову — открывать сервер на каждом компьютере, объединяя все компьютеры в кольцо. Сообщение передавать тому клиенту, к которому мы сейчас подключены. Там проверять, нам ли это сообщение, или нет; если нет — то отправлять дальше до тех пор, пока оно не достигнет адресата. Идея не понравилась, главным образом из-за того, что не хотелось, чтобы сообщение проходило через промежуточных пользователей.

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

Конечно же, должен присутствовать графический интерфейс, причем такой, чтобы работал по принципу «plug and play» — запустил программу и сразу можно приниматься за переписку. Поэтому в окне программы будет минимум компонентов, даже не будет меню бара.

Как будем пересыпать сообщения? Используя сокеты, а именно стандартные компоненты билдера ClientSocket и ServerSocket, которые будут использоваться в программах клиента и сервера соответственно.

Реализация

Сервер

Программа-сервер рассчитана на одноразовое использование. Т.е. при выходе из нее не сохраняется никакая информация о клиентах, в самой же программе все хранится в массиве. Вообще, сам интерфейс сокетов достаточно интересен. Для того, чтобы отправить сообщение клиенту, используется команда SendText, принимающая строку сообщения типа AnsiString (где-то я вычитал про длину в 4,3 миллиарда символов, что само собой впечатляет), но чтобы отправить его именно тому, кому нужно, следует указывать номер клиента, а не, например, его IP-адрес. При этом номера клиентам выдаются в том порядке, в каком они подключились. В .h файле объявлен массив m типа AnsiString, состоящий из 100 элементов. Честно говоря, я не проверил максимально возможное количество подключений к серверу, поэтому будем считать, что оно ограничивается только величиной этого массива. При подключении клиента первым делом отправляется его имя на сервер. Оно вносится в первую свободную ячейку массива, при этом номер элемента и будет являться номером клиента, по которому мы будем отправлять сообщения. Чтобы найти первую пустую ячейку, я написал функцию analog(), которая просто перебирает массив и возвращает номер пустой ячейки.

int TFormMain::analog()
{
int a;
for(int i=0;i<mass;i++)
{
if(m[i]=="")
{
a=i;
break;
}
}
return a;
}

В событии OnConnect сервера запускается таймер. Почему-то код исполняемый таймером у меня не получилось выполнять сразу в событии, поэтому таймер выполняет его и сразу же отключается. По таймеру сервер с помощью цикла отправляет всем клиентам сообщение, в котором в одной строке содержатся все имена подключенных в данный момент клиентов. Зачем это сделано — я расскажу в описании клиента. Список же клиентов формирует функция online() «склеивающая» имена клиентов из массива.

Подключение клиентов


void __fastcall TFormMain::ServerSocketClientConnect(TObject *Sender,
      TCustomWinSocket *Socket)
{
Timer1->Enabled=true;
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::Timer1Timer(TObject *Sender)
{

if(ServerSocket->Socket->ActiveConnections!=0)
for(int i=0;i<ServerSocket->Socket->ActiveConnections;i++)
ServerSocket->Socket->Connections[i]->SendText("8714"+online());
Timer1->Enabled=false;
}
//---------------------------------------------------------------------------

AnsiString TFormMain::online()
{
char str[500]="";
for(int i=0;i<analog();i++)
{
strcat(str,m[i].c_str());
strcat(str,",");
}
return str;
}

Однако, все самое интересное происходит в событии сервера OnRead. Структура каждого сообщения, посылаемого как клиентом, так и сервером, обязательно содержит в начале 4 цифры. Это случайная комбинация цифр, придуманная мной для того, чтобы сервер и клиент могли различать сообщения, необходимые для «авторизации», или же сообщения, содержащие в себе текст для пересылки. Всего клиент может посылать серверу 4 типа сообщений. Сообщения с кодом 6141 посылаются серверу при первом подключении, они также сообщают серверу имя нового клиента, а сервер вносит его в массив и выводит в Memo (декоративном элементе, созданном просто чтобы знать, кто в данный момент подключен). Сообщение с кодами 5280 и 5487 потеряли свою актуальность, но почему то не были убраны мной из кода сервера. Сообщения с кодом 3988 самые важные. Это и есть сообщения содержащие в себе всю информацию для обмена сообщениями между пользователями. Структура такого сообщения:

3988<Имя отправителя>%<Имя получателя>:<Текст сообщения>.

Вообще, из каждого полученного сообщения сервер первым делом выделяет код методом SubString, от этом в дальнейшем зависят его дальнейшие действия. Из этого же сообщения сервер также выделяет меня отправителя и получателя, а также текст сообщения. Затем формируется сообщение вида 7788<Имя отправителя>:<Текст сообщения>. Оно отправляется клиенту-получателю. Как, если известно только его номер а не имя? Для этого написана функция numer(AnsiString), принимающая имя, перебирающая массив и возвращающая номер ячейки в котором это имя находится.

Обработка входящих сообщений

void __fastcall TFormMain::ServerSocketClientRead(TObject *Sender,
      TCustomWinSocket *Socket)
{
message=Socket->ReceiveText();
time=Now().CurrentDateTime();
if(message.SubString(1,4).AnsiCompare("6141")==0)
{
m[analog()]=message.SubString(5,message.Length());
ListBox1->Clear();
for(int i=0;i<ServerSocket->Socket->ActiveConnections;i++)
{
ListBox1->Items->Add(m[i]);
}
}
else if(message.SubString(1,4).AnsiCompare("5487")==0)
{
for(int i=0;i<ServerSocket->Socket->ActiveConnections;i++)
ServerSocket->Socket->Connections[i]->SendText("8714"+online());
}
else if(message.SubString(1,4).AnsiCompare("3988")==0)
{
nametowho=message.SubString(message.AnsiPos('Й')+1,message.AnsiPos(':')-message.AnsiPos('Й')-1);
name=message.SubString(5,message.AnsiPos('Й')-5);
if(nametowho.IsEmpty()==false && (message.SubString(message.AnsiPos(':')+1,message.Length()).IsEmpty())==false)
{
ServerSocket->Socket->Connections[numer(nametowho)]->SendText("7788"+name+":"+message.SubString(message.AnsiPos(':')+1,message.Length()));
ofstream fout("chat.txt",ios::app);
fout<<time.c_str()<<"   "<<message.c_str()<<endl;
fout.close();
}
}
else if(message.SubString(1,4).AnsiCompare("5280")==0)
{

ServerSocket->Socket->Connections[numer(message.SubString(message.Pos('#')+1,message.Pos('%')-message.Pos('#')-1))]->SendText(
"6734"+message.SubString(message.Pos('%')+1,message.Length()-message.Pos('%')));
}
}

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

void __fastcall TFormMain::ServerSocketClientDisconnect(TObject *Sender,
      TCustomWinSocket *Socket)
{
if(ServerSocket->Socket->ActiveConnections!=0)
{
for(int i=0;i<mass;i++)
{
m[i]="";
}
TestNames();

Timer1->Enabled=true;

}
}

Графическая часть

Внешний вид окна сервера:

Изначально у меня не было в планах выводить какую либо информацию в окне сервера, но в итоге решил выводить самое важное: IP-адрес сервера, количество активных подключений, порт сервера, имя компьютера ка котором он запущен (почему-то всегда «1», исправить я это пока не смог), и список имен подключенных клиентов. Сервер сворачивается в область уведомлений. Реализовано это несколькими функциями, подробно разбирать я их не буду. Также на сервере полностью отключен отлов ошибок (которых вообще за 2 недели непрерывной работы не возникало, но мало ли, все таки полностью парализуется работа сети).

Графическая часть


void __fastcall TFormMain::DrawItem(TMessage& Msg)
{
     IconDrawItem((LPDRAWITEMSTRUCT)Msg.LParam);
     TForm::Dispatch(&Msg);
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::MyNotify(TMessage& Msg)
{
    POINT MousePos;

    switch(Msg.LParam)
    {
        case WM_RBUTTONUP:
            if (GetCursorPos(&MousePos))
            {
                PopupMenu1->PopupComponent = FormMain;
                SetForegroundWindow(Handle);
                PopupMenu1->Popup(MousePos.x, MousePos.y);
            }
            else
                Show();
            break;
        case WM_LBUTTONDBLCLK:
        Show();

        break;
        default:
            break;
    }
    TForm::Dispatch(&Msg);
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
bool __fastcall TFormMain::TrayMessage(DWORD dwMessage)
{
   NOTIFYICONDATA tnd;
   PSTR pszTip;

   pszTip = TipText();

   tnd.cbSize          = sizeof(NOTIFYICONDATA);
   tnd.hWnd            = Handle;
   tnd.uID             = IDC_MYICON;
   tnd.uFlags          = NIF_MESSAGE | NIF_ICON | NIF_TIP;
   tnd.uCallbackMessage	= MYWM_NOTIFY;

   if (dwMessage == NIM_MODIFY)
    {
        tnd.hIcon		= (HICON)IconHandle();
        if (pszTip)
           lstrcpyn(tnd.szTip, pszTip, sizeof(tnd.szTip));
	    else
        tnd.szTip[0] = '';
    }
   else
    {
        tnd.hIcon = NULL;
        tnd.szTip[0] = '';
    }

   return (Shell_NotifyIcon(dwMessage, &tnd));
}
//---------------------------------------------------------------------------
HICON __fastcall TFormMain::IconHandle(void)
{
return (Image2->Picture->Icon->Handle);
}

//---------------------------------------------------------------------------
PSTR __fastcall TFormMain::TipText(void)
{
        return ("Office Chat");

}
//---------------------------------------------------------------------------
LRESULT IconDrawItem(LPDRAWITEMSTRUCT lpdi)
{
return 0;
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------


void __fastcall TFormMain::FormDestroy(TObject *Sender)
{
	TrayMessage(NIM_DELETE);
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::N1Click(TObject *Sender)
{
Show();
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::N2Click(TObject *Sender)
{
Application->Terminate();
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::FormCloseQuery(TObject *Sender, bool &CanClose)
{
CanClose=false;
FormMain->Hide();
}
//---------------------------------------------------------------------------



void __fastcall TFormMain::FormCreate(TObject *Sender)
{
unsigned long Size = 256;
   char *Buffer = new char[Size];

Label5->Caption=GetUserName(Buffer, &Size);
delete [] Buffer;
}
//---------------------------------------------------------------------------

В заключение хочу сказать, что получилось достаточно примитивно написанный, однако стабильно работающий сервер, позволяющий одновременно переписываться 20 людям (больше я просто не проверял). Все исходники, exe-файлы и полный разбор кода клиента будут во второй статье.

Спасибо за внимание.

В этой статье мы напишем очень простой консольный чат на популярном языке 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»

img-Moy-chatik.jpg

Доброго времени суток!

Для работы большинства популярных мессенджеров (Skype, Telegram, Viber и др.) требуется доступ к сети Интернет (причем, стабильный доступ!). А что если нужно быстро обмениваться сообщениями и файлами в локальной сети, — среди компьютеров, где такого доступа нет или он не стабильный? (да хоть дома, даже если у вас всего 2-3 ПК/ноутбука, подключенных к одному роутеру)

Вот тут как раз может помочь свой локальный чат, — который будет работать вне зависимости от сбоев Интернета (кстати, в компаниях и организациях — вещь вообще не заменимая!). Да и скорость обмена файлами в таком чате будет явно повыше, чем в популярных мессенджерах! Чем не выход?!

Собственно, в этой заметке хочу привести пример подобного локального чата (который очень быстро развертывается и настраивается // причем без всяких специальных знаний 🙂).

Итак…

*

Содержание статьи

    ускорение ПК

  • 1 Простейший пример установки и настройки чата
    • 1.1 ШАГ 1: выбор ПО
    • 1.2 ШАГ 2: установка сервера (выбор «главного» ПК)
    • 1.3 ШАГ 3: установка клиента и подкл. к серверу (вход в чат)
    • 1.4 ШАГ 4: начинаем обмен сообщениями
    • 1.5 ШАГ 5: что есть еще интересного
    • 1.6 Выводы
  •  → Задать вопрос | дополнить 

Простейший пример установки и настройки чата

ШАГ 1: выбор ПО

В своей заметке я решил остановиться на продукте «MyChat» — это клиент-серверный чат, подходящий для передачи сообщений и обмена файлами как дома, так и в офисе или компании (офиц. сайт разработчиков: 📌https://nsoft-s.com/).

img-MyChat-logo.png

Свой пример ниже я подробно разберу по шагам именно в «MyChat».

Чем он интересен:

  1. во-первых, он очень легко и просто запускается, и настраивается. Даже если вы совсем не разбираетесь — вам потребуется 5-10 мин. времени, чтобы выполнить мой пример; 🙂
  2. во-вторых, «MyChat» поддерживает как групповые чаты (может быть несколько комнат), так и личные сообщения;
  3. в-третьих, можно обмениваться картинками, файлами;
  4. в-четвертых, сервер работает только в вашей сети, переписка не попадает в Интернет (а значит это безопасно!);
  5. в-пятых, «MyChat» предоставляет систему уведомлений, которая позволяет быть в курсе всех новостей и обновлений в команде;
  6. в-шестых, есть возможность совершения аудио-звонков.

В общем-то, весьма добротный набор для самых разных задач! 👌

*

ШАГ 2: установка сервера (выбор «главного» ПК)

Для начала нужно выбрать тот компьютер, который будет основным (обычно в роли сервера выбирают тот ПК, который работает дольше, чем все остальные).

На этот один ПК нужно установить «MyChat сервер» (это обычный EXE-файл, устанавливается стандартно) — приложение можно загрузить с офиц. сайта: 📌https://nsoft-s.com/downloadmychat.html

img-Skachat-PO-----skrin-s-sayta-razrabotchika.png

Скачать ПО — скрин с сайта разработчика

Кстати, при установке «MyChat сервер» нужно будет указать свой e-mail и название компании (может быть любым), а также настроить авто-запуск (см. пару скринов ниже). 👇

img-Tipovaya-ustanovka-servera.png

Типовая установка сервера

После того, как приложение будет установлено и сервер будет запущен — вы заметите, что у вас в трее (в нижнем углу, рядом с часами) появиться соотв. значок в виде ПК — нажав по нему появится окно (как у меня на скрине ниже). Если всё так — значит чат запущен! 👌

img-Server-rabotaet.png

Сервер работает!

Кстати, в верхней части окна «MyChat Server» обратите внимание на его IP-адрес (в моем случае он 192.168.1.2). Он будет нужен нам для дальнейшей настройки.

📌 В помощь!

Как узнать IP-адрес компьютера, ноутбука (а также, что такое локальный и внешний IP, динамический и статический — в чем разница) — https://ocomp.info/kak-uznat-ip-adres.html

img-192.168.1.2-IP-kompyutera-na-kotorom-ustanovlen-server.png

192.168.1.2 — IP компьютера, на котором установлен сервер

Кстати, IP-адрес компьютера также можно посмотреть в 📌настройках вашего роутера/маршрутизатора.

img-Nastroyki-routera-smotrim-svoy-IP.png

Настройки роутера — смотрим свой IP

*

ШАГ 3: установка клиента и подкл. к серверу (вход в чат)

Теперь нужно на все ПК/ноутбуки (на которых должен использоваться чат) — установить клиент «MyChat». Загрузить его можно также с офиц. сайта: 📌https://nsoft-s.com/downloadmychat.html

👉👉 Обратите внимание!

В заметке я рассмотрю стандартную установку и настройку клиента. Но есть и более простой вариант: он разобран на одной из страничек офиц. сайта.

img-Zagruzhaem-klient-s-ofits.-sayta.png

Загружаем клиент с офиц. сайта

Установка клиента проходит стандартно, в особых комментариях не нуждается… 👇

img-Ustanovka-klienta-standartno.png

Установка клиента, стандартно

При первом запуске клиента «MyChat» — нужно будет «кое-что» настроить. На этом остановлюсь…

Окно приветствия можно сразу же пропустить, нажав на «Далее». 👇

img-Master-pervogo-zapuska-MyChat.png

Мастер первого запуска MyChat

Далее лучше сразу выбрать пункт «Я знаю адрес сервера и укажу его вручную» (авто-поиск срабатывает далеко не всегда, в моем случае ни разу).

img-Avtomaticheski-nayti-moy-server.png

Автоматически найти мой сервер

Далее указываем тот IP-адрес компьютера, на котором у нас был запущен сервер (см. предыдущий шаг; в моем случае — это IP 192.168.1.2), и нажимаем кнопку «Проверить».  Если указали адрес правильно — загорится зеленая галочка… Можно нажать «Далее». 👇

img-Tot-adres-kotrryiy-myi-posmotreli-pri-ustanovke-servera.png

Нужно указать тот IP-адрес, который мы посмотрели при установке сервера

Рекомендую сразу же создать новую учетную запись: потребуется указать имя пользователя и пароль.

img-Sozdat-novogo-polzovatelya.png

Создать нового пользователя

img-Registratsiya-novogo-polzovatelya-MyChat.png

Регистрация нового пользователя, MyChat

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

img-CHat-voyti-v-konferentsiyu.png

Чат — войти в конференцию

*

ШАГ 4: начинаем обмен сообщениями

Для начала общения следует войти в нужную конференцию (в моем примере она называется «По работе»). Далее справа вы увидите список участников, по центру — сам чат, и окно отправки текстовых сообщений. Достаточно набрать что-нибудь и нажать на кнопку отправки, она с «Самолетиком» img-knopka-otpravki.png.

img-Okno-chata-3-uchastnika.png

Окно чата — 3 участника

В окне чата появится само сообщение и имя участника. 👇

img-Otpravlyayutsya-i-kartinki-i-tekst.png

Отправляются и картинки, и текст

А в нижнем углу экрана, в трее, всплывет небольшое окно-уведомление о новом сообщении в чате. Удобно! Благодаря таким уведомлениям все участники быстро узнают о новой вводной… 🙂

img-Kak-vyiglyadyat-uvedomleniya-pri-poluchenii-soobshheniya.png

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

*

ШАГ 5: что есть еще интересного

1) Приватные сообщения

Чтобы отправить кому-нибудь из участников чата личное сообщение (которое будет видно только ему!) — достаточно нажать по нику правой кнопкой мыши, и в меню выбрать «Открыть приват». 👇

img-Kak-otpravit-lichnoe-privatnoe-soobshhenie.png

Как отправить личное приватное сообщение

Далее можно спокойно общаться с пользователем в личном диалоге…

img-CHat-na-dvoih-MyChat.png

Чат на двоих — MyChat

2) Отправка файлов, ссылок и картинок

Чтобы отправить другому пользователю какой-нибудь файл — достаточно в меню чата нажать по кнопке со скрепкой «Вставить» (см. пример ниже). 👇 Всё просто!

img-Vstavit-znachok-so-skrepkoy.png

Вставить — значок со скрепкой

3) Доп. инструменты

Обратите внимание, что во вкладке «Инструменты» есть возможность пригласить др. пользователя (по почте), просмотреть историю сообщений, открыть вкладку форума и пр.

В общем-то, если вам нужен простой чат — то на это можно не обращать внимание… 🙂

img-Instrumentyi.png

Инструменты

4) Свой персональный профиль лучше заполнить чуть подробнее: ФИО, почта, какие-то интересы (особенно, если у вас в сети есть 2-3 Александра, например 🙂, а то все будут пытаться «кто есть кто»).

img-Moy-profil.png

Мой профиль

5) Кстати, у клиента «MyChat» достаточно много настроек: звуки, события, горячие клавиши, интерфейс и т.д. См. скрин ниже. 👇

img-Nastroyki-programmyi-MyChat.png

Настройки программы MyChat

6) Кстати, есть также приложение для смартфона (одноименное, «MyChat»). Загрузить и установить можно с Play Market. Большая часть функций, что есть в клиенте для Windows — есть и тут…

Ссылка на Play Market: https://play.google.com/store/apps/details?id=com.nss.mychat&hl=ru&gl=US&pli=1

img-Skrin-MyChat-ot-razrab.-na-Android.png

Скрин MyChat от разраб. на Android

*

Выводы

Если говорить в целом — то приложение свою задачу решает, если не на 5, то на 4 уж точно! Чат работает стабильно, уведомления приходят, файлы пересылаются, группы создаются, вроде бы ничего нигде не виснет и не тормозит. И главное, «это добро» можно быстро установить и настроить! 👌

Также понравилось, что у «MyChat» есть рус. поддержка, много инструкций на сайте разработчика. Думаю, что за небольшую плату — вам даже смогут настроить чат под любые тонкости вашей сети…

С другой стороны: есть, конечно, и несколько минусов (но они не критичны, по крайней мере, если вы ищите именно чат/мессенджер, а не комбайн).

Минусы:

  1. приложение бесплатное только при усл. что у вас в сети <20 пользователей (для дома этого хватит, а вот для офиса или организации — уже нет. Впрочем, цены весьма доступны…);
  2. простенький дизайн (+ не все элементы поддерживают масштабирование Windows). Но в принципе, пользоваться это не мешает 🙂;
  3. когда все пользователи покидают комнату (конференцию) — она автоматически «исчезает» из меню клиента. Эта штука (как по мне) не очень удобна. Чтобы это «устранить» — нужно создать «авто-возобновляемую» конференцию (правда, до этого пришлось «додуматься», сходу это не так очевидно);
  4. подозреваю, что на некоторых ПК могут быть проблемы с аудио-звонками (в настройках программы нужно правильно задать параметры звука: выбрать микрофон, наушники и пр. Мне это было без надобности, поэтому на этом я не зацикливался… 🙂).

*

Иные дополнения по теме — приветствуются в комментариях ниже.

За сим откланиваюсь, удачи!

👋

donate

dzen-ya

Полезный софт:

  • видеомонтаж
  • Видео-Монтаж
  • Отличное ПО для создания своих первых видеороликов (все действия идут по шагам!).
    Видео сделает даже новичок!

  • утилита для оптимизации
  • Ускоритель компьютера
  • Программа для очистки Windows от «мусора» (удаляет временные файлы, ускоряет систему, оптимизирует реестр).

Комната для чата — это интерфейс, который позволяет двум или более людям общаться в чате и отправлять сообщения всем, кто находится в комнате. Сегодня мы поговорим про то, как создать простое чат-приложение на 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».

Программирование, C++, Разработка систем передачи данных


Рекомендация: подборка платных и бесплатных курсов Python — https://katalog-kursov.ru/

Несколько месяцев назад понадобилось разработать чат для локальной сети одного офиса, а также выступить с этой программой на научной конференции. Делать его я решил в среде разработки Builder C++ 2006. При написании статьи у меня возникла одна самая главная проблема — полное отсутствие опыта в работе с сетями в билдере, поэтому статью пишу для таких же «программистов», как я. Отмечу сразу, в интернете найдется множество программ, которые, несомненно, будут лучше моей, но задание было не найти программу, а разработать. Статья получится большая, поэтому разделю ее на 2 части — серверную и клиентскую.

Первым делом надо было решить, что это будет за приложение.

Идея

Первое, что пришло в голову — открывать сервер на каждом компьютере, объединяя все компьютеры в кольцо. Сообщение передавать тому клиенту, к которому мы сейчас подключены. Там проверять, нам ли это сообщение, или нет; если нет — то отправлять дальше до тех пор, пока оно не достигнет адресата. Идея не понравилась, главным образом из-за того, что не хотелось, чтобы сообщение проходило через промежуточных пользователей.

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

Конечно же, должен присутствовать графический интерфейс, причем такой, чтобы работал по принципу «plug and play» — запустил программу и сразу можно приниматься за переписку. Поэтому в окне программы будет минимум компонентов, даже не будет меню бара.

Как будем пересыпать сообщения? Используя сокеты, а именно стандартные компоненты билдера ClientSocket и ServerSocket, которые будут использоваться в программах клиента и сервера соответственно.

Реализация

Сервер

Программа-сервер рассчитана на одноразовое использование. Т.е. при выходе из нее не сохраняется никакая информация о клиентах, в самой же программе все хранится в массиве. Вообще, сам интерфейс сокетов достаточно интересен. Для того, чтобы отправить сообщение клиенту, используется команда SendText, принимающая строку сообщения типа AnsiString (где-то я вычитал про длину в 4,3 миллиарда символов, что само собой впечатляет), но чтобы отправить его именно тому, кому нужно, следует указывать номер клиента, а не, например, его IP-адрес. При этом номера клиентам выдаются в том порядке, в каком они подключились. В .h файле объявлен массив m типа AnsiString, состоящий из 100 элементов. Честно говоря, я не проверил максимально возможное количество подключений к серверу, поэтому будем считать, что оно ограничивается только величиной этого массива. При подключении клиента первым делом отправляется его имя на сервер. Оно вносится в первую свободную ячейку массива, при этом номер элемента и будет являться номером клиента, по которому мы будем отправлять сообщения. Чтобы найти первую пустую ячейку, я написал функцию analog(), которая просто перебирает массив и возвращает номер пустой ячейки.

int TFormMain::analog()
{
int a;
for(int i=0;i<mass;i++)
{
if(m[i]=="")
{
a=i;
break;
}
}
return a;
}

В событии OnConnect сервера запускается таймер. Почему-то код исполняемый таймером у меня не получилось выполнять сразу в событии, поэтому таймер выполняет его и сразу же отключается. По таймеру сервер с помощью цикла отправляет всем клиентам сообщение, в котором в одной строке содержатся все имена подключенных в данный момент клиентов. Зачем это сделано — я расскажу в описании клиента. Список же клиентов формирует функция online() «склеивающая» имена клиентов из массива.

Подключение клиентов


void __fastcall TFormMain::ServerSocketClientConnect(TObject *Sender,
      TCustomWinSocket *Socket)
{
Timer1->Enabled=true;
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::Timer1Timer(TObject *Sender)
{

if(ServerSocket->Socket->ActiveConnections!=0)
for(int i=0;i<ServerSocket->Socket->ActiveConnections;i++)
ServerSocket->Socket->Connections[i]->SendText("8714"+online());
Timer1->Enabled=false;
}
//---------------------------------------------------------------------------

AnsiString TFormMain::online()
{
char str[500]="";
for(int i=0;i<analog();i++)
{
strcat(str,m[i].c_str());
strcat(str,",");
}
return str;
}

Однако, все самое интересное происходит в событии сервера OnRead. Структура каждого сообщения, посылаемого как клиентом, так и сервером, обязательно содержит в начале 4 цифры. Это случайная комбинация цифр, придуманная мной для того, чтобы сервер и клиент могли различать сообщения, необходимые для «авторизации», или же сообщения, содержащие в себе текст для пересылки. Всего клиент может посылать серверу 4 типа сообщений. Сообщения с кодом 6141 посылаются серверу при первом подключении, они также сообщают серверу имя нового клиента, а сервер вносит его в массив и выводит в Memo (декоративном элементе, созданном просто чтобы знать, кто в данный момент подключен). Сообщение с кодами 5280 и 5487 потеряли свою актуальность, но почему то не были убраны мной из кода сервера. Сообщения с кодом 3988 самые важные. Это и есть сообщения содержащие в себе всю информацию для обмена сообщениями между пользователями. Структура такого сообщения:

3988<Имя отправителя>%<Имя получателя>:<Текст сообщения>.

Вообще, из каждого полученного сообщения сервер первым делом выделяет код методом SubString, от этом в дальнейшем зависят его дальнейшие действия. Из этого же сообщения сервер также выделяет меня отправителя и получателя, а также текст сообщения. Затем формируется сообщение вида 7788<Имя отправителя>:<Текст сообщения>. Оно отправляется клиенту-получателю. Как, если известно только его номер а не имя? Для этого написана функция numer(AnsiString), принимающая имя, перебирающая массив и возвращающая номер ячейки в котором это имя находится.

Обработка входящих сообщений

void __fastcall TFormMain::ServerSocketClientRead(TObject *Sender,
      TCustomWinSocket *Socket)
{
message=Socket->ReceiveText();
time=Now().CurrentDateTime();
if(message.SubString(1,4).AnsiCompare("6141")==0)
{
m[analog()]=message.SubString(5,message.Length());
ListBox1->Clear();
for(int i=0;i<ServerSocket->Socket->ActiveConnections;i++)
{
ListBox1->Items->Add(m[i]);
}
}
else if(message.SubString(1,4).AnsiCompare("5487")==0)
{
for(int i=0;i<ServerSocket->Socket->ActiveConnections;i++)
ServerSocket->Socket->Connections[i]->SendText("8714"+online());
}
else if(message.SubString(1,4).AnsiCompare("3988")==0)
{
nametowho=message.SubString(message.AnsiPos('Й')+1,message.AnsiPos(':')-message.AnsiPos('Й')-1);
name=message.SubString(5,message.AnsiPos('Й')-5);
if(nametowho.IsEmpty()==false && (message.SubString(message.AnsiPos(':')+1,message.Length()).IsEmpty())==false)
{
ServerSocket->Socket->Connections[numer(nametowho)]->SendText("7788"+name+":"+message.SubString(message.AnsiPos(':')+1,message.Length()));
ofstream fout("chat.txt",ios::app);
fout<<time.c_str()<<"   "<<message.c_str()<<endl;
fout.close();
}
}
else if(message.SubString(1,4).AnsiCompare("5280")==0)
{

ServerSocket->Socket->Connections[numer(message.SubString(message.Pos('#')+1,message.Pos('%')-message.Pos('#')-1))]->SendText(
"6734"+message.SubString(message.Pos('%')+1,message.Length()-message.Pos('%')));
}
}

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

void __fastcall TFormMain::ServerSocketClientDisconnect(TObject *Sender,
      TCustomWinSocket *Socket)
{
if(ServerSocket->Socket->ActiveConnections!=0)
{
for(int i=0;i<mass;i++)
{
m[i]="";
}
TestNames();

Timer1->Enabled=true;

}
}

Графическая часть

Внешний вид окна сервера:

Изначально у меня не было в планах выводить какую либо информацию в окне сервера, но в итоге решил выводить самое важное: IP-адрес сервера, количество активных подключений, порт сервера, имя компьютера ка котором он запущен (почему-то всегда «1», исправить я это пока не смог), и список имен подключенных клиентов. Сервер сворачивается в область уведомлений. Реализовано это несколькими функциями, подробно разбирать я их не буду. Также на сервере полностью отключен отлов ошибок (которых вообще за 2 недели непрерывной работы не возникало, но мало ли, все таки полностью парализуется работа сети).

Графическая часть


void __fastcall TFormMain::DrawItem(TMessage& Msg)
{
     IconDrawItem((LPDRAWITEMSTRUCT)Msg.LParam);
     TForm::Dispatch(&Msg);
}
//---------------------------------------------------------------------------
void __fastcall TFormMain::MyNotify(TMessage& Msg)
{
    POINT MousePos;

    switch(Msg.LParam)
    {
        case WM_RBUTTONUP:
            if (GetCursorPos(&MousePos))
            {
                PopupMenu1->PopupComponent = FormMain;
                SetForegroundWindow(Handle);
                PopupMenu1->Popup(MousePos.x, MousePos.y);
            }
            else
                Show();
            break;
        case WM_LBUTTONDBLCLK:
        Show();

        break;
        default:
            break;
    }
    TForm::Dispatch(&Msg);
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------
bool __fastcall TFormMain::TrayMessage(DWORD dwMessage)
{
   NOTIFYICONDATA tnd;
   PSTR pszTip;

   pszTip = TipText();

   tnd.cbSize          = sizeof(NOTIFYICONDATA);
   tnd.hWnd            = Handle;
   tnd.uID             = IDC_MYICON;
   tnd.uFlags          = NIF_MESSAGE | NIF_ICON | NIF_TIP;
   tnd.uCallbackMessage	= MYWM_NOTIFY;

   if (dwMessage == NIM_MODIFY)
    {
        tnd.hIcon		= (HICON)IconHandle();
        if (pszTip)
           lstrcpyn(tnd.szTip, pszTip, sizeof(tnd.szTip));
	    else
        tnd.szTip[0] = '';
    }
   else
    {
        tnd.hIcon = NULL;
        tnd.szTip[0] = '';
    }

   return (Shell_NotifyIcon(dwMessage, &tnd));
}
//---------------------------------------------------------------------------
HICON __fastcall TFormMain::IconHandle(void)
{
return (Image2->Picture->Icon->Handle);
}

//---------------------------------------------------------------------------
PSTR __fastcall TFormMain::TipText(void)
{
        return ("Office Chat");

}
//---------------------------------------------------------------------------
LRESULT IconDrawItem(LPDRAWITEMSTRUCT lpdi)
{
return 0;
}
//---------------------------------------------------------------------------

//---------------------------------------------------------------------------


void __fastcall TFormMain::FormDestroy(TObject *Sender)
{
	TrayMessage(NIM_DELETE);
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::N1Click(TObject *Sender)
{
Show();
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::N2Click(TObject *Sender)
{
Application->Terminate();
}
//---------------------------------------------------------------------------

void __fastcall TFormMain::FormCloseQuery(TObject *Sender, bool &CanClose)
{
CanClose=false;
FormMain->Hide();
}
//---------------------------------------------------------------------------



void __fastcall TFormMain::FormCreate(TObject *Sender)
{
unsigned long Size = 256;
   char *Buffer = new char[Size];

Label5->Caption=GetUserName(Buffer, &Size);
delete [] Buffer;
}
//---------------------------------------------------------------------------

В заключение хочу сказать, что получилось достаточно примитивно написанный, однако стабильно работающий сервер, позволяющий одновременно переписываться 20 людям (больше я просто не проверял). Все исходники, exe-файлы и полный разбор кода клиента будут во второй статье.

Спасибо за внимание.

На очередной практике по Java, не предвещающей ничего необычного, преподаватель ворвался в аудиторию и с порога заявил: «Сегодня мы с вами познакомимся с сокетами и напишем прототип собственного чата».

«А вечер-то перестаёт быть томным» — подумал я и не ошибся. Чёрт возьми, это какая-то магия, вертелось в моей голове по пути домой. Тут надо отметить, что я не только бедный студент, но ещё и преподаватель в кружке программирования, поэтому после столкновения с такой интересной темой во мне затаилось жгучее желание поделиться ею со своими ребятами.

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

Для начала определимся, что это за зверь такой — Сокет?

Представим себе работу ресторана быстрого питания, пусть будет Burger Queen, так вот работник этого заведения и будет сокетом, то есть программой, которая отвечает за обмен данными(бургерами) между клиентом и заведением. Как сокет узнает кому отдать заветное хрючево(данные)? У него есть чек с номером заказа! Вот и у сокета есть порт к которому он привязан, то есть и в Burger Queen и в сетевых технологиях есть приложение, тот самый сокет, который отвечает за работу с определенным портом, так же как и работник, который отвечает за обработку конкретного номера заказа, ведь и к конкретному компьютеру и к конкретной забегаловке одновременно конектятся разные клиенты, и всем им нужны разные данные.

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

Пишем сервер

Фуууух, самая потная часть статьи позади, расчехляем питонов! Первым делом напишем программу серверного сокета, работника Burger Queen, который принимает заказы.

import socket # Подключаем необходимую библиотеку, она встроена


new_socket = socket.socket() #Создаём объект сокета
new_socket.bind(('127.0.0.1', 5000)) # Привязываем наш объект к ip и порту
new_socket.listen(2) # Указываем нашему сокету, что он будет слушать 2 других

print("Server is up now!")

Каюсь, в объяснении сокетов я ни слова не сказал про IP, но тут тоже всё просто, наш работник(сокет) работает в конкретном ресторане по конкретному адресу, то есть, подытоживая можно сказать, в конкретном ресторане по определенному IP адресу работает много работников, сокетов, каждый из которых обслуживает свой порт, номер заказа.

Почему я выбрал 5000-ный порт, потому что методом проб и ошибок только с ним запустилась моя программа, очевидно другие — заняты, а IP 127.0.0.1 — стандартный локальный адрес любого компьютера(совсем любого).

Ползём дальше, получаем коннекты.

conn1, add1 = new_socket.accept() 
# сохраняем объект сокета нашего клиента и его адрес
print("First client is connected!")

conn2, add2 = new_socket.accept()
#аналогично со вторым клиентом
print("Second client is connected!")

Далее создадим две функции, которые принимают данные от одного клиента и отправляют их другому.

def acceptor1():
  # Запустим бесконечный цикл, мы хотим общаться постоянно!
    while True:
#Получим 1024 байта от первого клиента
        a = conn1.recv(1024)
 #Перешлём их второму
        conn2.send(a)

def acceptor2():
# А здесь мы получим 1024 байта от второго клиента и перешлём первому.
    while True:
        b = conn2.recv(1024)
        conn1.send(b)

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

from threading import Thread # Подключили класс потока

#Создаём потоки, в качестве именнованного аргумента передаем нашу ф-ю
tread1 = Thread(target=acceptor1) 
tread2 = Thread(target=acceptor2)

#Запускаем потоки
tread1.start()
tread2.start()

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

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

curl 127.0.0.1:5000

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

Итак, приведём полный код сервера.

import socket 
from threading import Thread

new_socket = socket.socket()
new_socket.bind(('127.0.0.1', 5000))

new_socket.listen(2)

print("Server is up now!")

conn1, add1 = new_socket.accept()
print("First client is connected!")

conn2, add2 = new_socket.accept()
print("Second client is connected!")

def acceptor1():
    while True:
        a = conn1.recv(1024)
        conn2.send(a)

def acceptor2():
    while True:
        b = conn2.recv(1024)
        conn1.send(b)

tread1 = Thread(target=acceptor1)
tread2 = Thread(target=acceptor2)

tread1.start()
tread2.start()

Пишем клиента

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

#Подключаем зависимости
import socket
from threading import Thread
#Создаём новый сокет
client_socket = socket.socket()
#Заставляем его подключиться к серверному сокету
client_socket.connect(("127.0.0.1", 5000))
#Создаём ф-и отправки и получения сообщений
def sender():
    while True:
      #Читаем строку с клавиатуры
        a = input()
        #Отправляем её, предварительно закодировав
        client_socket.send(a.encode("utf-8"))
def reciver():
    while True:
      #Получаем строку от сервера
        b = client_socket.recv(1024)
       #Печатаем, предварительно раскодировав
        print(b.decode("utf-8"))
#Создаём по отдельному потоку для каждой функции
tread1 = Thread(target=sender)
tread2 = Thread(target=reciver)
#Потоки запушены, клиент готов получать и отправлять сообщения
tread1.start()
tread2.start()

Вот и всё! Создаем двух клиентов и переписываемся без мам пап, ватсапов и телеграмов, как говорится, мы и сами с усами.

P. S.

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

Исходник сетевого чата

Программа для чата по локальной сети на С++С созданием локальной (домашней сети) открываются новые возможности общения сидя за компьютером. Несколько человек с помощью программы чата могут обмениваться сообщениями по сети.

Исходный код такого сетевого приложения прилагается ниже. Сетевая программа написана на языке С++ на базе библиотеки MFC. Для визуализации построения пользовательского интерфейса выбран тип приложения на основе диалоговых окон.

Количество подключаемых к чату клиентов не ограничивается. Для удобного хранения сокетов подключенных клиентов используется шаблонный класс стандартной библиотеки С++ class vector. Класс vector представляет собой динамический массив и поддерживает быстрый доступ к любому элементу.

Сокет для соединения по сети

Работу сетевого приложения чата обеспечивают сокеты. Сетевое соединение устанавливается посредством объектов класса CSock. Класс CSock произведен от высокоуровневого класса асинхронных сокетов CAsyncSocket. Асинхронная работа сокетов исключает блокировку пользовательского интерфейса во время установления соединения и отправки-получения сообщений по сети. Класс CAsyncSocket входит в состав богатейшей библиотеки MFC, облегчающей рутинный труд С++ программистов. CAsyncSocket — интуитивно понятная реализация в виде класса для низкоуровневого интерфейса Windows Sockets API. Дочерний класс CSock инкапсулирует в себе достоинства асинхронной работы с сокетами и небольшим дополнительным программным кодом обеспечивает полноценную сетевую работу чата.

Листинг реализации класса CSock:

// Событие подключения на стороне клиентского приложения.
void CSock::OnConnect(int nErrorCode)
{
    // Данные в родительское окно для индикации процесса соединения.
    CChatCppDlg* pDlg = (CChatCppDlg*)m_pParent;
    nErrorCode == 0 ? pDlg->OnConnect(FALSE) : pDlg->OnConnect(TRUE);
	
    CAsyncSocket::OnConnect(nErrorCode);
}

// Событие отключения от сети
void CSock::OnClose(int nErrorCode)
{
    Beep(2000, 300);
	
    CAsyncSocket::OnClose(nErrorCode);
}

// Событие получения сообщений.
void CSock::OnReceive(int nErrorCode)
{
    // Данные в родительское окно для визуальности приема сообщений.
    CChatCppDlg* pDlg = (CChatCppDlg*)m_pParent;
    pDlg->OnReceive();
	
    CAsyncSocket::OnReceive(nErrorCode);
}

// Запрос на подключение, направляемый клиентом серверу.
// Происходит на стороне серверного приложения.
void CSock::OnAccept(int nErrorCode)
{
    // Данные в родительское окно для индикации процесса соединения.
    CChatCppDlg* pDlg = (CChatCppDlg*)m_pParent;
    pDlg->OnAccept();

    CAsyncSocket::OnAccept(nErrorCode);
}

Подключение клиентов

При установлении сетевого соединения для каждого клиента создаётся отдельный сокет. Сокеты клиентов хранятся в динамическом массиве std::vector m_vecSockets. В момент акцептирования (принятия) клиентов в чате рассылается информация о количестве подключенных пользователей. Успешное подключение к сети индицирует заголовок приложения.

// Принимаем запросы на подключения
void CChatCppDlg::OnAccept(void)
{
    CSock* pSock = new CSock;
    pSock->m_pParent = this;

    // Если все в порядке добавим рабочий сокет в список 
    // подключенных рабочих сокетов.
    if(m_mainSocket.Accept(*pSock) == TRUE)
    {
        m_vecSockets.push_back(pSock);
        m_ButtonSend.EnableWindow(TRUE);
        SendCountPeople();

        SetWindowText("Сеть работает!");
    }
    else 
    {
        delete pSock;
    }
}

Сортировка сетевых сообщений

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

Структура для передачи сетевых сообщений:

struct SENDBUFFER
{
    SENDBUFFER() 
    {
        typemessage = 0; 
        countpeople = 0;
        stopchat = false;
        ZeroMemory(name, sizeof(TCHAR)*14); 
        ZeroMemory(buffer, sizeof(TCHAR)*202);
    }

    int typemessage;
    int countpeople;
    bool stopchat;
    TCHAR name[14];
    TCHAR buffer[202];
};

Сортировку получаемых сообщений удобно производить с помощью оператора swicth:

m_mainSocket.Receive(&sb, sizeof(SENDBUFFER));

switch(sb.typemessage)
{
	case m_TypeMessage::tmCountPeople:
		{
			...
		}
		break;
	case m_TypeMessage::tmChat:
		{
			...
		}
		break;
	case m_TypeMessage::tmDisconnect:
		{
			...
		}
		break;
	default:
		AfxMessageBox("Неизвестное сообщение!");
		break;
}

Макросы WINVER и _WIN32_WINNT для разных версий

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

Для работы в ранних версиях операционной системы Windows требуется закомментировать отмеченные в листинге строки кода.

// Макросы для работы в ранних версиях операционной системы Windows
// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef WINVER				// Allow use of features specific to Windows 95 and Windows NT 4 or later.
#define WINVER 0x0400		// Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINNT		// Allow use of features specific to Windows NT 4 or later.
#define _WIN32_WINNT 0x0400		// Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif


/*
// Макросы для новых версий Windows
#define WINVER 0x0A00
#define _WIN32_WINNT 0x0A00
*/

Для работы в новых версиях Windows требуется закомментировать макросы WINVER и _WIN32_WINNT для ранних версий.

/*
// Макросы для работы в ранних версиях операционной системы Windows
// Modify the following defines if you have to target a platform prior to the ones specified below.
// Refer to MSDN for the latest info on corresponding values for different platforms.
#ifndef WINVER				// Allow use of features specific to Windows 95 and Windows NT 4 or later.
#define WINVER 0x0400		// Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif

#ifndef _WIN32_WINNT		// Allow use of features specific to Windows NT 4 or later.
#define _WIN32_WINNT 0x0400		// Change this to the appropriate value to target Windows 98 and Windows 2000 or later.
#endif
*/


// Макросы для новых версий Windows
#define WINVER 0x0A00
#define _WIN32_WINNT 0x0A00

Прикрепленный файл исходника чата по локальной сети

Доработав исходный код «под себя» можно изготовить полнофункциональное приложение для чата по сети, с возможностью отправки файлов, с шифрованием сообщений и т.д. Работа с MFC и языком программирования С++ в Visual Studio 2019 стала гораздо комфортней, чем в предыдущих версиях. Приятно отметить, что библиотека MFC не забыта и активно развивается.

Среда программирования MS Visual Studio 2019, обязательна установка MFC библиотеки версии 142 и выше.

Скачать исходник

  • chatcpp_vs16.zip
  • Размер: 62 Кбайт
  • Загрузки: 12351

Понравилась статья? Поделить с друзьями:
  • Как написать чат бота для telegram на javascript
  • Как написать чат бот телеграм
  • Как написать чат бот на нейросети
  • Как написать часы на питоне
  • Как написать часы на python