Пишем мессенджер на C#. Часть 1. Вёрстка
Клиент-серверная разработка — одна из самых востребованных отраслей программирования. Зная её азы, можно создавать как мессенджеры, так и онлайн-игры.
В этой серии статей мы напишем клиент-серверное приложение на C# — простейший мессенджер. Серия состоит из трёх частей:
- Вёрстка приложения — мы создадим графический интерфейс на C# и XAML для Windows.
- Создание WebAPI на ASP.NET — составим базу данных и разработаем серверную часть приложения.
- Объединение клиента и сервера — напишем запросы к серверу и позаботимся, чтобы всё работало как надо.
Язык C# пригодится в разработке чего угодно. Возможности WPF (система создания графических интерфейсов) позволяют создавать красивые и функциональные приложения для Windows, а ASP.NET — мощные серверные приложения.
Я постараюсь объяснить подробно, но охватить всё невозможно, поэтому вам нужно знать основы C#, ООП, ASP.NET, WPF и работы в Visual Studio.
Вот несколько статей, с которыми стоит ознакомиться, если вы чего-то не знаете:
Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Структура приложения
Мы не рассматриваем регистрацию, поиск контактов, хранение сообщений, продвинутый дизайн. Вместо этого вы узнаете азы создания клиент-серверных приложений и сможете сделать всё самостоятельно.
Исходный код мессенджера вы найдете на GitHub.
Приложение мы поделим на экраны:
- экран авторизации;
- экран с контактами;
- экран с чатом.
Экран — это элемент Border, который по умолчанию скрыт от пользователя. Виден будет только активный экран.
На экране авторизации пользователь сможет ввести логин и пароль, чтобы войти. Если он ввёл верные данные, то перейдёт на экран с контактами, иначе — увидит сообщение об ошибке.
На экране с контактами видны имена других пользователей, с которыми ведётся переписка. Чат открывается при нажатии на имя другого пользователя.
На экране с чатом видна переписка с одним конкретным контактом. Пользователь может написать и отправить новое сообщение или вернуться к экрану с контактами.
Верстаем экран авторизации
Начнём с определения стилей. В них минимально обозначим, как должны выглядеть элементы, и сразу укажем, что экраны по умолчанию должны быть скрыты:
Теперь сверстаем сам экран авторизации — он должен быть видимым:
Создание чата
Вложения
Вариант №2.rar (59.5 Кб, 36 просмотров) | |
Вариант №1.zip (90.1 Кб, 48 просмотров) |
Создание p2p чата для частной локальной сети
Добрый день. Задача: Существует необходимость создать p2p час для нескольких клиентов внутри.
Создание не многопользовательского чата
Нужно написать чат на С#. (Не многопользовательский), а просто между двумя пользователями. Один из.
Cокеты (создание чата)
Упражняюсь с сокетами, конечная цель, создание многопользовательского чата. Пока работает для.
Написание приложения под android: создание чата
Добрый день. Началась учеба, пришло время задуматься о теме для курсовой работы. Есть идея.
Решение
Нет сейчас возможности проверить((( а если в Варианте №1 указать, что broadcast = textBox(конкретный IP одного клиента)
Добавлено через 6 часов 53 минуты
Попробовал свою мысель, не получилось..
Добавлено через 5 часов 51 минуту
Вот часть кода.
Здесь надо, указать,что broadcast =textBox3.Text.
Добавлено через 20 часов 14 минут
Вот сторока из-за которой отпавляются сообщения всем пользователям
Заказываю контрольные, курсовые, дипломные и любые другие студенческие работы здесь или здесь.
Создание чата между двумя компьютерами через интернет
Салют! Заинтересовала меня работа с сокетами. Поэтому решил разобраться на примере создания чата.
Модерирование чата
Доброго времени суток. Меня интересует один вопрос. Вот у меня есть клиент и есть сервер. Как мне.
Интерфейс чата
Привет программисты, подскажите какой контрол использовать и как вообще реализовать такой интерфейс.
Парсер гг чата
Есть такой портал стримеров гутгейм. Есть там чатики на каналах стримера. Подскажите пожалуйста в.
Интерфейс чата
Примерно такой, не точь в точь естественно.
Комментарий модератора | ||
|
Модерирование чата
Доброго времени суток. Меня интересует один вопрос. Вот у меня есть клиент и есть сервер. Как мне.
Создание чата
В инете много написано о создании чата, но мне это не подходит. Вот в примере №1. Сообщение.
Личка для чата C#
Добрый вечер. у меня есть проект чата . мне нужно добавить в него элемент listbox чтобы там.
Вывод пользователей чата в список
Доброго времени суток! Пишу приложение на подобии аськи и необходимо организовать такой же.
Как сделать простой чат? С# Windows Forms
Перенос чат-бота с консоли в Windows Forms
Есть чат-бот, который работает в консоли и запоминает ответы в txt файл. Нужно перенести его в.
Как сделать из TextBoxa консоль на Windows Forms?
Я хочу сделать консоль на подобии как в CS
Как сделать сокет-сервер в Windows Forms?
Я сейчас имею приемлемый клиент, с которого передаю данные на сервер. Сервер у меня консольный, в.
Как сделать такую справочную систему в Windows forms C#?
Скрин во вложении Нужно написать справку к приложению. Хочется сделать что-похожее, но как это.
Заказываю контрольные, курсовые, дипломные и любые другие студенческие работы здесь или здесь.
Как сделать и установить свой скин System.Windows.Forms.Button?
Делаю свою линейку приложений и хочу сделать свой дизайн. P.S.: Лучше я знаю.
Как сделать чтобы при запуске кода Windows Forms открывалась определённого размера?
Как сделать чтобы при запуске кода Windows Forms открывалась определённого размера? Исходные.
как сделать Поля таблицы на русском и скрыть поля счетчик в windows forms
как правильно сделать Поля таблицы на русском и скрыть поля счетчик в windows forms? SQL server.
Сделать тест на windows forms
ребят подскажите как сделать тест (психилогический или другой)на windows forms, дали курсач, не.
Как я писал свой чат
Привет, Хабр!
В статье я написал, о том как разрабатывал чат. О его архитектуре и о технических решениях принятых в ходе его разработки.
Чат представляет собой клиент-серверное приложение с элементами p2p.
С поддеркжой:
- Личных сообщений.
- Комнат.
- Передачи файлов.
- Голосового чата.
Исходный код проекта: GitHub
Итак, понеслась.
Модель
Данные и их синхронизация.
Запись и воспроизведение звука.
Данные и их синхронизация.
В начале когда я писал первую версию, я сразу же написал асинхронную версию клиента и сервера. Но, почему-то, напрочь забыл про то, что данные нужно синхронизировать. И так-как серьезной нагрузки чат никогда не испытывал, то я понял это только после введения в чат передачи файлов. После этого сразу все вспомнилось и везде было вставлено куча локов. Что разумеется не было лучшим решением. Если сказать точнее, то это было всего лишь чуть лучше, чем программа без синхронизации.
Сейчас же на клиенте и на сервере используется единый механизм доступа к данным. Блокируется полностью модель. Должен сказать что для сервера это не самое удачное решение.
Идея достаточно простая: есть контекст, который приватным статическим полем содержит модель. В конструкторе он, вызывает Monitor.Enter. На саму модель, либо на отдельный объект синхронизации. Так же контекст реализует интерфейс IDisposable и в методе Dispose он эту эту модель освобождает, вызывая метод Monitor.Exit.
Обобщенный класс используемый как для сервера, так и для клиента. В примере модель содержится не в самом контексте а в классе его создающем.
В результате, для доступа к данным хочешь-не хочешь их нужно блокировать, и уже не задумываешься о синхронизации. Главное не забывать использовать конструкцию using. Для сервера это не является лучшим решением т.к. половина команд работают с 2умя пользователями максимум, а блокируются в результате — все.
Контекст в программе может создавать только одна сущность (ServerModel — (неожиданно) для сервера, и ClientModel — для клиента). Она представляет собой класс содержащий статическую приватную модель (саму себя), API а также клиентское соединение и пир — для клиентской модели или сервер для серверной. (API, клиент и т.д. содержатся как статические поля). Также клиентская модель, в отличии от серверной, содержит еще и события. На которые будет подписан пользовательский интерфейс. В общем эти классы выступают как основные для доступа к чему либо.
В качестве примера приведу серверную модель (она поменьше). Обратить внимание следует на метод Get() создающий контекст.
API в данном случае это логика чата. Она представляет собой класс хранящий в себе команды которые могут выполнятся на нашей стороне, и набор методов которые отправляют команды другой стороне (Клиенту, если рассматриваем себя со стороны сервера. Для клиента это сервер или другой пир). В методах содержатся наиболее сложные команды, либо просто часто используемые.
Работает вся эта система следующим образом: как только клиент или сервер принимает пакет данных, он передает его на анализ в API. (У сервера принимают сообщения его соединения, а они в свою очередь дергают один метод у сервера, о том что данные приняты). API просто считывает первые два байта сообщения и ищет у себя в словаре команду с нужным id, и возвращает ее. Или пустую команду, которая ничего не делает, если такого id нет. Дальше команде передается полученный пакет, и id приславшего его соединения и она выполняется.
Также API имеет свой интерфейс, изначально его не было. Появился после того как я решил написать другую его реализацию, предполагалось что это будет защищенное API. Но потом мне это просто стало не интересно, и я не на долго забросил проект. Месяца на два. После возвращения к нему мне уже не хотелось все это делать, и я занялся реализацией P2P.
Клиент, кстати, умеет сам выбирать API, который использует сервер, и если такового не имеется он отсоединяется от сервера и говорит что не поддерживает серверный API. Это реализовано достаточно просто — после того как сервер принял соединение он сразу же отправляет строку с названием своего API, а клиент собственно ожидает эту строку и устанавливает нужный интерфейс. Ну или не устанавливает, если такой не поддерживает. После этого действия уже идет апишный запрос регистрации пользователя на сервере.
Метод сервера обрабатывающего принятые пакеты:
Полный код класса API (в данном случае — серверного):
Каждая команда реализует интерфейс команды. Для сервера IServerAPICommand, для клиента IClientAPICommand, на данном этапе их можно было бы свести к 1 интерфейсу, но мне этого делать почему то не хочется. Также она содержит свой Id и данные необходимые для ее выполнения, описывающееся классом MessageContent. Впрочем команде могут быть и не нужны данные. И она сама ответственна за то, что бы десериализовать набор байт в экземпляр класса.
Пример команды. В данному случае это команда добавления файла в комнату:
Запись и воспроизведение звука.
Добавлением голосового чата занялся недавно, возможно во время публикации он все еще будет в демо версии. Но уже успел повозится с воспроизведением и записью звука.
Первым вариантом были WinApi функции waveIn* waveOut*. Это был самый простой вариант, поэтому начал с него. Но с ними не сложилось, т.к. на версии framework’a 3.5 неадекватно работал маршалинг на платформе x64 и при запуске приложение просто падало без каких либо исключений. При сборке под х86 все было нормально.
Дальше была попытка подключить DirectSound, но у него был найден свой баг с способом оповещения о завершении проигрывания куска данных. После гугления на эту тему выяснилось, что Mircosoft давно забросили DirectSound и работают с XAudio2. К тому же его использование привело бы к необходимости компиляции 2ух версий х86 и х64.
Так как мне не хотелось самому писать обертку для XAudio2, то я вспомнил про OpenAL. Для которого к тому же есть обертка (OpenTK), еще и с открытым исходным кодом. Из OpenTK был аккуратно вырезан только сама аудио библиотека. Которая сейчас и работает в программе.
Так как мне приходилось работать OpenGL ES 2, то и с OpenAL я подружился сразу. Особенное если учесть что по нему на официальном сайте OpenTK есть примеры.
Для записи данных я использую достаточно простую схему, из примера. При включении записи, запускается таймер, период срабатывания которого настраивается на время, за которое буфер должен заполнится на половину. После чего данные из него считываются и отправляются командой ClientPlayVoiceCommand всем кто может слушать нас.
Изначально чат представлял собой одну главное комнату, где находились все пользователи. Из протоколов передачи данных использовался только TCP. Так — как он уже предоставляет надежность передачи данных оставалось только разбить его непрерывный поток на сообщения.
Это было сделано просто добавлением размера сообщения в его начало.
То есть пакет представляет из себя следующее:
Первые 4 байта — размер сообщения.
5-6 байт — идентификатор команды.
Остальные данные это сериализованный MessageContent.
Далее на одном форуме мне предложили ввести передачу файлов и голосовую связь. С файлами я справился почти сразу, правда в первой версии файлы передавались через сервер, что было вообще ужасно. После этого я задумался над тем, что хорошо было бы передавать их на прямую. Как вы знаете с проблемой NAT, это сделать не так то и просто.
Я долго возился и пытался реализовать обход NAT используя TCP, тогда бы не пришлось парится по поводу ненадежности UDP. С ним так ничего и не получилось. После чего было решено использовать UDP и технологию UDP hole punching.
А для начала надо было решить проблемы надежности. Как обычно бывает, я начал трудится над своим протоколом поверх UDP обеспечивающим мне надежную доставку сообщений. И он все таки у меня получился, но работал только локально. При его тестировании в реальных условиях он, видимо, настолько нагружал сеть, что у меня полностью зависал компьютер.
После этого я начал искать уже реализованные библиотеки и наткнулся на сатью на Хабре, где аналогичная проблема была решена с помощью Lidgren.Network. Она и была выбрана.
Обход NAT реализован на уровне API. Схема простая, нужно всего лишь, что бы пиры узнали реальные адреса, по которым их видит сервер. После этого один пир должен кинуть сообщение другому. Это сообщение возможно не дойдет, но создаст правило на роутере, что сообщения от того адреса, по которому оно было отправлено нужно доставлять именно этому компьютеру. После этого с помощью сервера другой пир узнает, что ему уже можно подключатся и, собственно, подключается.
И так, последовательность действий:
- Клиент 1 говорит серверу, что хочет подключится к Клиенту 2. (Команда ServerP2PConnectRequestCommand)
- Сервер делегирует свою задачу классу P2PService
- P2PService смотрит не подключались ли к нему уже такие клиенты и не знает ли он уже их адреса. Если нет — просит подключится тех кто подключен небыл (Команда ClientConnectToP2PServiceCommand)
- После их подключения, P2PService отправляет одному из них команду ожидания подключения. В данному случае это Клиент 2. (ClientWaitPeerConnectionCommand)
- Клиент получивший команду, также получает адрес который будет к нему подключатся, и отправляет на него сообщение. Начинает ожидать подключение и отправляет серверу команду о том, что готов принять соединение. (ServerP2PReadyAcceptCommand)
- После получения команды готовности, сервер говорит другому клиенту (Клиент 1), что тот может подключатся. (ClientConnectToPeerCommand)
- Связь между клиентами установлена.
Черными стрелками обозначены отправки команд. Красным инициализации Lidgren.Network соединений.
Весь этот алгоритм спрятан в AsyncPeer, и достаточно вызвать метод SendMessage, и если клиент не подключен, он сам подключится, и отправит сообщение. Либо сразу отправит, если уже подключен.
Команды голосовой связи инициируют соединение немного иначе. При создании голосовой комнаты сервер создает в комнате карту подключений. В которой записано куда должен подключится каждый пользователь в комнате. А команды воспроизведения голоса отправляются с помощью метода AsyncPeer.SendMessageIfConnected, который просто выкидывает сообщение, если соединения нет.
Пользовательский интерфейс.
Напоследок немного об интерфейсе программы.
Он разработан с помощью WPF и паттерна MVVM. Очень гибкая технология, правда в версии 3.5 немного сыровата, но тем не менее позволяет обойти сыроватые места. Как на пример некоторые свойства Command все еще не зависимые, и для них пришлось написать обертку CommandReference.
CommandReference в данном случае содержит реальную команду — зависимое свойство, на которое можно повесить биндинг. Сама обертка размещается в статических ресурсах. И используется в нужном месте, реально вызывая прибинденую команду.
Еще приходилось вместо оберток использовать AttachedProperty, которые в свою очередь изменяют нужные.
Для оповещения интерфейса об изменениях было решено использовать событийную модель, при этому в событиях передается вся необходимая информация, чтобы отобразить изменения.
В прочем, об интерфейсах написать больше нечего, WPF как WPF.