โปรแกรม C ไคลเอนต์เซิร์ฟเวอร์ที่ง่ายที่สุด การทำงานกับสตรีมข้อมูล

อัพเดทล่าสุด: 31.10.2015

มาดูวิธีสร้างเซิร์ฟเวอร์ที่ทำงานบนโปรโตคอล TCP โดยใช้ซ็อกเก็ต โครงการทั่วไปงาน ซ็อกเก็ตเซิร์ฟเวอร์ TCP จะเป็นดังนี้:

รหัสโปรแกรมเซิร์ฟเวอร์จะเป็นดังนี้:

การใช้ระบบ; ใช้ System.Text; ใช้ System.Net; ใช้ System.Net.Sockets; เนมสเปซ SocketTcpServer ( คลาสโปรแกรม ( static int port = 8005; // พอร์ตสำหรับรับคำขอขาเข้า คงเป็นโมฆะ Main(args สตริง) ( // รับที่อยู่สำหรับการเปิดตัวซ็อกเก็ต IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1" ) , พอร์ต); // สร้างซ็อกเก็ต Socket ListenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); ลอง ( // ผูกซ็อกเก็ตเข้ากับจุดท้องถิ่นซึ่งเราจะได้รับข้อมูล ListenSocket.Bind( ipPoint); / / เริ่มฟัง ListenSocket.Listen(10); Console.WriteLine("เซิร์ฟเวอร์กำลังทำงาน กำลังรอการเชื่อมต่อ..."); ในขณะที่ (จริง) ( ​​Socket handler = ListenSocket.Accept(); // รับข้อความ StringBuilder builder = new StringBuilder( ); int bytes = 0; // จำนวนไบต์ที่ได้รับข้อมูลไบต์ = ไบต์ใหม่; // บัฟเฟอร์สำหรับข้อมูลที่ได้รับ ทำ ( bytes = handler.Receive(data); builder.Append(Encoding .Unicode.GetString(ข้อมูล, 0, ไบต์ )); ในขณะที่ (handler.Available>0); Console.WriteLine(DateTime.Now.ToShortTimeString() + ": " + builder.ToString());

// send a string message ตอบกลับ = "ข้อความของคุณถูกส่งแล้ว";

ข้อมูล = Encoding.Unicode.GetBytes (ข้อความ);

ตัวจัดการส่ง (ข้อมูล);

// ปิดตัวจัดการซ็อกเก็ตการปิดระบบ (SocketShutdown.Both);

ตัวจัดการ.ปิด();

) ) catch(ข้อยกเว้น เช่น) ( Console.WriteLine(ex.Message); ) ) ) )

ขั้นแรก หลังจากสร้างซ็อกเก็ตแล้ว เราจะผูกมันเข้ากับจุดในพื้นที่โดยใช้วิธี Bind:

เมธอด Accept จะลบคำขอแรกออกจากคิวคำขอที่รอดำเนินการ และสร้างออบเจ็กต์ Socket เพื่อประมวลผล หากคิวคำขอว่างเปล่า วิธีการยอมรับจะบล็อกเธรดการโทรจนกว่าการเชื่อมต่อใหม่จะพร้อมใช้งาน

ในการประมวลผลคำขอ อันดับแรกใน do.. While loop เราได้รับข้อมูลโดยใช้วิธีรับ:

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

วิธีการรับจะใช้อาร์เรย์ไบต์เพื่อใช้อ่านข้อมูลที่ได้รับเป็นพารามิเตอร์ และส่งกลับจำนวนไบต์ที่ได้รับ

หากไม่มีข้อมูลที่พร้อมให้อ่าน วิธีการรับจะบล็อกเธรดการเรียกจนกว่าข้อมูลจะพร้อมใช้งาน เว้นแต่ว่าค่าการหมดเวลาได้รับการตั้งค่าโดยใช้วัตถุ Socket.ReceiveTimeout หากเกินค่าการหมดเวลา วัตถุที่ได้รับจะส่ง SocketException เพื่อติดตามการมีอยู่ของข้อมูลในสตรีม จะใช้คุณสมบัติ Available

หลังจากได้รับข้อมูล ข้อความตอบกลับจะถูกส่งไปยังไคลเอนต์โดยใช้วิธีการส่ง ซึ่งรับอาร์เรย์ไบต์เป็นพารามิเตอร์:

Handler.Send (ข้อมูล);

เมื่อสิ้นสุดการประมวลผลคำขอ คุณจะต้องปิดซ็อกเก็ตที่เกี่ยวข้อง:

Handler.Shutdown (SocketShutdown.Both); ตัวจัดการ.ปิด();

การเรียกเมธอด Shutdown() ก่อนที่จะปิดซ็อกเก็ตจะทำให้แน่ใจได้ว่าไม่มีข้อมูลที่ยังไม่ได้ประมวลผลเหลืออยู่ วิธีการนี้รับค่าจากการแจงนับ SocketShutdown เป็นพารามิเตอร์:

    ทั้งสอง: หยุดทั้งการส่งและรับข้อมูลบนซ็อกเก็ต

    รับ: หยุดรับข้อมูล

    ส่ง: หยุดส่งข้อมูล

ลูกค้า

ตอนนี้เรามาเพิ่มโครงการสำหรับลูกค้า รูปแบบทั่วไปของวิธีการทำงานของไคลเอนต์บนซ็อกเก็ตจะแตกต่างกันเล็กน้อย:

รหัสลูกค้าแบบเต็ม:

การใช้ระบบ; ใช้ System.Text; ใช้ System.Net; ใช้ System.Net.Sockets; namespace SocketTcpClient ( class Program ( // ที่อยู่และพอร์ตของเซิร์ฟเวอร์ที่เราจะเชื่อมต่อคงที่ int port = 8005; // ที่อยู่สตริงคงที่ของพอร์ตเซิร์ฟเวอร์ = "127.0.0.1"; // ที่อยู่เซิร์ฟเวอร์ static void Main(string args) ( ลอง ( IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(address), port); Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); // เชื่อมต่อกับ ไปยังโฮสต์ระยะไกลซ็อกเก็ตเชื่อมต่อ (ipPoint);

Console.Write("ใส่ข้อความ:");

ข้อความสตริง = Console.ReadLine();

ข้อมูลไบต์ = Encoding.Unicode.GetBytes (ข้อความ);

ซ็อกเก็ตส่ง (ข้อมูล);

// รับข้อมูลคำตอบ = ไบต์ใหม่; // บัฟเฟอร์สำหรับการตอบสนอง StringBuilder builder = new StringBuilder();

ไบต์ int = 0; // จำนวนไบต์ที่ได้รับ ทำ ( bytes = socket.Receive(data, data.Length, 0); builder.Append(Encoding.Unicode.GetString(data, 0, bytes)); ) ในขณะที่ (socket.Available > 0) ;

Console.WriteLine("การตอบสนองของเซิร์ฟเวอร์: " + builder.ToString());

// ปิดซ็อกเก็ต socket.Shutdown(SocketShutdown.Both); ซ็อกเก็ต.ปิด();) catch(ข้อยกเว้น เช่น) ( Console.WriteLine(ex.Message); ) Console.Read();

-

การใช้ระบบ; ใช้ System.Net.Sockets; ใช้ System.Text; namespace ConsoleClient ( class Program ( const int port = 8888; const string address = "127.0.0.1"; static void Main(string args) ( Console.Write("Enter your name:"); string ชื่อผู้ใช้ = Console.ReadLine() ; TcpClient client = null; try ( client = new TcpClient(address, port); NetworkStream stream = client.GetStream(); ในขณะที่ (true) ( ​​Console.Write(userName + ": "); // ป้อนข้อความ ข้อความสตริง = Console.ReadLine(); message = String.Format("(0): (1)", ชื่อผู้ใช้, ข้อความ); // แปลงข้อความเป็นไบต์อาร์เรย์ byte data = Encoding.Unicode.GetBytes(message) ; stream.Write(data, 0, data.Length); // รับข้อมูลตอบกลับ = ไบต์ใหม่; // บัฟเฟอร์สำหรับข้อมูลที่ได้รับ StringBuilder = new StringBuilder(); 0, data.Length); builder.Append(Encoding.Unicode.GetString(data, 0, bytes)); ในขณะที่ (stream.DataAvailable); message = builder.ToString(); ) ) catch (ข้อยกเว้นเช่น) ( Console.WriteLine (เช่นข้อความ);

) ในที่สุด ( client.Close(); ) ) ) )

ในโปรแกรมไคลเอนต์ ผู้ใช้จะต้องป้อนชื่อของตนก่อนแล้วตามด้วยข้อความที่จะส่ง นอกจากนี้ข้อความจะถูกส่งในรูปแบบชื่อ: ข้อความ

หลังจากส่งข้อความแล้ว ลูกค้าจะได้รับข้อความจากเซิร์ฟเวอร์

ตอนนี้เรามาสร้างโปรเจ็กต์เซิร์ฟเวอร์กันดีกว่า ซึ่งเราจะเรียกว่า ConsoleServer ขั้นแรก ให้เพิ่มคลาส ClientObject ใหม่ให้กับโปรเจ็กต์เซิร์ฟเวอร์ ซึ่งจะแสดงการเชื่อมต่อแยกต่างหาก: การใช้ระบบ; ใช้ System.Net.Sockets; ใช้ System.Text; namespace ConsoleServer ( ClientObject คลาสสาธารณะ (ไคลเอ็นต์ TcpClient สาธารณะ; public ClientObject(TcpClient tcpClient) ( client = tcpClient; ) กระบวนการโมฆะสาธารณะ () ( NetworkStream stream = null; ลอง ( stream = client.GetStream(); byte data = ไบต์ใหม่; // บัฟเฟอร์สำหรับข้อมูลที่ได้รับในขณะที่ (จริง) ( ​​// รับข้อความ StringBuilder builder = new StringBuilder(); int bytes = 0; do ( bytes = stream.Read(data, 0, data.Length); builder. ผนวก(Encoding .Unicode.GetString(data, 0, bytes)); while (stream.DataAvailable); string message = builder.ToString(); // ส่งข้อความกลับมาที่ message = message.Substring(message.IndexOf(///) + 1).Trim().ToUpper();

ข้อมูล = Encoding.Unicode.GetBytes (ข้อความ);

stream.Write (ข้อมูล, 0, data.Length);

) ) catch(ข้อยกเว้น เช่น) ( Console.WriteLine(ex.Message); ) ในที่สุด ( if (stream != null) stream.Close(); if (client != null) client.Close(); ) ) ) )

ในทางกลับกัน ในคลาสนี้ เราได้รับข้อความใน do.. While loop ก่อน จากนั้นจึงเปลี่ยนแปลงเล็กน้อย (ตัดโคลอนออกแล้วแปลงเป็นตัวพิมพ์ใหญ่) แล้วส่งกลับไปยังไคลเอนต์ นั่นคือคลาส ClientObject มีการดำเนินการทั้งหมดสำหรับการทำงานกับการเชื่อมต่อแยกต่างหาก

ในคลาสหลักของโครงการเซิร์ฟเวอร์ เราจะกำหนดรหัสต่อไปนี้:

การใช้ระบบ; ใช้ System.Net; ใช้ System.Net.Sockets; โดยใช้ System.Threading; namespace ConsoleServer ( class Program ( const int port = 8888; static TcpListener Listener; static void Main(string args) ( ลอง ( Listener = new TcpListener(IPAddress.Parse("127.0.0.1"), port); Listener.Start() ; Console.WriteLine("กำลังรอการเชื่อมต่อ..."); ในขณะที่ (true) ( ​​​​TcpClient client = Listener.AcceptTcpClient(); ClientObject clientObject = new ClientObject(client); // สร้างเธรดใหม่เพื่อให้บริการใหม่ เธรดไคลเอนต์ clientThread = เธรดใหม่ (ThreadStart ใหม่ (clientObject.Process)); clientThread.Start (); ) catch (ข้อยกเว้นเช่น) ( Console.WriteLine (ex.Message); ) ในที่สุด ( if (listener! = null) Listener หยุด(); ) ) ) )

ทันทีหลังจากเชื่อมต่อไคลเอนต์ใหม่:

ไคลเอ็นต์ TcpClient = Listener.AcceptTcpClient()

ออบเจ็กต์ ClientObject ถูกสร้างขึ้นและมีการสร้างเธรดใหม่ซึ่งเรียกใช้เมธอด Process ของออบเจ็กต์ ClientObject โดยที่ข้อความได้รับและส่งข้อความจริง ดังนั้นเซิร์ฟเวอร์จะสามารถประมวลผลคำขอหลายรายการพร้อมกันได้

ผลลัพธ์ของโปรแกรม หนึ่งในลูกค้า: กรอกชื่อของคุณ: Evgeniy Evgeniy: สวัสดีชาวโลก เซิร์ฟเวอร์: HELLO WORLD Evgeniy: บายสันติภาพ เซิร์ฟเวอร์: BYE WORLD Evgeniy: _กำลังรอการเชื่อมต่อ... Evgeniy: สวัสดีชาวโลก Evgeniy: บายชาวโลก ทอม: สวัสดีแชท ในตัวอย่างนี้ เราจะพัฒนาเซิร์ฟเวอร์แบบธรรมดาและโปรแกรมไคลเอนต์แบบธรรมดาที่ดำเนินการสนทนาแบบ "สบายๆ" ระหว่างกัน เราจะสร้างลูกค้าตามเทคโนโลยี แบบฟอร์ม Windows และเซิร์ฟเวอร์ -บริการวินโดวส์

- เซิร์ฟเวอร์จะมีชุดการตอบกลับที่ทำเครื่องหมายไว้สำเร็จรูป รอคำขอที่ทำเครื่องหมายไว้จากไคลเอนต์ และตอบกลับด้วยข้อความที่เหมาะสม ซึ่งจะทำให้เราพร้อมที่จะสร้างสรรค์มากยิ่งขึ้น

เริ่มจากโปรแกรมไคลเอนต์ที่สามารถทำงานได้ในหลาย ๆ กรณี ("http://msdn.microsoft.com/ru-ru/library/system.net.sockets.tcplistener.accepttcpclient.aspx")


ตารางที่ 19.7
องค์ประกอบ คุณสมบัติ ความหมาย
รูปร่าง ข้อความ ลูกค้า
ขนาด 300; 300
กล่องรายการ (ชื่อ) กล่องรายการ
ท่าเรือ สูงสุด
แบบอักษร อาเรียล; 12แต้ม
รายการ
  1. สวัสดี!
  2. เลลิค
  3. ชีวิตเป็นยังไงบ้าง
  4. วันนี้เราจะออกไปเที่ยวกันไหม?
  5. ถ้าอย่างนั้น ลาก่อน!
โหมดการเลือก หนึ่ง
ขนาด 292; 119
ปุ่ม (ชื่อ) btnส่ง
ขนาดอัตโนมัติ จริง
แบบอักษร อาเรียล; 10แต้ม
ที่ตั้ง 96; 127
ขนาด 101; 29
ข้อความ ส่ง
กล่องข้อความ (ชื่อ) กล่องข้อความ
ท่าเรือ ด้านล่าง
ที่ตั้ง 0; 162
หลายบรรทัด จริง
แถบเลื่อน แนวตั้ง
ขนาด 292; 105

พักผ่อน การตั้งค่าที่จำเป็นเราจะเพิ่มองค์ประกอบของแบบฟอร์มโดยทางโปรแกรม

ใช้ระบบ; ใช้ System.Collections.Generic; โดยใช้ System.ComponentModel; ใช้ System.Data; โดยใช้ระบบการวาดภาพ; ใช้ System.Text; โดยใช้ System.Windows.Forms; // เนมสเปซเพิ่มเติมโดยใช้ System.IO; ใช้ System.Net; ใช้ System.Net.Sockets; โดยใช้ System.Threading; เนมสเปซ SimpleClient ( คลาสสาธารณะบางส่วน Form1: ฟอร์ม ( int port = 12000; String hostName = "127.0.0.1"; // local TcpClient client = null; // ไคลเอ็นต์อ้างอิง public Form1() ( InitializeComponent(); // เลือกอันแรก รายการ listBox.SelectedIndex = 0; listBox.Focus(); // เมนูบริบทสำหรับการล้าง TextBox ContextMenuStrip contextMenu = new ContextMenuStrip(); textBox.ContextMenuStrip = contextMenuItem("Clear"); .MouseDown += new MouseEventHandler(item_MouseDown); // ส่งคำขอและรับการตอบกลับส่วนตัว btnSubmit_Click(ผู้ส่งวัตถุ, EventArgs e) ( if (listBox.SelectedIndices.Count == 0) ( MessageBox.Show ("เลือกข้อความ" ); return; ) ลอง ( // สร้างไคลเอ็นต์ที่เชื่อมต่อกับเซิร์ฟเวอร์ client = new TcpClient(hostName, port); // กำหนดขนาดของคลิปบอร์ดด้วยตัวเอง (ไม่บังคับ!) client.SendBufferSize = client.ReceiveBufferSize = 1024 ; ) catch ( MessageBox.Show("เซิร์ฟเวอร์ไม่พร้อม!");

กลับ; ) // เขียนคำขอไปยังโปรโตคอล AddString("Client: " + listBox.SelectedItem.ToString()); เครือข่ายสตรีมและบรรจุลงในเปลือกที่สะดวกสำหรับการควบคุมการอ่าน/การเขียน การแลกเปลี่ยนกับเซิร์ฟเวอร์จะแสดงในโปรโตคอล กล่องข้อความ- เพื่อล้างโปรโตคอล เมนูบริบทจะถูกสร้างขึ้นแบบไดนามิก

ระดับ ทีซีพีไคลเอ็นต์ซึ่งเราใช้ในโค้ดนั้นเป็น wrapper ระดับสูง (และทำให้ง่ายขึ้น) รอบซ็อกเก็ต (class ซ็อกเก็ต- หากจำเป็นต้องมีการควบคุมซ็อกเก็ตระดับล่าง (รายละเอียดเพิ่มเติม) ลิงก์ไปยังซ็อกเก็ตนั้นจะถูกจัดเก็บไว้ในคุณสมบัติ TcpClient.Client แต่เนื่องจากทรัพย์สินนี้ได้รับการคุ้มครอง ( ได้รับการคุ้มครอง) จากนั้นจะสามารถเข้าถึงได้จากอนุพันธ์ของเท่านั้น ทีซีพีไคลเอ็นต์ระดับ.

หากคุณเรียกใช้แอปพลิเคชันตอนนี้ SimpleClientแล้วจะใช้งานได้ แต่เมื่อคุณพยายามส่งบางอย่างไปยังเซิร์ฟเวอร์ ข้อความจะปรากฏขึ้นว่าเซิร์ฟเวอร์ไม่พร้อม ยังไม่มีเซิร์ฟเวอร์ มาสร้างเซิร์ฟเวอร์กันดีกว่า

การสร้างเซิร์ฟเวอร์

มาสร้างโปรแกรมเซิร์ฟเวอร์ตามเทมเพลตกันเถอะ บริการวินโดวส์อย่างที่เราทำในตัวอย่างก่อนหน้านี้ แม้ว่าคุณจะสามารถทำได้ด้วยอินเทอร์เฟซ แต่สิ่งสำคัญคือมันควรจะเปิดตัวในสำเนาเดียวบน คอมพิวเตอร์ท้องถิ่น- หากโปรแกรมเซิร์ฟเวอร์รวมอยู่ในเครือข่ายทั่วโลกแล้วใช้งานด้วย ไอพีและควรเป็นพอร์ตเดียวในเครือข่ายนี้ ดังนั้นการใช้งานระบบเครือข่าย ไอพีสำหรับเครือข่ายทั่วโลก คุณต้องได้รับอนุญาต



ใช้ระบบ; โดยใช้ System.ComponentModel; โดยใช้ System.Configuration.Install; โดยใช้ System.ServiceProcess; namespace SimpleServer ( // ระหว่างการติดตั้งแอสเซมบลี ควรเรียกตัวติดตั้งคลาสสาธารณะบางส่วน Installer1: ตัวติดตั้ง ( ServiceInstaller serviceInstaller ส่วนตัว; ServiceProcessInstaller ส่วนตัว serviceProcessInstaller; Public Installer1() ( // สร้างการตั้งค่าสำหรับบริการ serviceInstaller = new ServiceInstaller(); serviceProcessInstaller = new ServiceProcessInstaller( ); // ชื่อบริการสำหรับเครื่องและผู้ใช้ serviceInstaller.ServiceName = "SimpleServerServiceName"; serviceInstaller.DisplayName = "SimpleServer"; เริ่มต้น this.serviceProcessInstaller.Account = ServiceAccount.LocalService; this.serviceProcessInstaller.Password = null; this.serviceProcessInstaller.Username = null; // เพิ่มการตั้งค่าให้กับคอลเลกชันของวัตถุปัจจุบัน this.Installers.AddRange(ตัวติดตั้งใหม่ ( serviceInstaller, บริการกระบวนการติดตั้ง ) ))

ใช้ระบบ; ใช้ System.Collections.Generic; ใช้ System.Text; // เนมสเปซเพิ่มเติมโดยใช้ System.IO; ใช้ System.Net; ใช้ System.Net.Sockets; โดยใช้ System.Threading; โดยใช้ System.ServiceProcess; ใช้ System.Collections; namespace SimpleServer ( class Service1: ServiceBase ( เซิร์ฟเวอร์ TcpListener = null; // ลิงก์ไปยังเซิร์ฟเวอร์ int port = 12000; String hostName = "127.0.0.1"; // IPAddress ท้องถิ่น localAddr; คำตอบของสตริง = ( "1. คุณเป็นใคร?" , "2. สวัสดี Lelik!", "3. ดีที่สุด!", "4. แน่นอนต่อไป ระเบิดเต็ม","5. เจอกันตอนเย็น!"); // Constructor public Service1() ( localAddr = IPAddress.Parse(hostName); // แปลงเป็นรูปแบบอื่น Thread thread = new Thread(ExecuteLoop); thread.IsBackground = true; thread.Start (); ) โมฆะส่วนตัว ExecuteLoop() ( ลอง ( เซิร์ฟเวอร์ = new TcpListener (localAddr, พอร์ต); // สร้างเซิร์ฟเวอร์ตัวฟัง server.Start (); // เริ่มเซิร์ฟเวอร์ข้อมูล String; // วนซ้ำไม่สิ้นสุดกำลังฟังไคลเอนต์ในขณะที่ (จริง) ( ​​if (!server.Pending()) // คิวคำขอว่างเปล่า ดำเนินการต่อ; TcpClient client = server.AcceptTcpClient(); // ไคลเอนต์ปัจจุบัน // เรากำหนดขนาดของคลิปบอร์ด ตัวเราเอง (ไม่บังคับ!) // ตามค่าเริ่มต้น บัฟเฟอร์ทั้งสองจะถูกตั้งค่าเป็น 8192 ไบต์ในขนาด client.SendBufferSize = client.ReceiveBufferSize = 1024; // เชื่อมต่อ NetworkStream และโหลดลงในเชลล์เพื่อความสะดวก NetworkStream streamIn = client.GetStream(); NetworkStream streamOut = client.GetStream(); StreamWriterwriterStream = new StreamWriter(streamOut); // อ่านข้อมูลคำขอ = readerStream.ReadLine(); data.Substring(0, data.IndexOf(" ")) data = คำตอบ; else data = data.ToUpper(); writeStream.WriteLine(data); writeStream.Flush(); และสตรีม ลำดับไม่สำคัญ client.Close(); readerStream.Close();writerStream.Close( ) catch (SocketException) ( ) ในที่สุด ( // Stop the server server.Stop();

- หากต้องการส่งข้อมูลและรับการตอบกลับแอปพลิเคชันไคลเอนต์ สร้างซ็อกเก็ตแบบสองทิศทาง (หรือสองซ็อกเก็ตทิศทางเดียว) ซึ่งระบุที่อยู่สำหรับเชื่อมต่อกับซ็อกเก็ตของแอปพลิเคชันเซิร์ฟเวอร์อื่น หากสร้างการเชื่อมต่อแล้ว (เซิร์ฟเวอร์กำลังทำงาน) แอปพลิเคชันไคลเอนต์จะเชื่อมต่อกับซ็อกเก็ต เครือข่ายสตรีมสตรีมเครือข่าย

และผ่านการส่งและรับข้อมูล มีเซิร์ฟเวอร์อยู่อีกด้านหนึ่งของการเชื่อมต่อ TcpListener ฟังวนไปไม่รู้จบกับลูกค้า หากลูกค้าบางรายเชื่อมต่อกับมัน ( server.Pending()!=false) จากนั้นเซิร์ฟเวอร์จะดึงข้อมูลไคลเอ็นต์นี้โดยใช้ ยอมรับ TcpClient()- สร้างซ็อกเก็ตสำหรับการรับ/ส่งสัญญาณด้วยที่อยู่ผู้ส่งที่พร้อม สร้างการไหลแบบสองทิศทาง (หรือสองทิศทางเดียว) จากนั้นอ่านคำขอและส่งการตอบสนอง



โปรดทราบว่าหากรหัสของโปรแกรมเซิร์ฟเวอร์ของเราไม่ได้ถูกบรรจุเป็นเธรดแยกต่างหาก ด้าย(เธรดการดำเนินการ) จากนั้นระบบปฏิบัติการจะไม่เปิดโปรแกรมนี้ในหน้าต่างบริการ (ลองเลย!) เหตุผลก็คือในรหัสวิธีการ ExecuteLoop()เซิร์ฟเวอร์ใช้การวนซ้ำอย่างไม่มีที่สิ้นสุดในการฟังคิวคำขอของไคลเอ็นต์ หากการวนซ้ำนี้ถูกทิ้งไว้ในเธรดหลักของการดำเนินการ ( ด้าย) แอปพลิเคชันจะเข้าสู่ลูปและไม่สามารถยุติได้ตามปกติ ดังนั้นเราจึงวางโค้ดที่มีการวนซ้ำในเธรดแยกต่างหาก (เธรด) และทำให้เป็นพื้นหลังเพื่อให้ปิดพร้อมกับเธรดแอปพลิเคชันหลัก (เธรดเซิร์ฟเวอร์)

หมายเหตุสำคัญ

ไหล เครือข่ายสตรีมมีความยาวคงที่สองด้าน วิธี รับสตรีม()เพียงสร้างการเชื่อมต่อที่อยู่ระหว่างไคลเอนต์และซ็อกเก็ตเซิร์ฟเวอร์เท่านั้น แต่ความยาวที่แท้จริงจะถูกกำหนดโดยข้อความจากฝ่ายที่ส่ง คุณสามารถใช้หนึ่งเธรดในการรับ/ส่ง แต่ความยาวของข้อความที่เซิร์ฟเวอร์ส่งไม่ควรเกินความยาวของข้อความที่ได้รับจากไคลเอนต์ (ฉันเกือบลืมตาขึ้น!) นั่นเป็นเหตุผลที่เราใช้สตรีมสองสตรีมในแต่ละด้านเพื่อแยกการส่งข้อมูลทิศทางเดียวระหว่างโหนดการเชื่อมต่อเครือข่ายสองโหนด

ตัวอย่างที่ 3 แอปพลิเคชันไคลเอ็นต์-เซิร์ฟเวอร์สำหรับการดูภาพจากฐานข้อมูล

เมื่อครั้งก่อน ตัวอย่างง่ายๆเราได้ทำความคุ้นเคย (เล็กน้อย) กับหลักการสร้างแอปพลิเคชันเครือข่าย ตอนนี้เรามาสร้างเพิ่มเติมกันดีกว่า ตัวอย่างที่ซับซ้อนเมื่อไคลเอนต์ร้องขอรูปภาพ และเซิร์ฟเวอร์ดึงรูปภาพเหล่านั้นจากที่จัดเก็บและส่งไปยังไคลเอนต์ ใน แบบฝึกหัดที่ 7เราได้พัฒนาคลังภาพที่แตกต่างกันสามแห่งและโปรแกรมรับชมสามโปรแกรม ในตัวอย่างนี้เราจะใช้ฐานข้อมูล รูปภาพ.my2.mdbด้วยภาพวาดสำเร็จรูปและเราจะสร้างตามนั้น แอปพลิเคชันเครือข่าย(สำหรับใครที่ยังไม่มี. แบบฝึกหัดที่ 7, DB ถูกแนบมาในแค็ตตาล็อก แหล่งที่มา/ข้อมูล).

การสร้างลูกค้า

สำหรับลูกค้าเราจะสร้าง window application อย่างเช่น ดับบลิวพีเอฟโดยมีอินเทอร์เฟซผู้ใช้ที่ยืมมาบางส่วน ตัวอย่างที่ 6 แบบฝึกหัดที่ 7.


เซิร์ฟเวอร์ไม่พร้อม กรุณารอสักครู่! เรากำลังพยายามติดต่อ ขออภัยในความไม่สะดวก...

ในการแสดงหน้าจอเริ่มต้นพร้อมข้อความว่าเซิร์ฟเวอร์ไม่พร้อม เราใช้องค์ประกอบ ช่องมองภาพซึ่งมีองค์ประกอบอื่นวางอยู่ ชายแดนด้วยเนื้อหาที่เป็นข้อความ "สวน" นี้จะช่วยให้คุณเพิ่มสกรีนเซฟเวอร์ตามสัดส่วนขนาดของหน้าต่าง อย่างไรก็ตามการนำเอาธาตุ ช่องมองภาพเริ่มชะลอการวาดอินเทอร์เฟซใหม่อย่างเห็นได้ชัดเมื่อย้ายหน้าต่างเนื่องจากพยายามคำนวณสเกลขององค์ประกอบลูกใหม่อย่างต่อเนื่อง เรากำหนดชื่อให้กับองค์ประกอบอินเทอร์เฟซที่เราจะจัดการในโค้ดขั้นตอนเท่านั้น

ใช้ระบบ; ใช้ System.Collections.Generic; ใช้ System.Text; ใช้ระบบ Windows; ใช้ System.Windows.Controls; ใช้ System.Windows.Data; โดยใช้ System.Windows.Documents; โดยใช้ System.Windows.Input; ใช้ System.Windows.Media; ใช้ System.Windows.Media.Animation; ใช้ System.Windows.Media.Imaging; ใช้ System.Windows.Shapes; // เนมสเปซเพิ่มเติมสำหรับการสตรีมโดยใช้ System.IO; ใช้ IO = System.IO; // นามแฝงสำหรับการกำหนดเส้นทางโดยใช้ System.Windows.Threading; // สำหรับ DispatcherTimer // เนมสเปซเพิ่มเติมสำหรับซ็อกเก็ต // โดยใช้ System.Net; ใช้ System.Net.Sockets; ใช้ System.Collections; // รายการ เนมสเปซ PicturesClientDB (คลาสสาธารณะบางส่วน Window1: หน้าต่าง (พอร์ต int = 12000; String hostName = "127.0.0.1"; // ไคลเอนต์ TcpClient ท้องถิ่น = null; // ลิงก์ไคลเอนต์ String sendMessage = "!!!GetNames!!!"; / / ขอรายการ (ยุ่งยาก) ตัวคั่นอักขระ = ( "#" ); // เพื่อแปลงการตอบสนองต่ออาร์เรย์ของชื่อ DispatcherTimer ตัวจับเวลา; // ตัวจับเวลา // ตัวสร้างสาธารณะ Window1() ( InitializeComponent(); // สร้างและ เริ่มจับเวลา = new DispatcherTimer(); timer.Tick += new EventHandler(timer_Tick); timer.Interval = TimeSpan.FromSeconds(1); // เริ่มการเรียกไปยังเซิร์ฟเวอร์ void timer_Tick(object sender, EventArgs e) ( Execute(listBox); ) private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e) ( Execute((ListBox)sender); ) void Execute(ListBox lst) ( // กรอกรายการด้วยชื่อรูปภาพ ลอง ( / / หากเซิร์ฟเวอร์พร้อมใช้งาน ให้สร้างไคลเอ็นต์ไคลเอ็นต์ = new TcpClient(hostName, port) catch ( // เซิร์ฟเวอร์ไม่พร้อม เริ่มจับเวลาและออกหาก (Prompt.Visibility != Visibility.Visible) ( Prompt.Visibility = ทัศนวิสัยมองเห็นได้; ปิด(); readerStream.ปิด(); รายการ = รายการใหม่ (client.ReceiveBufferSize); // ไบต์ความจุที่เพิ่มขึ้น = ไบต์ใหม่; // ขนาดบัฟเฟอร์ซ็อกเก็ต int count = 0; // ส่วนของข้อมูลที่เข้ามาในขณะที่ ((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; } } } }

โปรดทราบว่าเมื่อแสดงรูปภาพ เราได้ละทิ้งองค์ประกอบแบบเดิมๆ ภาพดังที่ได้กระทำไปแล้วในแบบฝึกหัดครั้งก่อน และสำหรับการเปลี่ยนแปลงพวกเขาได้กระทำการที่แหวกแนวอย่างสิ้นเชิง (ในภาษาตุรกี) ตอนนี้เราจะแสดงภาพวาดด้วยแปรง แปรงรูปภาพในพื้นหลังของสี่เหลี่ยม ชายแดนผ่านวัตถุที่ติดอยู่ รูปภาพ- แน่นอนว่าในชีวิต ไม่น่าเป็นไปได้ที่คุณจะต้องบิดเบือนวิธีนี้ แต่ตัวเลือกนี้อาจมีประโยชน์ที่ไหนสักแห่ง


หน้าจอเริ่มต้นจะปรากฏขึ้นทันทีที่ตรวจพบว่าเซิร์ฟเวอร์หายไปหรือหยุดทำงาน และหลังจากตรวจพบเซิร์ฟเวอร์แล้วสกรีนเซฟเวอร์จะหายไป กลไกนี้จะทำงานได้ทันทีด้วย ตัวจับเวลาของระบบ- อย่างไรก็ตาม ยังไม่มีเซิร์ฟเวอร์และควรจะสร้าง

การสร้างเซิร์ฟเวอร์ฐานข้อมูลเป็นบริการ
  • ทีม ไฟล์/เพิ่ม/โครงการใหม่เพิ่มลงในโซลูชัน เครือข่ายสตรีมโครงการใหม่ชื่อ รูปภาพเซิร์ฟเวอร์DBพิมพ์ บริการวินโดวส์


ใช้ระบบ; ใช้ System.Collections.Generic; โดยใช้ System.ComponentModel; ใช้ System.Data; โดยใช้ระบบการวินิจฉัย โดยใช้ System.ServiceProcess; ใช้ System.Text; // เนมสเปซเพิ่มเติมสำหรับ ADO.NET โดยใช้ System.Data.OleDb; โดยใช้ System.Data.Common; // เนมสเปซเพิ่มเติมโดยใช้ System.IO; ใช้ System.Net; ใช้ System.Net.Sockets; โดยใช้ System.Threading; ใช้ System.Collections; Namespace PicturesServerDB ( Service1 คลาสบางส่วนสาธารณะ: ServiceBase ( int port = 12000; String hostName = "127.0.0.1"; // IPAddress ท้องถิ่น localAddr; เซิร์ฟเวอร์ TcpListener = null; // ลิงก์ไปยังเซิร์ฟเวอร์ ตัวแยกสตริง = "#"; // ชื่อตัวคั่นในบรรทัดตอบกลับ String ConnectionString; // สตริงการเชื่อมต่อฐานข้อมูล public Service1() ( // แยกสตริงการเชื่อมต่อฐานข้อมูลจากไฟล์ App.config ลงในฟิลด์ ConnectionString = System.Configuration.ConfigurationManager. ConnectionStrings["PicturesDB"] ConnectionString; / / แปลง IP เป็นรูปแบบอื่น localAddr = IPAddress.Parse(hostName); // เรียกใช้ในเธรดใหม่ (เธรด) Thread = new Thread(ExecuteLoop); thread.IsBackground = true; ) ( ลอง ( server = new TcpListener(localAddr, port);// Create a server-listener server.Start();// Start the server // Endless loop of Listening to client while (true) ( ​​​​// ตรวจสอบ คิวการเชื่อมต่อถ้า (!server .Pending()) // คิวคำขอว่างเปล่า ดำเนินการต่อ; NET OleDbConnection conn = OleDbConnection ใหม่ (สตริงการเชื่อมต่อ);

แอปพลิเคชันไคลเอ็นต์-เซิร์ฟเวอร์ในการสตรีมมิ่ง ซ็อกเก็ต TCP

ตัวอย่างต่อไปนี้ใช้ TCP เพื่อจัดเตรียมสตรีมไบต์แบบสองทางที่เป็นระเบียบและเชื่อถือได้ มาสร้างแอปพลิเคชันที่สมบูรณ์ซึ่งประกอบด้วยไคลเอนต์และเซิร์ฟเวอร์ ขั้นแรก เราจะสาธิตวิธีการสร้างเซิร์ฟเวอร์โดยใช้ซ็อกเก็ตสตรีม TCP จากนั้นจึงใช้แอปพลิเคชันไคลเอ็นต์เพื่อทดสอบเซิร์ฟเวอร์ของเรา

โปรแกรมต่อไปนี้สร้างเซิร์ฟเวอร์ที่ได้รับการร้องขอการเชื่อมต่อจากไคลเอนต์ เซิร์ฟเวอร์ถูกสร้างขึ้นพร้อมกัน ดังนั้น การดำเนินการเธรดจึงถูกบล็อกจนกว่าเซิร์ฟเวอร์จะตกลงที่จะเชื่อมต่อกับไคลเอนต์ แอปพลิเคชั่นนี้สาธิตเซิร์ฟเวอร์อย่างง่ายที่ตอบสนองต่อไคลเอนต์ ไคลเอนต์สิ้นสุดการเชื่อมต่อโดยการส่งข้อความไปยังเซิร์ฟเวอร์ .

เซิร์ฟเวอร์ TCP

การสร้างโครงสร้างเซิร์ฟเวอร์จะแสดงในแผนภาพการทำงานต่อไปนี้:

ที่นี่ รหัสเต็มโปรแกรม SocketServer.cs:

// SocketServer.cs โดยใช้ระบบ; ใช้ System.Text; ใช้ System.Net; ใช้ System.Net.Sockets; เนมสเปซ SocketServer ( คลาสโปรแกรม ( static void Main(string args) ( // ตั้งค่าจุดปลายท้องถิ่นสำหรับซ็อกเก็ต IPHostEntry ipHost = Dns.GetHostEntry("localhost"); IPAddress ipAddr = ipHost.AddressList; IPEndPoint ipEndPoint = new IPEndPoint (ipAddr, 11000 ); // สร้างซ็อกเก็ต Tcp/Ip sListener = new Socket(ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // กำหนดซ็อกเก็ตให้กับจุดสิ้นสุดในเครื่องและลองฟังซ็อกเก็ตขาเข้า ( sListener.Bind(ipEndPoint sListener. Listen(10); // เริ่มฟังการเชื่อมต่อในขณะที่ (true) ( ​​​​Console.WriteLine("กำลังรอการเชื่อมต่อบนพอร์ต (0)", ipEndPoint); // โปรแกรมหยุดชั่วคราวขณะรอ การเชื่อมต่อขาเข้าตัวจัดการซ็อกเก็ต = sListener.Accept(); ข้อมูลสตริง = null;

// เรารอให้ไคลเอ็นต์พยายามเชื่อมต่อกับเรา ไบต์ ไบต์ = ไบต์ใหม่;

int bytesRec = ตัวจัดการรับ (ไบต์);

ข้อมูล += การเข้ารหัส UTF8.GetString (ไบต์, 0, bytesRec); // แสดงข้อมูลบนคอนโซล Console.Write("ข้อความที่ได้รับ: " + data + "\n\n");รองรับโดยอุปกรณ์บนเครือข่ายท้องถิ่น หากอุปกรณ์ LAN มีที่อยู่เครือข่ายมากกว่าหนึ่งแห่ง คลาส Dns จะส่งคืนข้อมูลเกี่ยวกับที่อยู่เครือข่ายทั้งหมด และแอปพลิเคชันจะต้องเลือกที่อยู่ที่เหมาะสมเพื่อให้บริการจากอาร์เรย์

มาสร้าง IPEndPoint สำหรับเซิร์ฟเวอร์โดยการรวมที่อยู่ IP แรกของโฮสต์คอมพิวเตอร์ที่ได้รับจากวิธี Dns.Resolve() เข้ากับหมายเลขพอร์ต:

IPHostEntry ipHost = Dns.GetHostEntry("localhost"); IPAddress ipAddr = ipHost.AddressList; IPEndPoint ipEndPoint = IPEndPoint ใหม่ (ipAddr, 11000);

ที่นี่คลาส IPEndPoint แสดงถึง localhost บนพอร์ต 11000 ต่อไป เราจะสร้างซ็อกเก็ตกระแสข้อมูลด้วยอินสแตนซ์ใหม่ของคลาส Socket เมื่อตั้งค่าจุดสิ้นสุดในเครื่องเพื่อรับฟังการเชื่อมต่อแล้ว เราสามารถสร้างซ็อกเก็ตได้:

Socket sListener = ซ็อกเก็ตใหม่ (ipAddr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

โอนย้าย ที่อยู่ครอบครัวระบุรูปแบบการกำหนดแอดเดรสที่อินสแตนซ์ของคลาส Socket สามารถใช้เพื่อแก้ไขที่อยู่

ในพารามิเตอร์ ประเภทซ็อกเก็ตซ็อกเก็ต TCP และ UDP นั้นแตกต่างกัน ในนั้นคุณสามารถกำหนดค่าต่างๆ ต่อไปนี้:

ดีแกรม

รองรับดาต้าแกรม ค่า Dgram กำหนดให้ระบุ Udp สำหรับประเภทโปรโตคอลและ InterNetwork ในพารามิเตอร์ตระกูลที่อยู่

ดิบ

รองรับการเข้าถึงโปรโตคอลการขนส่งพื้นฐาน

ลำธาร

รองรับซ็อกเก็ตสตรีม ค่าสตรีมต้องระบุ Tcp สำหรับประเภทโปรโตคอล

พารามิเตอร์ที่สามและสุดท้ายระบุประเภทโปรโตคอลที่จำเป็นสำหรับซ็อกเก็ต ในพารามิเตอร์ ProtocolTypeคุณสามารถระบุสิ่งต่อไปนี้ได้มากที่สุด ค่านิยมที่สำคัญ- Tcp, Udp, Ip, ดิบ

ขั้นตอนต่อไปควรกำหนดซ็อกเก็ตโดยใช้วิธีการ ผูก()- เมื่อคอนสตรัคเตอร์เปิดซ็อกเก็ต จะไม่ได้กำหนดชื่อ แต่จะสงวนไว้เฉพาะหมายเลขอ้างอิงเท่านั้น Bind() วิธีการถูกเรียกเพื่อกำหนดชื่อให้กับซ็อกเก็ตเซิร์ฟเวอร์ หากต้องการอนุญาตให้ซ็อกเก็ตไคลเอ็นต์ระบุซ็อกเก็ตสตรีม TCP โปรแกรมเซิร์ฟเวอร์จะต้องตั้งชื่อให้ซ็อกเก็ต:

SListener.Bind(ipEndPoint);

Bind() วิธีการผูกซ็อกเก็ตกับปลายทางในเครื่อง ต้องเรียกเมธอด Bind() ก่อนที่จะพยายามเรียกเมธอด Listen() และ Accept()

ตอนนี้เมื่อสร้างซ็อกเก็ตและเชื่อมโยงชื่อแล้วคุณสามารถฟังข้อความขาเข้าโดยใช้วิธีการได้ ฟัง()- ในสถานะการฟัง ซ็อกเก็ตจะรับฟังความพยายามในการเชื่อมต่อขาเข้า:

SListener.ฟัง(10);

พารามิเตอร์กำหนด งานค้างระบุจำนวนการเชื่อมต่อสูงสุดที่รออยู่ในคิว ในโค้ดด้านบน ค่าพารามิเตอร์อนุญาตให้มีการเชื่อมต่อสูงสุดสิบรายการเพื่อสะสมในคิว

ในสถานะการฟัง คุณต้องพร้อมที่จะตกลงที่จะเชื่อมต่อกับไคลเอนต์ที่ใช้เมธอดนี้ ยอมรับ()- วิธีการนี้ได้รับการเชื่อมต่อไคลเอ็นต์ และดำเนินการเชื่อมโยงชื่อไคลเอ็นต์-เซิร์ฟเวอร์ให้เสร็จสมบูรณ์ เมธอด Accept() จะบล็อกเธรดของโปรแกรมที่เรียกใช้จนกว่าการเชื่อมต่อจะมาถึง

เมธอด Accept() จะลบคำขอการเชื่อมต่อแรกออกจากคิวคำขอที่รอดำเนินการ และสร้างซ็อกเก็ตใหม่เพื่อประมวลผล แม้ว่าซ็อกเก็ตใหม่จะถูกสร้างขึ้น ซ็อกเก็ตเดิมยังคงรับฟังและสามารถใช้กับมัลติเธรดเพื่อรับคำขอการเชื่อมต่อหลายรายการจากไคลเอนต์ แอปพลิเคชันเซิร์ฟเวอร์ไม่ควรปิดซ็อกเก็ตการฟัง จะต้องทำงานร่วมกับซ็อกเก็ตที่สร้างขึ้นโดยวิธีการยอมรับต่อไปเพื่อประมวลผลคำขอของลูกค้าที่เข้ามา

ในขณะที่ (จริง) ( ​​Console.WriteLine("กำลังรอการเชื่อมต่อบนพอร์ต (0)", ipEndPoint); // โปรแกรมหยุดชั่วคราวขณะรอการเชื่อมต่อขาเข้า ตัวจัดการซ็อกเก็ต = sListener.Accept();

เมื่อไคลเอ็นต์และเซิร์ฟเวอร์สร้างการเชื่อมต่อระหว่างกันแล้ว คุณสามารถส่งและรับข้อความโดยใช้วิธีการได้ ส่ง()และ รับ()ซ็อกเก็ตคลาส

วิธีการ Send() เขียนข้อมูลขาออกไปยังซ็อกเก็ตที่เชื่อมต่อ วิธีการรับ () อ่านข้อมูลขาเข้าลงในซ็อกเก็ตกระแสข้อมูล เมื่อใช้ระบบที่ใช้ TCP จะต้องสร้างการเชื่อมต่อระหว่างซ็อกเก็ตก่อนที่จะดำเนินการเมธอด Send() และ Receiver() โปรโตคอลที่แน่นอนระหว่างเอนทิตีการสื่อสารทั้งสองจะต้องถูกกำหนดล่วงหน้าเพื่อให้แอปพลิเคชันไคลเอนต์และเซิร์ฟเวอร์ไม่บล็อกซึ่งกันและกันโดยไม่รู้ว่าใครควรส่งข้อมูลก่อน

เมื่อการแลกเปลี่ยนข้อมูลระหว่างเซิร์ฟเวอร์และไคลเอนต์เสร็จสิ้น คุณจะต้องปิดการเชื่อมต่อโดยใช้วิธีการ ปิดเครื่อง()และ ปิด():

Handler.Shutdown (SocketShutdown.Both); ตัวจัดการ.ปิด();

SocketShutdown เป็น enum ที่มีค่าสามค่าที่จะหยุด: ทั้งคู่- หยุดการส่งและรับข้อมูลโดยซ็อกเก็ต รับ- หยุดซ็อกเก็ตไม่ให้รับข้อมูลและ ส่ง- หยุดส่งข้อมูลทางซ็อกเก็ต

ซ็อกเก็ตถูกปิดโดยการเรียกเมธอด Close() ซึ่งตั้งค่าคุณสมบัติการเชื่อมต่อของซ็อกเก็ตให้เป็นเท็จด้วย

ไคลเอ็นต์ TCP

ฟังก์ชันที่ใช้ในการสร้างแอปพลิเคชันไคลเอนต์นั้นคล้ายกับแอปพลิเคชันเซิร์ฟเวอร์ไม่มากก็น้อย เช่นเดียวกับเซิร์ฟเวอร์ วิธีการเดียวกันนี้ใช้ในการกำหนดจุดสิ้นสุด สร้างอินสแตนซ์ของซ็อกเก็ต ส่งและรับข้อมูล และปิดซ็อกเก็ต