Het eenvoudigste client-server C-programma. Werken met datastromen

Laatste update: 31.10.2015

Laten we eens kijken hoe we met behulp van sockets een server kunnen maken die op het TCP-protocol draait. Algemeen schema werk server-aansluiting TCP ziet er als volgt uit:

De serverprogrammacode ziet er als volgt uit:

Systeem gebruiken; met behulp van System.Text; met behulp van System.Net; met behulp van System.Net.Sockets; naamruimte SocketTcpServer ( class Program ( static int port = 8005; // poort voor het ontvangen van inkomende verzoeken static void Main(string args) ( // haal adressen op voor het starten van de IPEndPoint-socket ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1' ) , port); // maak een socket Socket luisterSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try ( // associeer de socket met een lokaal punt waarop we gegevens zullen ontvangen listenSocket.Bind( ipPoint); / / begin met luisteren luisterSocket.Listen(10); Console.WriteLine("De server is actief. Wacht op verbindingen..."); while (true) (Socket handler = luisterSocket.Accept(); / / krijg het bericht StringBuilder builder = new StringBuilder(); int bytes = 0; // aantal ontvangen bytes byte data = nieuwe byte; // buffer voor ontvangen data do ( bytes = handler.Receive(data); builder.Append( Encoding.Unicode.GetString(data, 0, bytes )); ) while (handler.Available>0); Console.WriteLine(DateTime.Now.ToShortTimeString() + ": " + builder.ToString()); // stuur een antwoordstring message = "uw bericht is afgeleverd"; data = Encoding.Unicode.GetBytes(bericht); handler.Verzenden(gegevens); // sluit de sockethandler.Shutdown(SocketShutdown.Both); handler.Sluiten(); ) ) catch(Uitzondering ex) ( Console.WriteLine(ex.Bericht); ) ) ) )

Nadat we een socket hebben gemaakt, binden we deze eerst aan een lokaal punt met behulp van de Bind-methode:

ListenSocket.Bind(ipPoint);

De socket luistert naar verbindingen op poort 8005 op het lokale adres 127.0.0.1. Dat wil zeggen dat de client verbinding moet maken met het lokale adres en poort 8005.

LuisterSocket.Listen(10);

De Listen-methode wordt alleen aangeroepen na de Bind-methode. Als parameter wordt het aantal inkomende verbindingen gebruikt dat op de socket in de wachtrij kan worden geplaatst.

Na het aanroepen van de Listen-methode begint het luisteren naar inkomende verbindingen en als er verbindingen op de socket aankomen, kunnen deze worden ontvangen met behulp van de Accept-methode:

Sockethandler = luisterSocket.Accept();

De Accept-methode verwijdert het eerste verzoek uit de wachtrij met openstaande verzoeken en creëert een Socket-object om het te verwerken. Als de aanvraagwachtrij leeg is, blokkeert de Accept-methode de aanroepende thread totdat er een nieuwe verbinding beschikbaar komt.

Om het verzoek te verwerken, ontvangen we eerst in de do..while-lus de gegevens met behulp van de methode Ontvangen:

Do ( bytes = handler.Receive(data); builder.Append(Encoding.Unicode.GetString(data, 0, bytes)); ) while (handler.Available > 0);

De methode Ontvangen neemt als parameter een bytearray waarin de ontvangen gegevens worden gelezen, en retourneert het aantal ontvangen bytes.

Als er geen gegevens beschikbaar zijn om te lezen, blokkeert de methode Ontvangen de aanroepende thread totdat er gegevens beschikbaar zijn, tenzij er een time-outwaarde is ingesteld met behulp van het object Socket.ReceiveTimeout. Als de time-outwaarde is overschreden, genereert het object Receiver een SocketException. Om de aanwezigheid van gegevens in een stream bij te houden, wordt de eigenschap Available gebruikt.

Na ontvangst van de gegevens wordt een antwoordbericht naar de client verzonden met behulp van de Send-methode, die een array van bytes als parameter gebruikt:

Handler.Verzenden(gegevens);

Aan het einde van de verwerking van het verzoek moet u de bijbehorende socket sluiten:

Handler.Shutdown(SocketShutdown.Beide); handler.Sluiten();

Als u de methode Shutdown() aanroept voordat u de socket sluit, zorgt u ervoor dat er geen onverwerkte gegevens achterblijven. Deze methode neemt een waarde uit de SocketShutdown-opsomming als parameter:

    Beide: stopt zowel het verzenden als ontvangen van gegevens op de socket

    Ontvangen: stop met het ontvangen van gegevens

    Verzenden: stoppen met het verzenden van gegevens

Cliënt

Laten we nu een project voor de klant toevoegen. Het algemene schema van hoe de client op sockets werkt, zal enigszins anders zijn:

Volledige klantcode:

Systeem gebruiken; met behulp van System.Text; met behulp van System.Net; met behulp van System.Net.Sockets; naamruimte SocketTcpClient ( class Program ( // adres en poort van de server waarmee we verbinding zullen maken static int port = 8005; // serverpoort static string adres = "127.0.0.1"; // serveradres static void Main(string args) ( probeer (IPEndPoint ipPoint = nieuw IPEndPoint(IPAddress.Parse(adres), poort); Socket socket = nieuw Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // maak verbinding met naar een externe host socket.Verbinden(ipPoint); Console.Write("Voer bericht in:"); string bericht = Console.ReadLine(); bytegegevens = Encoding.Unicode.GetBytes(bericht); socket.Verzenden(gegevens); // haal de antwoordgegevens op = nieuwe byte; // buffer voor antwoord StringBuilder builder = new StringBuilder(); int-bytes = 0; // aantal ontvangen bytes do ( bytes = socket.Receive(data, data.Length, 0); builder.Append(Encoding.Unicode.GetString(data, 0, bytes)); ) while (socket.Available > 0) ; Console.WriteLine("serverantwoord: " + builder.ToString()); // sluit de socket-socket.Shutdown(SocketShutdown.Both); socket.Sluiten(); ) catch(Uitzondering ex) ( Console.WriteLine(ex.Message); ) Console.Read(); ) ) )

Voor de client is alles hetzelfde, alleen wordt nu na het maken van de socket de methode Connect() aangeroepen, waaraan het serveradres wordt doorgegeven:

IPEndPoint ipPoint = nieuw IPEndPoint(IPAddress.Parse(adres), poort); socket.Verbinden(ipPoint);

Laten we nu de server- en clientprogramma's starten. Clientconsole:

Voer uw bericht in: hallo wereld serverreactie: uw bericht is afgeleverd

Serverconsole:

De server is actief. Wachten op verbindingen... 22:34: hallo wereld

Laatst bijgewerkt: 31-10-2015

Laten we eens kijken hoe we een multi-threaded client kunnen maken: serverapplicatie. In feite zal het alleen verschillen van een single-threaded thread doordat de verwerking van het verzoek van de klant in een aparte thread zal worden uitgevoerd.

Laten we eerst een project voor de klant maken. Laten we het project ConsoleClient aanroepen en de volgende code definiëren in de Program-klasse:

Systeem gebruiken; met behulp van System.Net.Sockets; met behulp van System.Text; naamruimte ConsoleClient ( class Program ( const int port = 8888; const string adres = "127.0.0.1"; static void Main(string args) ( Console.Write("Voer uw naam in:"); string userName = Console.ReadLine() ; TcpClient client = null; try ( client = new TcpClient(address, port); NetworkStream stream = client.GetStream(); while (true) ( ​​Console.Write(gebruikersnaam + ": "); // voer een bericht in string message = Console.ReadLine(); message = String.Format("(0): (1)", gebruikersnaam, bericht); // converteer het bericht naar een byte array byte data = Encoding.Unicode.GetBytes(message) ; // de berichtenstroom verzenden.Write(data, 0, data.Length); // haal de antwoordgegevens op = nieuwe byte; // buffer voor de ontvangen gegevens StringBuilder builder = new StringBuilder(); int bytes = 0; do ( bytes = stream.Read(data, 0, data.Length); builder.Append(Encoding.Unicode.GetString(data, 0, bytes)); ) while (stream.DataAvailable); message = builder.ToString(); Console.WriteLine("Server: ( 0)", bericht); ) ) catch (Exception ex) ( Console.WriteLine(ex.Message); ) eindelijk (client.Close(); ) ) ) )

In het clientprogramma voert de gebruiker eerst zijn naam in en vervolgens het te verzenden bericht. Bovendien wordt het bericht verzonden in het formaat Naam: bericht.

Na het verzenden van het bericht ontvangt de client het bericht van de server.

Laten we nu een serverproject maken, dat we ConsoleServer zullen noemen. Laten we eerst een nieuwe ClientObject-klasse toevoegen aan het serverproject, die een afzonderlijke verbinding zal vertegenwoordigen:

Systeem gebruiken; met behulp van System.Net.Sockets; met behulp van System.Text; naamruimte ConsoleServer ( public class ClientObject ( publieke TcpClient-client; public ClientObject(TcpClient tcpClient) ( client = tcpClient; ) public void Process() ( NetworkStream-stream = null; try ( stream = client.GetStream(); bytegegevens = nieuwe byte; // buffer voor ontvangen gegevens while (true) ( ​​// krijg het bericht StringBuilder builder = new StringBuilder(); int bytes = 0; do ( bytes = stream.Read(data, 0, data.Length); builder. Append(Encoding .Unicode.GetString(data, 0, bytes)); ) while (stream.DataAvailable); string message = builder.ToString(); Console.WriteLine(message); // stuur het bericht terug naar hoofdletter bericht = bericht.Substring(bericht.IndexOf(teken) + 1).Trim().ToUpper(); data = Encoding.Unicode.GetBytes(bericht); stream.Write(data, 0, data.Lengte); ) ) catch(Uitzondering ex) ( Console.WriteLine(ex.Message); ) eindelijk ( if (stream != null) stream.Close(); if (client != null) client.Close(); ) ) ) )

In deze klasse ontvangen we het bericht daarentegen eerst in de do..while-lus en veranderen het dan een beetje (de dubbele punt afsnijden en converteren naar hoofdletters) en sturen het terug naar de client. Dat wil zeggen dat de klasse ClientObject alle acties bevat voor het werken met een afzonderlijke verbinding.

In de hoofdklasse van het serverproject definiëren we de volgende code:

Systeem gebruiken; met behulp van System.Net; met behulp van System.Net.Sockets; met behulp van System.Threading; naamruimte ConsoleServer ( klasse Programma ( const int poort = 8888; statische TcpListener-luisteraar; statische leegte Main(string args) ( try ( luisteraar = new TcpListener(IPAddress.Parse("127.0.0.1"), poort); luisteraar.Start() ; Console.WriteLine("Wachten op verbindingen..."); while(true) ( ​​TcpClient client = luisteraar.AcceptTcpClient(); ClientObject clientObject = new ClientObject(client); // maak een nieuwe thread om de nieuwe te bedienen client Thread clientThread = nieuwe Thread(nieuwe ThreadStart(clientObject.Process)); clientThread.Start(); ) ) catch(Exception ex) ( Console.WriteLine(ex.Message); ) tenslotte ( if(listener!=null) luisteraar .Stop(); ) ) ) )

Onmiddellijk na het koppelen van een nieuwe klant:

TcpClient-client = luisteraar.AcceptTcpClient()

Er wordt een ClientObject-object gemaakt en er wordt een nieuwe thread gemaakt die de Process-methode van het ClientObject-object uitvoert, waar berichten daadwerkelijk worden ontvangen en verzonden. Zo kan de server meerdere verzoeken tegelijk verwerken.

Resultaten van het programma. Eén van de opdrachtgevers:

Voer je naam in: Evgeniy Evgeniy: hallo wereld Server: HELLO WORLD Evgeniy: dag vrede Server: BYE WORLD Evgeniy: _

Wachten op verbindingen... Evgeniy: hallo wereld Evgeniy: dag wereld Tom: hallo chat

In dit voorbeeld gaan we een eenvoudige server en een eenvoudig clientprogramma ontwikkelen, die ‘op een ontspannen manier’ met elkaar in dialoog gaan. Wij bouwen de klant volgens de technologie Windows-formulieren, en de server- Windows-service . De server beschikt over een reeks kant-en-klare gemarkeerde antwoorden, wacht op gemarkeerde verzoeken van clients en reageert hierop met de juiste berichten. Dit zal ons in staat stellen om nog meer te creëren complex Systeem- het op afstand bekijken van tekeningen uit de database, die we later zullen behandelen.

Een klant aanmaken

Laten we beginnen met een clientprogramma dat in veel gevallen kan worden uitgevoerd ("http://msdn.microsoft.com/ru-ru/library/system.net.sockets.tcplistener.accepttcpclient.aspx")


Tabel 19.7.
Element Eigendom Betekenis
Formulier Tekst Cliënt
Maat 300; 300
Keuzelijst (Naam) keuzelijst
Dok Bovenkant
Lettertype Ariaal; 12pt
Artikelen
  1. Hallo!
  2. Lelik
  3. Wat is er
  4. Zullen we vandaag afspreken?
  5. Tot ziens dan!
SelectieModus Een
Maat 292; 119
Knop (Naam) btnVerzenden
Automatische grootte WAAR
Lettertype Ariaal; 10pt
Plaats 96; 127
Maat 101; 29
Tekst Versturen
Tekstvak (Naam) tekstvak
Dok Onderkant
Plaats 0; 162
Meerdere regels WAAR
Schuifbalken Verticaal
Maat 292; 105

Rest vereiste instellingen We zullen formulierelementen programmatisch toevoegen.

gebruik maken van systeem; met behulp van System.Collections.Generic; met behulp van System.ComponentModel; met behulp van System.Data; gebruik maken van System.Drawing; met behulp van System.Text; met behulp van System.Windows.Forms; // Extra naamruimten met System.IO; met behulp van System.Net; met behulp van System.Net.Sockets; met behulp van System.Threading; naamruimte SimpleClient ( public gedeeltelijke klasse Form1: Form ( int port = 12000; String hostName = "127.0.0.1"; // local TcpClient client = null; // Clientreferentie public Form1() ( InitializeComponent(); // Selecteer de eerste lijstitem listBox.SelectedIndex = 0; listBox.Focus(); // Contextmenu voor het wissen van TextBox ContextMenuStrip contextMenu = nieuw ContextMenuStrip(); textBox.ContextMenuStrip = contextMenu; ToolStripMenuItem item = nieuw ToolStripMenuItem("Wissen"); contextMenu.Items. Add(item); item.MouseDown += new MouseEventHandler(item_MouseDown); ) // Stuur een verzoek en ontvang een antwoord private void btnSubmit_Click(object sender, EventArgs e) ( if (listBox.SelectedIndices.Count == 0) ( MessageBox .Show ("Selecteer bericht"); return; ) try ( // Maak een client verbonden met de server client = new TcpClient(hostName, port); // Stel zelf de grootte van de klemborden in (optioneel!) client.SendBufferSize = client.ReceiveBufferSize = 1024; ) catch ( MessageBox.Show("Server is niet gereed!"); opbrengst; ) // Schrijf het verzoek naar het protocol AddString("Client: " + listBox.SelectedItem.ToString()); // Maak NetworkStream-streams verbonden met de server NetworkStream streamIn = client.GetStream(); NetworkStream streamOut = client.GetStream(); StreamReader readerStream = nieuwe StreamReader(streamIn); StreamWriter writerStream = nieuwe StreamWriter(streamOut); // Stuur een verzoek naar de server writerStream.WriteLine(listBox.SelectedItem.ToString()); writerStream.Flush(); // Lees het antwoord String receiverData = readerStream.ReadLine(); // Schrijf het antwoord naar het protocol AddString("Server: " + receiverData); // Sluit de verbinding en streams, de volgorde is niet belangrijk client.Close(); writerStream.Sluiten(); readerStream.Sluiten(); ) // Een regel toevoegen wanneer het TextBox is ingeschakeld in de Multiline-modus private void AddString(String line) ( StringBuilder sb = new StringBuilder(textBox.Text); StringWriter sw = new StringWriter(sb); sw.WriteLine(line); textBox .Text = sw.ToString(); ) // De lijst wissen via het contextmenu void item_MouseDown(object sender, MouseEventArgs e) ( textBox.Clear(); listBox.Focus(); ) ) )

Nadat we een verbinding met de server hebben ontvangen, creëren we twee threads NetwerkStream en verpak ze in schelpen die handig zijn voor lees-/schrijfcontrole. De uitwisseling met de server wordt weergegeven in het protocol Tekstvak. Om het protocol te wissen, werd dynamisch een contextmenu gemaakt.

Klas TcpClient, die we in de code hebben gebruikt, is een (en vereenvoudigde) wrapper op hoog niveau rond de socket (class Stopcontact). Als socketbeheer op een lager niveau (meer gedetailleerd) vereist is, wordt een link ernaar opgeslagen in de eigenschap TcpClient.Client. Maar aangezien deze eigenschap beschermd is ( beschermd), dan is het alleen toegankelijk via een afgeleide van TcpClient klas.

Als u de toepassing nu uitvoert EenvoudigeClient, dan werkt het wel, maar wanneer je iets naar de server probeert te sturen, verschijnt er een melding dat de server niet gereed is. Er is nog geen server, laten we er een maken.

Een server maken

Laten we het serverprogramma resident maken volgens de sjabloon Windows-service, zoals we in het vorige voorbeeld deden, hoewel je het met de interface kunt doen, is het belangrijkste dat het in één exemplaar moet worden gestart op lokale computer. Als het serverprogramma is opgenomen in het mondiale netwerk, dan wordt het gebruikt IK P en het zou de enige poort in dit netwerk moeten zijn. Daarom is het gebruik van netwerk IK P Voor het wereldwijde netwerk moet u toestemming verkrijgen.



gebruik maken van systeem; met behulp van System.ComponentModel; met behulp van System.Configuration.Install; met behulp van System.ServiceProcess; naamruimte SimpleServer ( // Tijdens de installatie van de assembly moet het installatieprogramma de naam public gedeeltelijke klasse Installer1 hebben: Installer ( private ServiceInstaller serviceInstaller; private ServiceProcessInstaller serviceProcessInstaller; public Installer1() ( // Maak instellingen voor de service serviceInstaller = new ServiceInstaller(); serviceProcessInstaller = new ServiceProcessInstaller( ); // Naam van de service voor de machine en gebruiker serviceInstaller.ServiceName = "SimpleServerServiceName"; serviceInstaller.DisplayName = "SimpleServer"; serviceInstaller.StartType = ServiceStartMode.Manual;// Handmatig starten // Hoe de Service wordt gestart this.serviceProcessInstaller.Account = ServiceAccount.LocalService; this.serviceProcessInstaller.Password = null; this.serviceProcessInstaller.Username = null; // Voeg instellingen toe aan de verzameling van het huidige object this.Installers.AddRange(new Installer ( serviceInstaller, serviceProcessInstaller )); ) ) )

gebruik maken van systeem; met behulp van System.Collections.Generic; met behulp van System.Text; // Extra naamruimten met System.IO; met behulp van System.Net; met behulp van System.Net.Sockets; met behulp van System.Threading; met behulp van System.ServiceProcess; met behulp van System.Collections; naamruimte SimpleServer ( klasse Service1: ServiceBase ( TcpListener server = null; // Link naar server int poort = 12000; String hostName = "127.0.0.1"; // lokaal IP-adres localAddr; String antwoorden = ( "1. Wie ben jij?" , "2. Hallo, Lelik!", "3. Het beste van alles!", "4. Natuurlijk, verder volle kracht”, “5. Tot vanavond!"); // Constructor public Service1() ( localAddr = IPAddress.Parse(hostName); // Converteren naar een ander formaat Thread thread = new Thread(ExecuteLoop); thread.IsBackground = true; thread.Start (); ) private void ExecuteLoop() ( try ( server = new TcpListener(localAddr, port); // Maak een luisterserverserver aan.Start(); // Start de String-gegevensserver; // Eindeloze cyclus luisteren naar clients terwijl (true) ( ​​if (!server.Pending()) // De verzoekenwachtrij is leeg doorgaan; TcpClient client = server.AcceptTcpClient(); // Huidige client // We stellen de grootte van de klemborden in onszelf (optioneel!) // Standaard zijn beide buffers ingesteld op een grootte van 8192 bytes client.SendBufferSize = client.ReceiveBufferSize = 1024 // Verbind NetworkStream en dompel het voor het gemak onder in shells NetworkStream streamIn = client.GetStream(); NetworkStream streamOut = client.GetStream(); StreamReader readerStream = nieuwe StreamReader(streamIn); StreamWriter writerStream = nieuwe StreamWriter(streamOut); // Lees de verzoekgegevens = readerStream.ReadLine(); // Verzend het antwoord int index; if ( int.TryParse(data.Substring(0, data.IndexOf(" .")), out index)) data = antwoorden; else data = data.ToUpper(); writerStream.WriteLine(data); writerStream.Flush(); // Sluit de verbinding en streams, de volgorde is niet belangrijk client.Close(); readerStream.Close(); writerStream.Close(); ) ) catch (SocketException) ( ) eindelijk ( // Stop de server server.Stop( ); ) ) )

Om gegevens te verzenden en een antwoord te ontvangen, klant applicatie creëert een bidirectionele socket (of twee unidirectionele), waarin het adres wordt gespecificeerd voor verbinding met de socket van een andere servertoepassing. Als de verbinding tot stand is gebracht (de server draait), maakt de clienttoepassing verbinding met de socket netwerk stroom NetwerkStream en daardoor gegevens verzendt en ontvangt.

Er is een server aan de andere kant van de verbinding TcpListener luistert in een eindeloze lus verbindingswachtrij met klanten. Als een client er verbinding mee heeft gemaakt ( server.Pending()!=false), dan haalt de server deze client op met behulp van AccepteerTcpClient()- creëert een socket voor ontvangen/verzenden met een gereed retouradres, creëert een bidirectionele stroom (of twee unidirectionele stromen), leest vervolgens het verzoek en verzendt het antwoord.



Houd er rekening mee dat de code van ons serverprogramma niet in een aparte thread is verpakt Draad(uitvoeringsthread), dan zal het besturingssysteem dit programma niet starten in het servicesvenster (probeer het!). De reden is dat in de methodecode ExecuteLoop() De server gebruikt een eindeloze lus van luisteren naar een wachtrij met clientverzoeken. Als deze lus in de hoofdthread van uitvoering blijft ( Draad)-toepassing, gaat deze gewoon in een lus en kan deze niet normaal worden beëindigd. Daarom plaatsen we de code met de lus in een aparte thread (thread) en maken er een achtergrond van, zodat deze samen met de hoofdtoepassingsthread (serverthread) wordt gesloten.

Belangrijke notitie

Stroom NetwerkStream is dubbelzijdig vaste lengte. Methode GetStream() het brengt alleen een adresverbinding tot stand tussen de client- en server-sockets. Maar de werkelijke lengte wordt bepaald door het bericht van de verzendende partij. Je kunt één thread gebruiken voor het ontvangen/verzenden, maar dan mag de lengte van het bericht dat door de server wordt verzonden niet groter zijn dan de lengte van het bericht dat het van de client heeft ontvangen (ik verloor bijna mijn oog!). Daarom gebruiken we aan elke kant twee stromen voor afzonderlijke unidirectionele transmissie tussen twee netwerkverbindingsknooppunten.

Voorbeeld 3. Client-server applicatie voor het bekijken van afbeeldingen uit een database

Op de vorige eenvoudig voorbeeld We maakten (een beetje) kennis met de principes van het maken van netwerkapplicaties. Laten we er nu meer bouwen complex voorbeeld, wanneer de client om afbeeldingen vraagt, en de server deze uit de opslag haalt en naar de client stuurt. IN Oefening 7 We hebben drie verschillende beeldopslagplaatsen en drie weergaveprogramma's ontwikkeld. In dit voorbeeld gebruiken we de database Afbeeldingen.mijn2.mdb met kant-en-klare tekeningen en op basis daarvan gaan we creëren netwerk applicatie(voor degenen die dat niet hebben gedaan Oefening 7, DB is bijgevoegd in de catalogus Data bron).

Een klant opbouwen

Voor de klant gaan we een raamapplicatie bouwen zoals WPF met een gebruikersinterface die gedeeltelijk is geleend van Voorbeeld 6 Oefeningen 7.


De server is niet gereed, even geduld a.u.b.! We proberen contact op te nemen Sorry voor het ongemak...

Om een ​​splashscreen te tonen met de tekst dat de server niet klaar is, hebben we het element gebruikt Kijkdoos, waarin een ander element werd geplaatst Grens met tekstinhoud. Met deze "tuin" kunt u de screensaver vergroten in verhouding tot de grootte van het venster. Echter, de introductie van het element Kijkdoos begint het opnieuw tekenen van de interface merkbaar te vertragen bij het verplaatsen van het venster, omdat het voortdurend probeert de schalen van zijn onderliggende elementen te herberekenen. We hebben alleen namen gegeven aan de interface-elementen die we in procedurele code gaan beheren.

gebruik maken van systeem; met behulp van System.Collections.Generic; met behulp van System.Text; met behulp van System.Windows; met behulp van System.Windows.Controls; met behulp van System.Windows.Data; met behulp van System.Windows.Documents; met behulp van System.Windows.Input; met behulp van System.Windows.Media; met behulp van System.Windows.Media.Animation; met behulp van System.Windows.Media.Imaging; met behulp van System.Windows.Shapes; // Extra naamruimten voor Stream met System.IO; met behulp van IO = System.IO; // Alias ​​voor het adresseren van Path met System.Windows.Threading; // Voor DispatcherTimer // Extra naamruimten voor Socket // met behulp van System.Net; met behulp van System.Net.Sockets; met behulp van System.Collections; // Lijst naamruimte PicturesClientDB ( openbare gedeeltelijke klasse Window1: Window ( int port = 12000; String hostName = "127.0.0.1"; // lokale TcpClient-client = null; // Clientlink String sendMessage = "!!!GetNames!!!"; / / Verzoek om een ​​lijst (van de lastige) Char separator = ( "#"); // Om het antwoord om te zetten in een reeks namen DispatcherTimer timer; // Timer // Constructor public Window1() ( InitializeComponent(); / / Een timer maken en starten timer = new DispatcherTimer(); timer.Tick += new EventHandler(timer_Tick); timer.Interval = TimeSpan.FromSeconds(1); timer.Start(); ) // Initieert een oproep naar de server void timer_Tick(object sender, EventArgs e) ( Execute(listBox); ) private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) ( Execute((ListBox)sender); ) void Execute(ListBox lst) ( // Vul de lijst met een afbeelding namen try ( // Als de server beschikbaar is, maak dan een client client = new TcpClient (hostName, port); ) catch ( // De server is niet gereed, start de timer en sluit af if (Prompt.Visibility != Visibility.Visible ) ( Prompt.Visibility = Zichtbaarheid.Visible; timer.Start(); ) opbrengst; ) switch (sendMessage) ( case "!!!GetNames!!!": // Ontvang en bind de namen van afbeeldingen aan de lijst lst.ItemsSource = GetNames(); // Selecteer het eerste element van de lijst dat SelectionChanged lst moet aanroepen .SelectedIndex = 0; lst.Focus(); sendMessage = ""; break; standaard: // Verberg het bericht en stop de timer als (Prompt.Visibility == Visibility.Visible) ( Prompt.Visibility = Visibility.Hidden; timer .Stop(); ) / / Ontvang de afbeelding en geef deze weer aan de gebruiker met een penseel String name = lst.SelectedValue.ToString(); BitmapImage bi = new BitmapImage(); bi.BeginInit(); // Ontvang de afbeelding van de server en verpak het in een geheugenstroom bi.StreamSource = new MemoryStream (GetPicture(name)); bi.EndInit(); Pictures.picture.ImageSource = bi; // Geef de afbeelding door aan de achtergrond Randafbreking; ) ) private String GetNames() ( Stringnamen; // Maak streams van netwerkverbindingen StreamReader readerStream = nieuwe StreamReader(client.GetStream());StreamWriter writerStream = nieuwe StreamWriter(client.GetStream()); // Stuur een verzoek naar de server writerStream.WriteLine(sendMessage); writerStream.Flush(); // Lees het antwoord String receiverData = readerStream.ReadLine(); namen = receiverData.Split(separator);//Converteren naar een stringarray///Sluit de verbinding en streams, de volgorde is niet belangrijk client.Close(); schrijverStream. Dichtbij(); readerStream.Sluiten(); retour namen; ) Byte GetPicture(String name) ( // Maak streams van netwerkverbindingen NetworkStream readerStream = client.GetStream(); StreamWriter writerStream = new StreamWriter(client.GetStream()); // Stuur een verzoek naar de server writerStream.WriteLine(name ); writerStream.Flush(); // Lees het antwoord // ReceiverBufferSize - buffergrootte voor inkomende gegevens // SendBufferSize - buffergrootte voor uitgaande gegevens Lijst lijst = nieuwe lijst (client.ReceiveBufferSize);//Incrementele capaciteit Byte bytes = nieuwe byte; // Socketbuffergrootte int count = 0; // Gedeelten van binnenkomende gegevens while ((count = readerStream.Read(bytes, 0, bytes.Length)) != 0) for (int i = 0; i< count; i++) list.Add(bytes[i]); // Преобразуем в массив результата bytes = new Byte; list.CopyTo(bytes); // Закрываем соединение и потоки, порядок неважен client.Close(); writerStream.Close(); readerStream.Close(); return bytes; } } // Для привязки к ресурсу class Pictures { // Поле public static ImageBrush picture = new ImageBrush(); static Pictures() { // Дежурный рисунок заставки picture.ImageSource = new BitmapImage(new Uri(@"flower2.jpg", UriKind.Relative)); picture.Stretch = Stretch.Fill; picture.Opacity = 1.0D; } // Привязываемое в интерфейсе свойство public static ImageBrush Picture { get { return picture; } } } }

Houd er rekening mee dat we bij het weergeven van afbeeldingen het traditionele element hebben verlaten Afbeelding, zoals in de vorige oefening werd gedaan. En voor de verandering handelden ze volkomen onconventioneel (in het Turks). Nu zullen we de tekeningen met een penseel weergeven ImageBrush op de achtergrond van een rechthoek Grens door een voorwerp dat eraan vastzit Afbeeldingen. Natuurlijk is het in het echte leven onwaarschijnlijk dat je het zo moet verdraaien, maar deze optie kan ergens van pas komen.


Het splash-scherm verschijnt zodra wordt gedetecteerd dat de server ontbreekt of gestopt is. En nadat de server is gedetecteerd, verdwijnt de screensaver. Dit mechanisme werkt onmiddellijk dankzij de systeemtimer. Er is echter nog geen server en deze moet gemaakt worden.

Het bouwen van een databaseserver als een service
  • Team Bestand/Toevoegen/Nieuw project toevoegen aan oplossing NetwerkStream nieuw project genoemd AfbeeldingenServerDB type Windows-service


gebruik maken van systeem; met behulp van System.Collections.Generic; met behulp van System.ComponentModel; met behulp van System.Data; met behulp van System.Diagnostics; met behulp van System.ServiceProcess; met behulp van System.Text; // Extra naamruimten voor ADO.NET met behulp van System.Data.OleDb; met behulp van System.Data.Common; // Extra naamruimten met System.IO; met behulp van System.Net; met behulp van System.Net.Sockets; met behulp van System.Threading; met behulp van System.Collections; naamruimte PicturesServerDB ( publieke gedeeltelijke klasse Service1: ServiceBase ( int port = 12000; String hostName = "127.0.0.1"; // lokaal IP-adres localAddr; TcpListener-server = null; // Link naar server String separator = "#"; // Separator namen in de antwoordregel String ConnectionString; // Databaseverbindingsreeks public Service1() ( // Extraheer de databaseverbindingsreeks uit het App.config-bestand in het veld ConnectionString = System.Configuration.ConfigurationManager. ConnectionStrings["PicturesDB"].ConnectionString ; // Converteer het IP-adres naar een ander formaat localAddr = IPAddress.Parse(hostName); // Uitvoeren in een nieuwe thread (thread) Thread thread = new Thread(ExecuteLoop); thread.IsBackground = true; thread.Start(); ) private void ExecuteLoop() ( try ( server = new TcpListener(localAddr, port);// Maak een server-listener-server.Start();// Start de server // Eindeloze lus van luisteren naar clients terwijl (true) ( ​​​​// Controleer de verbindingswachtrij als (!server .Pending()) // De verzoekwachtrij is leeg doorgaan; TcpClient-client = server.AcceptTcpClient();// Huidige client // Netwerkverbindingsstreams maken StreamReader readerStream = nieuwe StreamReader(client.GetStream()); NetworkStream streamOut = client.GetStream(); StreamWriter writerStream = nieuwe StreamWriter(streamOut); // Lees de clientopdracht String receiverData = readerStream.ReadLine(); // Herkennen en uitvoeren van switch (receiverData) ( case "!!!GetNames!!!":// Verzend namen gescheiden door een scheidingsteken String namen = GetNames(); writerStream.WriteLine(names); // Gebruik writerStream.Flush door de shell (); break; default:// Verzend de afbeelding Byte bytes = GetPicture(receiverData); streamOut.Write(bytes, 0, bytes.Length); // Gebruik direct streamOut.Flush(); break; ) // Sluit de verbinding en threads, de volgorde maakt niet uit client.Close(); readerStream.Sluiten(); writerStream.Sluiten(); ) ) eindelijk ( // Stop de server server.Stop(); ) ) // Haal afbeeldingsnamen op uit de database en verpak ze in één regel voor verzending naar de clientstring GetNames() ( // Maak en configureer de ADO-infrastructuur. NET OleDbConnection conn = nieuwe OleDbConnection(connectionString); OleDbCommand cmd = new OleDbCommand("SELECTEER Bestandsnaam UIT MijnTabel"); cmd.Verbinding = verbinding; conn.Open(); // Afbeeldingsnamen ophalen OleDbDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection); // Vorm een ​​reeks uitgaande gegevens StringBuilder sb = new StringBuilder(); foreach (DbDataRecord-record in lezer)// Equivalent aan het lezen van reader.Read() sb.Append(((string)record["Bestandsnaam"]).Trim() + separator); // De verbinding hier sluit het DataReader-object zelf na het lezen van alle gegevens // in overeenstemming met de conventie toen het werd gemaakt CommandBehavior.CloseConnection // Verwijder het extra laatste teken van het scheidingsteken sb.Replace(separator, String.Empty, sb.ToString().LastIndexOf(separator ), separator.Length); retourneer sb.ToString(); ) // De afbeelding zelf ophalen uit de database om deze naar de clientbyte GetPicture(String name) te sturen ( // Maak en configureer de ADO.NET OleDbConnection-infrastructuur conn = new OleDbConnection(); conn.ConnectionString = connectionString; // Creëer en configureer een opdrachtobject geparametriseerd op afbeeldingsnaam OleDbCommand cmd = new OleDbCommand(); cmd.Connection = conn; cmd.CommandType = CommandType.Text; // Optioneel! Standaard ingesteld cmd.CommandText = "SELECTEER afbeelding UIT MijnTabel WAAR Bestandsnaam=? "; cmd.Parameters .Add(new OleDbParameter()); cmd.Parameters.Value = naam;// Naam van de afbeelding OleDbDataAdapter adapter = nieuwe OleDbDataAdapter(cmd); // De afbeelding ophalen uit de database DataTable table = nieuwe DataTable (); adapter.Fill(table) ; byte bytes = (byte)table.Rows["Picture"]; // Maak verbinding met de afbeeldingsretourbytes; ) ) )

Client-server-applicatie op streaming TCP-socket

In het volgende voorbeeld wordt TCP gebruikt om ordelijke, betrouwbare bytestromen in twee richtingen te leveren. Laten we een complete applicatie bouwen die een client en een server bevat. Eerst demonstreren we hoe we een server kunnen bouwen met behulp van TCP-stream-sockets, en vervolgens een clienttoepassing om onze server te testen.

Het volgende programma maakt een server die verbindingsverzoeken van clients ontvangt. De server is synchroon gebouwd, daarom wordt de uitvoering van threads geblokkeerd totdat de server ermee instemt verbinding te maken met de client. Deze applicatie demonstreert een eenvoudige server die reageert op een client. De client beëindigt de verbinding door een bericht naar de server te sturen .

TCP-server

De creatie van de serverstructuur wordt weergegeven in het volgende functionele diagram:

Hier volledige code SocketServer.cs-programma's:

// SocketServer.cs met behulp van System; met behulp van System.Text; met behulp van System.Net; met behulp van System.Net.Sockets; naamruimte SocketServer ( class Program ( static void Main(string args) ( // Stel het lokale eindpunt voor de socket in IPHostEntry ipHost = Dns.GetHostEntry("localhost"); IPAddress ipAddr = ipHost.AddressList; IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 11000); // Maak een Tcp/Ip-socket Socket sListener = new Socket(ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // Wijs de socket toe aan het lokale eindpunt en luister naar inkomende sockets try ( sListener.Bind( ipEndPoint); sListener. Listen(10); // Begin met luisteren naar verbindingen terwijl (true) ( ​​Console.WriteLine("Wachten op een verbinding op poort (0)", ipEndPoint); // Het programma pauzeert tijdens het wachten inkomende verbinding Socket-handler = sListener.Accept(); tekenreeksgegevens = nul; // We hebben gewacht tot de client verbinding met ons probeerde te maken byte bytes = nieuwe byte; int bytesRec = handler. Ontvangen (bytes); gegevens += Encoding.UTF8.GetString(bytes, 0, bytesRec); // Toon de gegevens op de console Console.Write("Ontvangen tekst: " + data + "\n\n"); // Stuur een antwoord naar de klant\ string antwoord = "Bedankt voor het verzoek in " + data.Length.ToString() + " karakters"; byte msg = Encoding.UTF8.GetBytes(antwoord); handler.Verzenden (bericht); if (data.IndexOf(" ") > -1) ( Console.WriteLine("De server heeft de verbinding met de client beëindigd."); break; ) handler.Shutdown(SocketShutdown.Both); handler.Close(); ) ) catch (Exception ex) ( Console.WriteLine (ex.ToString()); ) tenslotte ( Console.ReadLine(); ) ) ) )

Laten we eens kijken naar de structuur van dit programma.

De eerste stap is het instellen van de socket op een lokaal eindpunt. Voordat u een socket opent om naar verbindingen te luisteren, moet u er een lokaal eindpuntadres voor voorbereiden. Een uniek TCP/IP-serviceadres wordt bepaald door de combinatie van het IP-adres van de host met het servicepoortnummer, waardoor het service-eindpunt wordt gecreëerd.

De klasse Dns biedt methoden die informatie retourneren over netwerkadressen ondersteund door het apparaat op het lokale netwerk. Als een LAN-apparaat meer dan één netwerkadres heeft, retourneert de klasse Dns informatie over alle netwerkadressen en moet de toepassing het juiste adres selecteren om vanuit de array te bedienen.

Laten we een IPEndPoint voor de server maken door het eerste IP-adres van de hostcomputer verkregen via de Dns.Resolve() -methode te combineren met het poortnummer:

IPHostEntry ipHost = Dns.GetHostEntry("localhost"); IP-adres ipAddr = ipHost.AddressList; IPEndPoint ipEndPoint = nieuw IPEndPoint(ipAddr, 11000);

Hier vertegenwoordigt de IPEndPoint-klasse localhost op poort 11000. Vervolgens maken we een stream-socket met een nieuw exemplaar van de Socket-klasse. Nadat we een lokaal eindpunt hebben opgezet om naar verbindingen te luisteren, kunnen we een socket maken:

Socket sListener = nieuwe Socket(ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

Overdracht AdresFamilie specificeert de adresseringsschema's die een exemplaar van de Socket-klasse kan gebruiken om een ​​adres op te lossen.

In de parameter SocketType TCP- en UDP-sockets zijn verschillend. Daarin kunt u onder andere de volgende waarden definiëren:

Dgram

Ondersteunt datagrammen. Voor de Dgram-waarde moet Udp worden opgegeven voor het protocoltype en InterNetwork in de adresfamilieparameter.

Rauw

Ondersteunt toegang tot het onderliggende transportprotocol.

Stroom

Ondersteunt stream-sockets. Voor de Stream-waarde moet Tcp worden opgegeven voor het protocoltype.

De derde en laatste parameter specificeert het protocoltype dat vereist is voor de socket. In de parameter Protocoltype U kunt het volgende het meest aangeven belangrijke waarden- Tcp, Udp, Ip, Rauw.

De volgende stap zou moeten zijn om de socket toe te wijzen met behulp van de methode Binden(). Wanneer een socket door een constructor wordt geopend, krijgt deze geen naam toegewezen, maar wordt alleen een handle gereserveerd. De methode Bind() wordt aangeroepen om een ​​naam aan de serversocket toe te wijzen. Om een ​​client-socket een TCP-stream-socket te laten identificeren, serverprogramma moet een naam geven aan de socket:

SListener.Bind(ipEndPoint);

De methode Bind() bindt een socket aan een lokaal eindpunt. De methode Bind() moet worden aangeroepen vóór elke poging om de methoden Listen() en Accept() aan te roepen.

Nu u een socket hebt gemaakt en er een naam aan hebt gekoppeld, kunt u met behulp van deze methode naar inkomende berichten luisteren Luisteren(). In de luisterstatus luistert de socket naar inkomende verbindingspogingen:

SListener.Luister(10);

De parameter definieert achterstand, waarmee het maximale aantal verbindingen in de wachtrij wordt aangegeven. In de bovenstaande code zorgt de parameterwaarde ervoor dat er maximaal tien verbindingen in de wachtrij kunnen worden verzameld.

In de luisterstatus moet u bereid zijn om verbinding te maken met de cliënt waarvoor de methode wordt gebruikt Aanvaarden(). Met deze methode wordt een clientverbinding verkregen en wordt de koppeling van de client- en servernaam voltooid. De methode Accept() blokkeert de thread van het aanroepende programma totdat er een verbinding tot stand komt.

De methode Accept() verwijdert het eerste verbindingsverzoek uit de wachtrij met openstaande verzoeken en creëert een nieuwe socket om het te verwerken. Hoewel er een nieuwe socket wordt gemaakt, blijft de oorspronkelijke socket luisteren en kan deze met multi-threading worden gebruikt om meerdere verbindingsverzoeken van clients te accepteren. Geen enkele servertoepassing mag een luistersocket sluiten. Het moet blijven werken naast sockets die zijn gemaakt door de Accept-methode om inkomende clientverzoeken te verwerken.

While (true) ( ​​Console.WriteLine("Wachten op een verbinding op poort (0)", ipEndPoint); // Het programma pauzeert tijdens het wachten op een inkomende verbinding Socket handler = sListener.Accept();

Zodra de client en server een verbinding met elkaar tot stand hebben gebracht, kunnen met behulp van deze methoden berichten worden verzonden en ontvangen Versturen() En Ontvangen() klasse stopcontact.

De Send()-methode schrijft uitgaande gegevens naar de aangesloten socket. De methode Receiver() leest binnenkomende gegevens in de stream-socket. Wanneer u een op TCP gebaseerd systeem gebruikt, moet er een verbinding tot stand worden gebracht tussen de sockets voordat de methoden Send() en Receiver() worden uitgevoerd. Het exacte protocol tussen de twee communicerende entiteiten moet vooraf worden gedefinieerd, zodat de client- en serverapplicaties elkaar niet blokkeren omdat ze niet weten wie hun gegevens als eerste moet verzenden.

Wanneer de gegevensuitwisseling tussen de server en de client is voltooid, moet u de verbinding verbreken met behulp van de methoden Afsluiten() En Dichtbij():

Handler.Shutdown(SocketShutdown.Beide); handler.Sluiten();

SocketShutdown is een enum met drie waarden om te stoppen: Beide- stopt met het verzenden en ontvangen van gegevens via de socket, Ontvangen- stopt de socket met het ontvangen van gegevens en Versturen- stopt met het verzenden van gegevens via de socket.

De socket wordt gesloten door de methode Close() aan te roepen, die ook de eigenschap Connected van de socket instelt op false.

TCP-client

De functies die worden gebruikt om een ​​clientapplicatie te maken zijn min of meer vergelijkbaar met een serverapplicatie. Net als bij de server worden dezelfde methoden gebruikt om het eindpunt te bepalen, de socket te instantiëren, gegevens te verzenden en te ontvangen en de socket te sluiten.