Cегодня мы с Вами поговорим о программировании сокетов, с помощью платформы .Net и языка C#
Здравствуйте, сегодня мы с Вами поговорим о программировании сокетов, с помощью платформы .Net и языка C#.
Давайте разберемся, что же такое сокет?
Сокет — конечная точка связи двустороннего канала между 2 компьютерами.
Если мы соединим 2 сокета, то получим канал, через который можно передавать данные в обе стороны. Одна сторона канала называется сервером, другая — клиентом.
Для передачи/приема данных нужно открыть канал. Вконце всех операций — закрыть. Типы сокетов
Существует 2 вида сокетов: потоковые, дейтаграммные.
Теперь о каждом по-отдельности.
Потоковый сокет — это сокет, который состоит из потока байтов, который может быть двунапрямленным (в обе стороны). Он берет на себя всю ответственность о доставке данных и исправлении ошибок.
Особенностью есть возможность передачи больших объемов данных.
Использует протокол TCP (Transmission Control Protocol), именно который обеспечивает поступление данных на другую сторону в нужной последовательности и без ошибок.
Если вам важна точность доставки данных, или их объем — потоковые сокеты будут лучшим выбором.
Дейтаграммный сокет — в отличие от потокового, имеет ограничения по размеру. Реилизирован через протокол UDP(User Datagram Protocol), который не отвечает за приход в конечную точку всех данных. Одним из плюсов — не нужно создавать соединения между 2 сторонами. Это очень важно, когда затраты времени неприпустимы.
Сокеты связываются между собой через порты. http://ru.wikipedia.org/wiki/Порт_(IP)
Вот схема сокета:
От слова к делу
Мы создадим приложение-чат, которое реализовывает обе структуры: клиент/сервер. Для начала, нам нужно написать класс со статистическими методами для работы с сокетами.
Добавьте в проект новый класс, с именем: SocketWorker.
Вот его код:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Net.Sockets; // Для работы с сокетами нужно подключить это пространство имен using System.Windows.Forms;
namespace TestChat { class SocketWorker { // ---------------------------------------------------------------------- private static IPHostEntry ipHost; // Класс для сведений об адресе веб-узла private static IPAddress ipAddr; // Предоставляет IP-адрес private static IPEndPoint ipEndPoint; // Локальная конечная точка
private static Socket Server; // Создаем объект сокета-сервера private static Socket Client; // Создаем объект сокета-клиента private static Socket Handler; // Создаем объект вспомогательного сокета // ---------------------------------------------------------------------- // Деструктор ~SocketWorker() { // Вместо проверки сокетов на подключение, просто используем блок try-catch try { // Сразу обрываем соединения Server.Shutdown(SocketShutdown.Both); // А потом закрываем сокет! Server.Close();
Client.Shutdown(SocketShutdown.Both); Client.Close();
Handler.Shutdown(SocketShutdown.Both); Handler.Close(); } catch { } } // ---------------------------------------------------------------------- // Создание сокета public static bool IsConnected = false; public static void CreateSocket(Object obj) { Object[] TempObject = (Object[])obj; // IP-адрес сервера, для подключения string HostName = (string)TempObject[0]; // Порт подключения string Port = (string)TempObject[1]; bool ServerApp = (bool)TempObject[2];
// Разрешает DNS-имя узла или IP-адрес в экземпляр IPHostEntry. ipHost = Dns.Resolve(HostName); // Получаем из списка адресов первый (адресов может быть много) ipAddr = ipHost.AddressList[0]; // Создаем конечную локальную точку подключения на каком-то порту ipEndPoint = new IPEndPoint(ipAddr, int.Parse(Port));
if (!ServerApp) { try { // Создаем сокет на текущей машине Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); while (true) { // Пытаемся подключиться к удаленной точке Client.Connect(ipEndPoint); if (Client.Connected) IsConnected = true; break; } } catch (SocketException error) { // 10061 - порт подключения занят/закрыт if (error.ErrorCode == 10061) { MessageBox.Show("Порт подключения закрыт!"); Application.Exit(); } } } else { try { // Создаем сокет сервера на текущей машине Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); } catch (SocketException error) { // 10061 - порт подключения занят/закрыт if (error.ErrorCode == 10061) { MessageBox.Show("Порт подключения закрыт!"); Application.Exit(); } }
// Ждем подключений try { // Связываем удаленную точку с сокетом Server.Bind(ipEndPoint); // Не более 10 подключения на сокет Server.Listen(10);
// Начинаем "прослушивать" подключения while (true) { Handler = Server.Accept(); if (Handler.Connected) IsConnected = true; break; } } catch { throw new Exception("Проблемы с подключением"); } } } // ---------------------------------------------------------------------- // Получение данных от сервера public static string GetDataFromServer() { string GetInformation = "";
// Создаем пустое «хранилище» байтов, куда будем получать информацию byte[] GetBytes = new byte[1024]; int BytesRec = Client.Receive(GetBytes); // Переводим из массива битов в строку GetInformation += Encoding.Unicode.GetString(GetBytes, 0, BytesRec);
return GetInformation; } // ---------------------------------------------------------------------- // Получение информации от клиента public static string GetDataFromClient() { string GetInformation = "";
byte[] GetBytes = new byte[1024]; int BytesRec = Handler.Receive(GetBytes);
GetInformation += Encoding.Unicode.GetString(GetBytes, 0, BytesRec);
return GetInformation; } // ---------------------------------------------------------------------- // Отправка информации на сервер public static void SendDataToServer(string Data, string Name) { // Используем unicode, чтобы не было проблем с кодировкой, при приеме информации byte[] SendMsg = Encoding.Unicode.GetBytes(Name + " : " + Data); Client.Send(SendMsg); } // ---------------------------------------------------------------------- // Отправка информации на клиент public static void SendDataToClient(string Data, string Name) { byte[] SendMsg = Encoding.Unicode.GetBytes(Name + " : " + Data); Handler.Send(SendMsg); } // ---------------------------------------------------------------------- } }
Вот и все, ничего тяжелого. Создаем и настраиваем удаленную локальную точку, сокет и пытаемся подключиться. Интерфейс программы
Теперь нужно сделать интерфейс нашего чата. Он состоит из 2 окон:
Окно настроек подключения.

Сервер: ServerRadio
Клиент: ClientRadio
IP-адрес: IP-adress
Ник в чате: ChatNick
Чатиться =): StartChat

Поле ввода: ChatText
Отправить: Send
История чата: ChatHistory
«Ядро» программы
Еслы бы мы писали более сложную программу, тогда нужно было бы создать 2 класса, характеризирующих сервер и клиент, или же чат, где больше возможностей. Делать это нам не нужно, поэтому создайте 1 новый класс: AppCore.cs.using System; using System.Collections.Generic; using System.Linq; using System.Text;
namespace TestChat { class AppCore { // Тип приложения -------------------------------------------- public enum AppType { Server, Client } private AppType ApplicationType; public AppType ApplicationTypeProp { get { return ApplicationType; } set { ApplicationType = value; } } // Ник в чате ----------------------------------------------- private string ChatNick = "BadUser"; public string ChatNickProp { get { return ChatNick; } set { ChatNick = value; } } // ---------------------------------------------------------- } }
Теперь сделаем один основной класс со статистическими ф-циями, в котором запишем основные функции. Назовите его Worker. Вот код:using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading;
namespace TestChat { class Worker { // Глобальный объект, характеризирующий персону в чате -------------------- public static AppCore GlobalAppObject; // Настраивает объект выше public static void CreateGameObject(bool IsServer, string ChatNick) { GlobalAppObject = new AppCore(); // Назначаем ник GlobalAppObject.ChatNickProp = ChatNick; // Тип приложения if (IsServer) { GlobalAppObject.ApplicationTypeProp = AppCore.AppType.Server; } else { GlobalAppObject.ApplicationTypeProp = AppCore.AppType.Client; } } // Ф-ция, работающая в новом потоке: получение новых сообщенй ------------- public static void GetMessages(Object obj) { // Получаем объект истории чата (лист бокс) Object[] Temp = (Object[])obj; System.Windows.Forms.ListBox ChatListBox = (System.Windows.Forms.ListBox)Temp[0];
// В бесконечном цикле получаем сообщения while (true) { // Ставим паузу, чтобы на время освобождать порт для отправки сообщений Thread.Sleep(50); if (GlobalAppObject.ApplicationTypeProp == AppCore.AppType.Server) { try { // Получаем сообщение от клиента string Message = SocketWorker.GetDataFromClient(); // Добавляем в историю + текущее время ChatListBox.Items.Add(DateTime.Now.ToShortTimeString() + " " + Message); // Автопрокрутка списка ChatListBox.TopIndex = ChatListBox.Items.Count - 1; } catch { } } else { try { string Message = SocketWorker.GetDataFromServer(); ChatListBox.Items.Add(DateTime.Now.ToShortTimeString() + " " + Message); ChatListBox.TopIndex = ChatListBox.Items.Count - 1; } catch { } } } } // ----------------------------------------------------------------------- } }
Окно настроек подключения
С его интерфейсом мы уже познайомились. Единственное, добавьте новый таймер на форму 300 миллисекунд, Enabled, UpdateForm. вот код формы:using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms;
namespace TestChat { public partial class WindowSettings : Form { public WindowSettings() { InitializeComponent(); // Назначаем потоку имя ф-ции WaitingForConnecting = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(SocketWorker.CreateSocket)); }
// Указывает на тип приложения private static bool AppCheckedTypeServer = true; // Обработчик таймера private void UpdateForm_Tick(object sender, EventArgs e) { // Если подключились - закрываем окно настроек if (SocketWorker.IsConnected) { try { // Пытаемся завершить поток подключений WaitingForConnecting.Abort(); } catch { } // Закрываем окно настроек this.Close(); } if (ServerRadio.Checked) { // Блокируем поле ввода IP-адреса IPAdress.Enabled = false; AppCheckedTypeServer = true; } else { AppCheckedTypeServer = false; IPAdress.Enabled = true; } }
private void StartChat_Click(object sender, EventArgs e) { // Блокируем кнопку от повторного нажатия StartChat.Enabled = false;
// Настраиваем основной игровой объект Worker.CreateGameObject(AppCheckedTypeServer, ChatNick.Text);
// Запускаем поток подключения if (AppCheckedTypeServer) { // 0.0.0.0 -для прослушки внешней сети, не используйте 127.0.0.1 или localhost! WaitingForConnecting.Start(new Object[] { "0.0.0.0", "12123", AppCheckedTypeServer }); } else { // 12123 –порт подключения WaitingForConnecting.Start(new Object[] { IPAdress.Text.ToString(), "12123", AppCheckedTypeServer }); } } } }
Основное окно чата
Создайте Windows Form, с именем MainChat. Сразу же добавьте в MainChat.Designer код:using System.Threading; ... Thread WaitingForMessage;
Теперь, если хотите, установите Anchor для объектов на форме, чтобы можно было делать ресайзинг.
Вот код формы:using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms;
namespace TestChat { public partial class MainChat : Form { public MainChat() { InitializeComponent(); // Создаем новый поток, указываем на ф-цию получения сообщений в классе Worker WaitingForMessage = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(Worker.GetMessages)); // Запускаем, в параметрах передаем листбокс (история чата) WaitingForMessage.Start(new Object[] {ChatHistory}); }
// Обработчик отправки сообщения (кнопки) private void Send_Click(object sender, EventArgs e) { if (Worker.GlobalAppObject.ApplicationTypeProp == AppCore.AppType.Server) { // Посылаем клиенту новое сообщение SocketWorker.SendDataToClient(ChatText.Text, Worker.GlobalAppObject.ChatNickProp); } else { SocketWorker.SendDataToServer(ChatText.Text, Worker.GlobalAppObject.ChatNickProp); } // Добавляем в историю свое же сообщение + ник + время написания ChatHistory.Items.Add(DateTime.Now.ToShortTimeString() + " " + Worker.GlobalAppObject.ChatNickProp + ": " + ChatText.Text.ToString()); // Автопрокрутка истории чата ChatHistory.TopIndex = ChatHistory.Items.Count - 1; // Убираем текст из поля ввода ChatText.Text = ""; } } }
Теперь остается добавить последний штрих. Отредактируйте файл Program.cs static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new WindowSettings()); Application.Run(new MainChat()); }
Здесь мы сразу запускаем окно настроек, а потом уже само окно чата.
Вот что у нас получилось:

Еще немного подкрутить, и ася отдыхает ;)
Источник: |