Здравствуйте, сегодня мы с Вами поговорим о программировании сокетов, с помощью платформы .Net и языка C#.
Давайте разберемся, что же такое сокет?
Сокет — конечная точка связи двустороннего канала между 2 компьютерами.
Если мы соединим 2 сокета, то получим канал, через который можно передавать данные в обе стороны. Одна сторона канала называется сервером, другая — клиентом.
Для передачи/приема данных нужно открыть канал. Вконце всех операций — закрыть.
Типы сокетов
Существует 2 вида сокетов: потоковые, дейтаграммные.
Теперь о каждом по-отдельности.
Потоковый сокет — это сокет, который состоит из потока байтов, который может быть двунапрямленным (в обе стороны). Он берет на себя всю ответственность о доставке данных и исправлении ошибок.
Особенностью есть возможность передачи больших объемов данных.
Использует протокол TCP (Transmission Control Protocol), именно который обеспечивает поступление данных на другую сторону в нужной последовательности и без ошибок.
Если вам важна точность доставки данных, или их объем — потоковые сокеты будут лучшим выбором.
Дейтаграммный сокет — в отличие от потокового, имеет ограничения по размеру. Реилизирован через протокол UDP(User Datagram Protocol), который не отвечает за приход в конечную точку всех данных. Одним из плюсов — не нужно создавать соединения между 2 сторонами. Это очень важно, когда затраты времени неприпустимы.
Сокеты связываются между собой через порты.
Вот схема сокета:
От слова к делу
Мы создадим приложение-чат, которое реализовывает обе структуры: клиент/сервер. Для начала, нам нужно написать класс со статистическими методами для работы с сокетами.
Добавьте в проект новый класс, с именем: 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;
private static IPEndPoint ipEndPoint;
private static Socket Server;
private static Socket Client;
private static Socket Handler;
~SocketWorker()
{
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;
string HostName = (string)TempObject[0];
string Port = (string)TempObject[1];
bool ServerApp = (bool)TempObject[2];
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)
{
if (error.ErrorCode == 10061)
{
MessageBox.Show("Порт подключения закрыт!");
Application.Exit();
}
}
}
else
{
try
{
Server = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
}
catch (SocketException error)
{
if (error.ErrorCode == 10061)
{
MessageBox.Show("Порт подключения закрыт!");
Application.Exit();
}
}
try
{
Server.Bind(ipEndPoint);
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)
{
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)
{
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)
{
WaitingForConnecting.Start(new Object[] { "0.0.0.0", "12123", AppCheckedTypeServer });
}
else
{
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();
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());
}
|
Здесь мы сразу запускаем окно настроек, а потом уже само окно чата.
Вот что у нас получилось:

Еще немного подкрутить, и ася отдыхает ;)
Закачать проект или исполняемый файл можете по ссылке: http://wincode.org.ua/downloads/TestChat/
Удачи.
Тысленко Максим (Ockonal)
17.11.2008