
- •Робота з сокетами засобами c#.
- •Типи комунікації сокетів.
- •2.Робота з сокетами в .Net
- •Клас System.Net.Sockets.Socket
- •Задача 1. Отримати список адресів машини по його імені.
- •Асінхронний варіант взаємодії
- •IsComplete – вказує на завершення операції.
- •Розглянемо приклад тестування паралельної обробки
- •Клас ThreadedServer:
- •Сервер доопрацювання флюорографії (сокети)
- •12. Останньою функцією в цьому класі є функція відправки відповіді клієнтові
Розглянемо приклад тестування паралельної обробки
class Program
{
private static AsyncTcpServer asynchTcpServer = new AsyncTcpServer();
private static Client client = new Client();
static void Main(string[] args)
{
RunTcpAsynchServer();
//RunRawServer();
}
private static void RunTcpAsynchServer()
{
//PrintIpAddress("www.rambler.ru");
Thread serverThread = new Thread(startAsynchServer);
serverThread.IsBackground = true;
Thread clientThread = new Thread(startClient);
clientThread.IsBackground = true;
Thread clientThread2 = new Thread(startClient);
clientThread2.IsBackground = true;
serverThread.Start();
Console.WriteLine("Press ENTER to test interaction...");
if (Console.ReadKey().Key == ConsoleKey.Enter) ;
{
clientThread.Start();
clientThread2.Start();
Console.WriteLine("Press ESC to exit");
while (true)
{
if (Console.ReadKey().Key == ConsoleKey.Escape)
{
break;
}
else
{
client.Run();
}
}
}
}
private static void startAsynchServer()
{
asynchTcpServer.Run();
}
private static void startClient()
{
client.Run();
}
}
The Socket class in version 2.0 is suitable for a variety of client applications that need to use network sockets as well as for some server and service type applications. Unfortunately, there are some service application scenarios that are not possible with the Socket class in version 2.0, but they are possible with native languages that directly use the Windows WinSock APIs. The primary issue with the version 2.0 Socket class is that it consumes excess CPU cycles to perform a single socket I/O operation as well as when allocating the necessary underlying objects to maintain I/O operations on a large number of sockets simultaneously.
With the .NET Framework 3.5, the common language runtime (CLR) can now more efficiently manage a large number of Overlapped objects simultaneously. Overlapped objects in the CLR effectively wrap the native Windows OVERLAPPED structure used to manage asynchronous I/O operations. There is one Overlapped object instance for every Socket asynchronous I/O operation in progress. And it is now possible to have 60,000 or more connected sockets while maintaining a pending asynchronous receive I/O operation on each socket.
The Socket class in version 2.0 uses Windows I/O Completion Ports for asynchronous I/O operation completion. This allows applications to easily scale to a large number of open sockets. The .NET Framework implements the System.Threading.ThreadPool class that provides the completion threads that read Completion Ports and complete asynchronous I/O operations. In developing the upcoming 3.5 version of the .NET Framework, we put a significant focus on removing overhead in the code paths between reading a Completion Port and calling an application's completion delegate or signaling the I/O completion event object in an IAsyncResult object.
The APM in the .NET Framework is also referred to as the Begin/End pattern. This is because a Begin method is called to initiate an asynchronous operation and returns an IAsyncResult object. A delegate can optionally be provided as a parameter to the Begin method that will be called when the asynchronous operation completes. Alternatively a thread can wait on the IAsyncResult.AsyncWaitHandle. When the callback is called or the wait signaled, the End method is called to get the results of the asynchronous operation. This pattern is flexible, relatively easy to use, and common throughout the .NET Framework.
It is important for you to note, though, that there is a price when doing a significant number of asynchronous socket operations. For each operation, an IAsyncResult object must be created—and it cannot be reused. This can impact performance by heavily exercising object allocation and garbage collection. To work around this issue, the new release offers a different pattern of methods to do asynchronous I/O with sockets. This new pattern doesn't require the allocation of an operation context object for every socket operation[http://msdn.microsoft.com/en-us/magazine/cc163356.aspx].
Rather than create an entirely new pattern, we took an existing pattern and made one fundamental change. There are now methods in the Socket class that use a variation of the Event-based completion model. In version 2.0, you would use the following code to initiate an asynchronous send on a Socket:
void OnSendCompletion(IAsyncResult ar) { }
IAsyncResult ar = socket.BeginSend(buffer, 0, buffer.Length,
SocketFlags.None, OnSendCompletion, state);
In the new version, you can also do this:
void OnSendCompletion(object src, SocketAsyncEventArgs sae) { }
SocketAsyncEventArgs sae = new SocketAsyncEventArgs();
sae.Completed += OnSendCompletion;
sae.SetBuffer(buffer, 0, buffer.Length);
socket.SendAsync(sae);
There are a few notable differences here. The object that wraps the context of the operation is a SocketAsyncEventArgs object instead of an IAsyncResult object. The application creates and manages (and can even reuse) SocketAsyncEventArgs objects. All the parameters to the socket operation are specified by properties and methods of the SocketAsyncEventArgs object. Completion status is provided by properties of the SocketAsyncEventArgs object as well. And finally, the use of an event handler callback completion method is required.
All of these changes result in significantly improved performance and scalability of the System.Net.Sockets class in the .NET Framework 3.5. Two of these improvements will be automatically realized by existing applications. The third improvement, the new Socket methods, can only be used by modifying an application, but these methods provide increased scalability for the most demanding socket-based applications.
1.3 UDP
Як вже було вказано, протокол UDP не вимагає зєднання – данні можуть бути надіслані не одному, а множині отримувачів використовуючи при цьому один сокет.
Для огранизації взаємодії використовується класс System.Net.Sockets.UdpClient в конструктор якого передається адреса віддаленого хоста та номер порту у разі клієнтського додатку, або номер порта в разі серверного.
UdpClient udpc = new UdpClient(2055);
Та
string hostName = Dns.GetHostName();
IPAddress[] ipHostAddressList = Dns.Resolve(hostName).AddressList;
IPAddress ipServerAddress = ipHostAddressList[0];
UdpClient udpc = new UdpClient(ipServerAddress.ToString(), 2055);
Отримання даних здійснюється з використанням методу Receive:
IPEndPoint ep = null;
byte[] data = udpc.Receive(ref ep);
де
data – байтовий масив, в який зберігаються отримані дані
ep – адреса відправника.
Відправка даних здійснюється з використанням методу Send.
Якщо адреса віддаленого хоста була задана в конструкторі використовується
виклик методу
udpc.Send(sdata, sdata.Length);
де
data – байтовий масив
Інакше, в метод передається адреса процесу отримувача
udpc.Send(sdata, sdata.Length, ep);
де
IPEndPoint ep – адреса отричувача.
Розглянемо задачу віддаленої ідентифікації користувача.
Серверна програма має вигляд.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SocketTest
{
public class UDPServer
{
public void Run()
{
UdpClient udpc = new UdpClient(2055);
Console.WriteLine("Server started on port 2055");
IPEndPoint ep = null;
string PID;
string status = "not identified";
while (true)
{
byte[] rdata = udpc.Receive(ref ep);
PID = Encoding.ASCII.GetString(rdata);
//Some logic to identify
if (PID == "12345")
status = "identified";
byte[] sdata = Encoding.ASCII.GetBytes(status);
udpc.Send(sdata, sdata.Length, ep);
}
}
}
}
Клієнтська програма.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace SocketTest
{
public class UDPClient
{
public void Run()
{
string hostName = Dns.GetHostName();
IPAddress[] ipHostAddressList = Dns.Resolve(hostName).AddressList;
IPAddress ipServerAddress = ipHostAddressList[0];
UdpClient udpc = new UdpClient(ipServerAddress.ToString(), 2055);
IPEndPoint ep = null;
while (true)
{
Console.Write("Enter your personal identifier: ");
string PID = Console.ReadLine();
if (PID == "" )
{
//Console.Write("Press ESC to quit. ");
break;
}
//
byte[] sdata = Encoding.ASCII.GetBytes(PID);
udpc.Send(sdata, sdata.Length);
byte[] rdata = udpc.Receive(ref ep);
string status = Encoding.ASCII.GetString(rdata);
Console.WriteLine(status);
if (status == "identified")
{
break;
}
}
}
}
}
Тестова програма має вигляд.
private static UDPServer udpserver = new UDPServer();
private static UDPClient udpclient = new UDPClient();
static void Main(string[] args)
{
Thread serverThread = new Thread(startUDPServer);
serverThread.IsBackground = true;
serverThread.Start();
Console.WriteLine("Press ENTER to test interaction...");
if (Console.ReadKey().Key == ConsoleKey.Enter) ;
{
udpclient.Run();
}
}
private static void startUDPServer()
{
udpserver.Run();
}
Результати тесту показан на рис.
Рис.
Розглянемо приклад посилки однієї датаграми декільком отримувачам. Для цього отправувач відправляє пакети на адресу в діапазоні 224.0.0.1 - 239.255.255.255(Class D address group). Отримувачі можуть увійти в цю групу (join the group) та почати отримувати пакети. Вход в групу здійснюється методом JoinMulticastGroup(addr).
Розглянемо приклад публікації цін на товар «product» сервером, що здійснюється кожні 5 секунд. Для цього на адресу 230.0.0.1 відправляється датаграма, яку отримуєть клієнти, що входять у групу.
Код сервера
public class UDPPublisher
{
private string product = "Product";
public void Run()
{
UdpClient publisher = new UdpClient("230.0.0.1",8899);
Console.WriteLine("Publishing stock prices to 230.0.0.1:8899");
Random gen = new Random();
while(true)
{
double price = 400*gen.NextDouble()+100;
string msg = String.Format("{0} {1:#.00}",product,price);
byte[] sdata = Encoding.ASCII.GetBytes(msg);
publisher.Send(sdata,sdata.Length);
System.Threading.Thread.Sleep(5000);
}
}
}
Код клієнта
class UDPSubscriber
{
public void Run()
{
UdpClient subscriber = new UdpClient(8899);
IPAddress addr = IPAddress.Parse("230.0.0.1");
subscriber.JoinMulticastGroup(addr);
IPEndPoint ep = null;
for(int i=0; i<10;i++){
byte[] pdata = subscriber.Receive(ref ep);
string price = Encoding.ASCII.GetString(pdata);
Console.WriteLine(price);
System.Threading.Thread.Sleep(3000);
}
subscriber.DropMulticastGroup(addr);
}
}
Код тестера
private static UDPPublisher udppublisher = new UDPPublisher();
private static UDPSubscriber udpsubscriber = new UDPSubscriber();
static void Main(string[] args)
{
Thread serverThread = new Thread(startUDPPublisher);
serverThread.IsBackground = true;
serverThread.Start();
Console.WriteLine("Press ENTER to test interaction...");
if (Console.ReadKey().Key == ConsoleKey.Enter) ;
{
udpsubscriber.Run();
Console.WriteLine("Press ESC to exit");
while (true)
{
if (Console.ReadKey().Key == ConsoleKey.Escape)
{
break;
}
}
}
}
private static void startUDPPublisher()
{
udppublisher.Run();
}
Результат
Рис.
Raw
FmainSocket = new Socket( AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP );
FmainSocket.Bind( new IPEndPoint( IPAddress.Parse( FcaptureIP ), 0 ) );
FmainSocket.SetSocketOption( SocketOptionLevel.IP, SocketOptionName.HeaderIncluded, true );
byte[] byTrue = new byte[4] { 1, 0, 0, 0 };
byte[] byOut = new byte[4];
FmainSocket.IOControl( IOControlCode.ReceiveAll, byTrue, byOut );
FmainSocket.BeginReceive( FreceiveBuf, 0, FreceiveBuf.Length, SocketFlags.None, new AsyncCallback( OnReceive ), null );
private void OnReceive ( IAsyncResult _ar ) {
int vReceived = FmainSocket.EndReceive( _ar );
byte[] vReceiveBuf = FreceiveBuf;
// отпарсить принятое
ParseData( vReceiveBuf, vReceived );
FreceiveBuf = new byte[CmaxPacketSize];
FmainSocket.BeginReceive( FreceiveBuf, 0, FreceiveBuf.Length, SocketFlags.None, new AsyncCallback( OnReceiv ), null );
}
private void ParseData ( byte[] _receiveBuf, int _Received ) {
lock(this){
CapturedPackets++;
CapturedBytes += _Received;
}
}
Завдання
-
Реалізувати версію системи публікації-подписки цін на товари. Деклілька серверів публікують свої ціни на товари, клієнти отримують ці ціни, виконують порівняння та виводять в GUI-інтерфейс користувача порівняльну по вказаному товару.
-
Написати простішу програму чат на базі архітектури «клієнт-сервер» з використанням вивчених технологій.
ЛІСТИНГ СЕРВЕРУ ДООПРАЦЮВАННЯ ФЛЮОРОГРАФІЇ (СОКЕТИ)
Клас Form1:
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;
using System.Threading;
using System.IO;
using System.Drawing.Imaging;
namespace SocketServer2
{
public partial class Form1 : Form
{
private MemoryStream ms;
private Image img;
private ThreadedServer ts;
public Form1()
{
InitializeComponent();
ts = new ThreadedServer(11000);
Thread thread1 = new Thread(ts.Start);
thread1.Start();
}
private void button1_Click(object sender, EventArgs e)
{
ms = new MemoryStream(ts.buffer, 0, ts.buffer.Length);
img = Image.FromStream(ms);
pictureBox1.Image = img;
}
private void button2_Click(object sender, EventArgs e)
{
ts.Send(richTextBox1.Text.ToString());
}
}
}