Четверг, 17.07.2025, 04:01
Приветствую Вас Гость | Регистрация | Вход

Мой сайт

Меню сайта
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Форма входа
Поиск
Календарь
«  Ноябрь 2012  »
Пн Вт Ср Чт Пт Сб Вс
   1234
567891011
12131415161718
19202122232425
2627282930
Архив записей
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Главная » 2012 » Ноябрь » 10 » Создание простого чата на c#
    05:47

    Создание простого чата на c#





    Создание простого чата на C#

    >

    Как это было…..strong>

    Ты хоть раз смотрел американские фильмы про хакеров, ФБР и т.д. Обычно там показывают очень важные компьютеры, доступ, к которым очень ограничен: нельзя устанавливать программы, отключены все внешние накопители, учетная запись сильно ограничена и вдобавок ко всему за всем следит администратор сети.

    И вот видимо насмотревшись фильмов про секретные архивы ФСБ наши институтские админы, вообразив себя ковбоями хаггис и секретными агентами ГРУ и ФАПСИ, решили сделать тоже самое и у нас. Теперь на компах нам доступны только гостевая учетная запись, дисководы и USB-порты отключили, все программы для сетевого общения удалены, даже net send отрубили. А ведь общаться с програмерами из соседних аудиторий хочется (или с вопросами помочь), и я решил освободить наш техникум от админского ига :) . Нет, я не пошел бить их в подворотни ржавой железякой по почкам (хотя хотелось :) ), а решил я на парах написать свой простенький чат, который бы позволил народу говорить…

    Неудачное начало нового дня

    Прежде всего я решил что буду писать на С#, и потом начал искать в инете инфу по чатам на шарпе. Каково же было мое удивление, когда место ожидаемой кучи разношерстной информации и
    пару десятков исходников я увидел одну и туже статью на всех сайтах и один исходник, да еще и на VB.Net. Ужас и моральный террор. Но это еще оказалось не все, оказывается в качестве серверной составляющей чата была использована самая простая и неэффективная техника (алгоритм программы). Плюс к этому ты не знал кто находиться в данный момент в чате и самое главное сам чат нельзя было контролировать – а это прекрасная жизнь для флудеров. Ладно, хватит о грустном, пора исправлять дело.

    Дела серверные или теория чатов

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

    Сокеты определяют конечные точки взаимодействия (обычно через сеть). Они поддерживают целый ряд протоколов, самыми популярными из которых сегодня являются User Datagram Protocol (UDP) и Transmission Control Protocol (TCP).

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

    TCP-сокеты могут быть либо клиентскими, либо серверными. Серверный сокет ожидает запросы на установление соединений, а клиентский — инициирует соединение. Как только соединение между сокетами установлено, клиент и сервер могут передавать и принимать данные или закрыть это соединение.

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

    Socket s = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);

    Параметр AddressFamily определяет используемую сокетом схему адресации. Чаще всего в качестве этого параметра используются значения InterNetwork (для адресов IPv4) и InterNetworkV6 (для адресов IPv6). Параметр SocketType определяет тип коммуникационной связи, осуществляемой при помощи сокета; к двум наиболее распространенным типам относятся Stream (для сокетов, ориентированных на логические соединения) и Dgram (если сокет не требует логических соединений). Параметр ProtocolType определяет применяемый сокетом протокол и принимает такие значения, как Tcp, Udp, Idp, Ggp и т. д.

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

    //Метод для инициализации серверного сокета

    //******************************************

    public void SetupServerSocket(int _port)

    {

    // Получаем информацию о локальном компьютере

    IPHostEntry localMachineInfo =

    Dns.GetHostEntry(Dns.GetHostName());

    System.Windows.Forms.MessageBox.Show(Dns.GetHostName(),"Это сетевое имя вашего компьютера");

    IPEndPoint myEndpoint = new IPEndPoint(

    localMachineInfo.AddressList[0], _port);



    // Создаем сокет, привязываем его к адресу

    // и начинаем прослушивание

    _serverSocket = new Socket(

    myEndpoint.Address.AddressFamily,

    SocketType.Stream, ProtocolType.Tcp);

    _serverSocket.Bind(myEndpoint);

    _serverSocket.Listen((int)SocketOptionName.MaxConnections);

    }

    //******************************************/



    Вероятно, приложению потребуется адрес сервера — для установления соединения или для привязки сокета к этому адресу. Один способ получения адреса сервера предоставляет класс System.Net.Dns. Он содержит ряд статических методов, служащих для преобразования IP-адреса в имя хоста и наоборот. Метод Dns.GetHostEntry возвращает объект IPHostEntry, содержащий соответствующую информацию о компьютере и позволяющий узнать все IP-адреса компьютера, которые известны DNS-серверу. Для упрощения в этой статье я буду использовать первый адрес из списка. После вызова метода Bind метод Socket.Listen конфигурирует для сокета внутренние очереди. Когда клиент пытается подключиться к серверу, в очередь помещается запрос соединения. Метод Listen принимает один аргумент — максимальное число запросов соединений, которые могут находиться в очереди. Метод Socket.Accept извлекает из очереди первый запрос и возвращает новый объект Socket, который можно использовать для коммуникационного взаимодействия с клиентом.

    Если ты создал клиентский сокет, то обычно после этого используют метод Connect. Как только соединение установлено клиент и сервер могут обмениваться данными с помощью команд Send и Receive. Как только вы решили прекратить обмен данными нужно вызвать метод Shutdown – он отправит все неоправленные данные и примет не принятые.

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

    Так, теперь тебе надо обратить внимание на метод Accept – он выполняется в синхронном блокирующем режиме, то есть что бы программа не блокировалась на все время, каждый вызов Accept нужно вызывать в отдельном потоке – чувствуешь, это получается, что для каждого клиента нужно создавать свой отдельный поток. Кроме того, что потоки излишне нагружают систему, так еще каждый поток имеет собственный стек, который по умолчанию равен 1 Мб. А если учесть что на компах в техникуме у нас всего 128-256 метров памяти и вечно лютующий Касперский, который жрет 90% ресурсов, то можно сделать вывод что этот метод нам не подойдет.
    Второй способ – это применение метода Select для «мультиплексирования» ввода-вывода. Слушающий сокет следует перед вызовом Select поместить в список проверки возможности чтения. Если после вызова Select данный сокет все еще присутствует в этом списке, значит, у нас есть соединение, которое следует принять (такое использование Select для определения наличия запроса гарантирует, что метод Accept не заблокируется). Но у этого метода есть и свои недостатки: представьте, что будет, если сразу 100 сокетов требуют выполнения операций ввода-вывода: сотый сокет будет ждать, пока вы не завершите обслуживание или составление графика обслуживания первых 99 сокетов.
    Ну а теперь я расскажу о самом эффективном алгоритме построения серверной части чата – асинхронный ввод-вывод.
    При асинхронном вводе-выводе нам не нужно генерировать и просматривать списки с сокетами, также не нужно создавать лишнии потоки для работы с клиентами, для этого используются методы обратного вызова. Вы должны лишь вызвать нужный Begin-метод (BeginAccept, BeginSend, BeginReceive и т. д.), передав ему соответствующий делегат метода обратного вызова, а в методе обратного вызова вызвать аналогичный End-метод (EndAccept, EndSend, EndReceive и т. п.) для получения результатов. Все асинхронные Begin-методы принимают объект состояния контекста, который может представлять все, что вы захотите. По завершении асинхронной операции этот объект является частью IAsyncResult, который передается методу обратного вызова. Так на этом хватит голой теории, приступаем к написанию чата.

    Начинаем кодить…
    Так, для начала тебе нужно создать новый проект Visual C#.
    Затем сделать себе такую же визуальную форму нашего чата как на этом рисунке. Кстати, если не хочешь делать все это сам, то просто загрузи исходник.

    Наверное, сразу бросается в глаза вертикальный ползунок на зеленом фоне, над которым написано «Прозрачность». Оно позволяет менять прозрачность окна, зачем оно нужно? Хороший вопрос, просто, когда я только начал его делать, мне приходилось тестировать серверную и клиентскую части на локальном компьютере, и чтобы не путать где сервер, а где клиент, я, когда запускал сервер, то делал его окно прозрачным, а клиентов непрозрачными.
    private void OpacityBar_Scroll(object sender, EventArgs e) //функция для увеличения или уменьшения прозрачности

    {

    this.Opacity= ((double) (this.OpacityBar.Value))/100;

    }

    Так, хватит ерундой маятся, на время пока забудем о визуальной форме и добавим новый класс – Server. Он будет сохранен в той же папке что и весь проект под именем Server.cs

    В нем мы опишем два класса: Серверный и Клиентский.

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

    ******************ЛИСТИНГ №1***********************

    public class Server //класс для работы программы в качестве сервера

    {

    //Переменные

    //***************************************************

    private int _port; //для порта

    private Socket _serverSocket; //для серверного сокета

    public string _UserName; //для имени юзверя

    MainForm _MyForm; //типа указатель на главную форму окна

    //****************************************************



    public Server(int port, string UserName, MainForm form) //конструктор

    {

    _port = port;

    _UserName = UserName;

    _MyForm = form;

    }



    private class ConnectionInfo // специальный класс для охранения инфы от подключенного клиента

    {

    public Socket Socket; //сокет

    public byte[] Buffer; //принятые данные будут помещены сюда

    public string Name; // ну, а это имя клиента

    }
    private List<ConnectionInfo /> _connections =new List<ConnectionInfo />(); //создаем новый список пользователей, вернее хранилище для них :)

    public void Start() // с этой функции начинает свою работу сервер

    {

    SetupServerSocket(_port); //настраиваем серверный сокет на нужном порту

    ConnectionInfo conn = new ConnectionInfo(); //создаем фиктивно юзера

    conn.Name = ("@"+_UserName); // добавляем к нему Админское знамя "@"

    conn.Socket = null; // ну сокета у него нет - он же фиктивный :)

    _MyForm.OnLineList.Items.Add(conn.Name);// добавляем себя на форму

    _connections.Add(conn);//добавляем себя к списку клиентов

    _serverSocket.BeginAccept(new

    AsyncCallback(AcceptCallback), _serverSocket); // начинаем асинхронное принятие соединений

    }



    private void AcceptCallback(IAsyncResult result) //метод обратного вызова для принятия подключения

    {

    ConnectionInfo connection = new ConnectionInfo();

    try

    {

    // Завершение операции Accept

    Socket s = (Socket)result.AsyncState;

    connection.Socket = s.EndAccept(result);

    connection.Buffer = new byte[1024];

    lock (_connections) _connections.Add(connection);

    // Начало операции Receive и новой операции Accept

    connection.Socket.BeginReceive(connection.Buffer,

    0, connection.Buffer.Length, SocketFlags.None,

    new AsyncCallback(ReceiveCallback),

    connection);

    _serverSocket.BeginAccept(new AsyncCallback(

    AcceptCallback), result.AsyncState);

    }

    catch (SocketException exc)

    {

    CloseConnection(connection);

    Console.WriteLine("Ошибка из-за сокета: " +

    exc.SocketErrorCode);

    }

    catch (Exception exc)

    {

    CloseConnection(connection);

    Console.WriteLine("Исключение: " + exc);

    }

    }



    private void ReceiveCallback(IAsyncResult result)//метод обратного вызова для получения данных

    {

    ConnectionInfo connection =

    (ConnectionInfo)result.AsyncState;

    try

    {

    int bytesRead =

    connection.Socket.EndReceive(result);

    if (0 != bytesRead)

    {

    //////////////////////
    string ReceivedText = Encoding.Unicode.GetString(connection.Buffer, 0,bytesRead);
    //////////////////////////////// Сдесь будем проводить проверку входящих комманд

    switch (ReceivedText.Substring(0,2))

    {

    case "/n": //проводим авторизацию в системе

    {

    connection.Name = (ReceivedText.Substring(2, (ReceivedText.Length - 2)));

    ReceivedText = ("К нам присоединился ''" + ReceivedText.Substring(2, (ReceivedText.Length - 2)) + "''");

    _MyForm.OnLineList.Items.Add(connection.Name);

    lock (_connections)

    {

    foreach (ConnectionInfo conn in _connections) //отсылаем новому клиенту список всех подключенных до него

    {

    if (conn != connection)

    {



    connection.Socket.Send(Encoding.Unicode.GetBytes("/n"+conn.Name));

    Thread.Sleep(400);
    }

    }



    }



    break;

    }

    case "/q": //сообщяем о том что юзер выходит из чата

    {



    ReceivedText = (("От нас ушел " + connection.Name));

    _MyForm.OnLineList.Items.Remove(connection.Name);

    connection.Name = "$Man$Dead$";//;)

    break;

    }

    }



    ///////////////////////////////////////////

    _MyForm.ChatRichTextBox.AppendText((ReceivedText));

    _MyForm.ChatRichTextBox.AppendText("n"); //отображаем всю эту чушь у себя в проге

    //////////////////////////////////////////
    /////////////////////

    if (connection.Name != null)

    {

    lock (_connections)

    {

    foreach (ConnectionInfo conn in

    _connections)

    {



    if ((conn.Name != null) &(conn.Name.Substring(0,1)!="@") ) //отправить если юзер уже зарегился;)

    {

    conn.Socket.Send(connection.Buffer,bytesRead, SocketFlags.None);

    }

    }

    }



    }

    connection.Socket.BeginReceive(

    connection.Buffer, 0,

    connection.Buffer.Length, SocketFlags.None,

    new AsyncCallback(ReceiveCallback),

    connection);



    if (connection.Name == "$Man$Dead$")

    CloseConnection(connection);

    }

    else CloseConnection(connection);



    }

    catch (SocketException exc)

    {

    CloseConnection(connection);

    Console.WriteLine("Неожиданное исключение: " +

    exc.SocketErrorCode);

    }

    catch (Exception exc)

    {

    CloseConnection(connection);

    Console.WriteLine("Блин, ошибка: " + exc);

    }



    }

    public void SendToAllFromServer(string text) //типа функция для посылки сообщения от сервера всем клиентам

    {

    foreach (ConnectionInfo conn in _connections)

    {

    if ((conn.Name != "") && (conn.Name != ("@" + _UserName)))

    conn.Socket.Send(Encoding.Unicode.GetBytes(text));

    }

    }

    public void KickUser(string Name, string why) //типа функция для выкидывания из чата надоедливого юзера

    {

    foreach (ConnectionInfo conn in _connections)

    {

    if ((conn.Name==Name))

    {

    try

    {

    conn.Socket.Send(Encoding.Unicode.GetBytes(why));

    conn.Socket.Close();

    _MyForm.OnLineList.Items.Remove(conn.Name);

    conn.Name = "$Man$Dead$";

    }

    catch

    { }

    }

    }

    }



    private void CloseConnection(ConnectionInfo ci) //для закрытия соединения

    {

    try

    {

    ci.Socket.Close();

    }

    catch

    {

    }

    lock (_connections) _connections.Remove(ci);

    }



    //Метод для инициализации серверного сокета

    //******************************************

    public void SetupServerSocket(int _port)

    {

    // Получаем информацию о локальном компьютере

    IPHostEntry localMachineInfo =

    Dns.GetHostEntry(Dns.GetHostName());

    System.Windows.Forms.MessageBox.Show(Dns.GetHostName(),"Это сетевое имя вашего компьютера");

    IPEndPoint myEndpoint = new IPEndPoint(

    localMachineInfo.AddressList[0], _port);



    // Создаем сокет, привязываем его к адресу

    // и начинаем прослушивание

    _serverSocket = new Socket(

    myEndpoint.Address.AddressFamily,

    SocketType.Stream, ProtocolType.Tcp);

    _serverSocket.Bind(myEndpoint);

    _serverSocket.Listen((int)SocketOptionName.MaxConnections);

    }

    //******************************************/



    public void Close()//закрываем чат

    {



    try

    {

    _serverSocket.Shutdown(SocketShutdown.Both);

    }

    catch

    {

    }

    _serverSocket.Close();



    }



    }

    ***********************КОНЕЦ ЛИСТИНГА******************
    С серверной частью разобрались, начинаем кодить клиентскую.

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

    ***********************ЛИСТИНГ №2***********************

    public class Client //класс для работы программы в качестве клиента

    {

    //*****ПЕРЕМЕННЫЕ****************



    private Socket _ClientSocket;

    public string _UserName;

    MainForm MyForm;

    Thread ListenThead;



    //*******************************



    public Client(string UserName, MainForm form)//конструктор (ЛЕГО :) )

    {

    _UserName = UserName;

    MyForm = form;

    }

    public bool Connect(string HostName, int _port) // метод для подключения клиента к нужному компу на нужный порт

    {



    _ClientSocket = new Socket(AddressFamily.InterNetwork,

    SocketType.Stream, ProtocolType.Tcp);

    try

    {

    _ClientSocket.Connect(HostName, _port);

    Send(("/n" + _UserName));

    }

    catch (SocketException exp)

    {

    MessageBox.Show("Подключиться к чату не удалось, попробуйте еще раз. Техническая причина:"+exp.Message, "ПОДКЛЮЧЕНИЕ К ЧАТУ НЕ УДАЛОСЬ");

    return false;

    }



    ListenThead = new Thread(new ThreadStart(StartListen));//создаем новый поток, в котором будет производиться чтение данных

    ListenThead.Start();//запускаем :)



    return true;



    }

    private void StartListen()//вот из этого метода будет состоять поток

    {



    while (true) // это типа бесконечный цикл

    {



    try

    {

    byte []buffer=new byte[1024];

    _ClientSocket.Receive(buffer);

    /////////////////////////////////

    string ReceivedText = Encoding.Unicode.GetString(buffer);

    switch (ReceivedText.Substring(0, 2))//проверяем пришедшие данные на наличие комманд

    {

    case "/n":

    {



    MyForm.OnLineList.Items.Add((ReceivedText.Substring(2, (ReceivedText.Length- 2))));

    MyForm.ChatRichTextBox.AppendText(("К нам присоединился "+(ReceivedText.Substring(2, (ReceivedText.Length-2)))));

    break;

    }

    case "/q":

    {

    MyForm.OnLineList.Items.Remove((ReceivedText.Substring(2, (ReceivedText.Length- 2))));

    MyForm.ChatRichTextBox.AppendText(("От нас ушел " + (ReceivedText.Substring(2, (ReceivedText.Length - 2)))));

    break;

    }



    default:

    {

    MyForm.ChatRichTextBox.AppendText(ReceivedText);

    break;

    }

    }

    //////////////////////////////

    MyForm.ChatRichTextBox.AppendText("n");



    }

    catch

    {



    try

    {



    MyForm.ChatRichTextBox.AppendText("nСОЕДИНЕНИЕ ПРЕРВАНО!!! ПОПРОБУЙТЕ ПОДКЛЮЧИТЬСЯ К СЕРВЕРУ ЕЩЕ РАЗn");

    MyForm.OnLineList.Items.Clear();

    MyForm.Disconnect();

    }

    catch

    { }

    }



    Thread.Sleep(350);

    }

    }



    public void Send(string Text)//метод для посылки сообщения на сервер

    {

    try

    {

    _ClientSocket.Send((Encoding.Unicode.GetBytes(Text)));

    }

    catch

    {

    MyForm.ChatRichTextBox.AppendText("СОЕДИНЕНИЕ ОТСУТСТВУЕТ!!! ПОПРОБУЙТЕ ПОДКЛЮЧИТЬСЯ К СЕРВЕРУ ЕЩЕ РАЗ");

    MyForm.Disconnect();

    }

    }

    public void Close()//метод для отсоединения от чата

    {

    _ClientSocket.Close();



    ListenThead.Abort();



    _ClientSocket.Close();



    }

    }

    *****************КОНЕЦ ЛИСТИНГА************************

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

    Happy End…

    Вот ты и узнал как написать простой, но быстрый чат не прилагая больших усилий. Если у тебя возникли какие либо пожелания или вопросы пиши мне в личку на сайте или на мыло:
    John-Frost@yandex.ru или John-Frost@coderszone.info, иногда я также появляюсь в IRC на канале #coding.
    Колесник Сергей aka John Frost

    СКАЧАТЬ ИСХОДНИК



    ЕСЛИ ВЫ ХОТИТЕ РАЗМЕСТИТЬ ЭТУ СТАТЬЮ НА СВОЕМ РЕСУРСЕ, ТО ОБРАТИТЕСЬ К АДМИНИСТРАЦИИ САЙТА, В ЛЮБОМ СЛУЧАЕ ССЫЛКА НА САЙТ WWW.CODERSZONE.INFO ОБЯЗАТЕЛЬНА!
    Please enable JavaScript
    Просмотров: 15914 | Добавил: afthed | Рейтинг: 3.0/2
    Всего комментариев: 0