Bài 48: (Networking) Giao thức Tcp với lớp TcpListener/TcpClient và lớp Uri IPAddress C Sharp

Ngày đăng: 12/29/2022 2:33:49 PM

Lớp tiện ích trong lập trình Networking

Lớp Uri và UriBuilder

Lớp Uri để biểu diễn một Uri (nó là chuỗi ký tự xác định, nhận dạng tài nguyên), tài nguyên nhận dạng cho đối tượng trên mạng Uri đó là Url.

Cấu trúc Uri

http://site.yourdomain.com/path/to/page/?a=1&b=price#section

Từ một chuỗi URL ví dụ: https://xuanthulab.net/testpate.html#testfragment?read=1, có thể khởi tạo Uri và đọc được các thông tin về Uri

 public static void ShowUriInfo(string url) {
     Uri uri = new Uri(url);
     Console.WriteLine(url);
     Console.WriteLine($"Scheme   : {uri.Scheme}");
     Console.WriteLine($"Host     : {uri.Host}");
     Console.WriteLine($"Port     : {uri.Port}");
     Console.WriteLine($"Fragment : {uri.Fragment}");
     Console.WriteLine($"Query    : {uri.Query}");
     Console.WriteLine($"Path     : {uri.LocalPath}");
     foreach (var seg in uri.Segments)
         Console.WriteLine($"           {seg}");
     /*
     https://xuanthulab.net/abc/testpate.html?read=1#testfragment
     Scheme   : https
     Host     : xuanthulab.net
     Port     : 443
     Fragment : #testfragment
     Query    : ?read=1
     Path     : /abc/testpate.html
             /
             abc/
             testpate.html
     */
 }

Ngược lại, bạn có thể từ các thành phần của Uri như Path, Host, Port ... dùng lớp UriBuilder để có được Uri

 public static void BuildUriExample() {
     UriBuilder uriBuilder = new UriBuilder();
     uriBuilder.Host = "xuanthulab.net";
     uriBuilder.Port = 80;
     uriBuilder.Path = "path/to/site";
     uriBuilder.Query = "lession=1";
     uriBuilder.Fragment = "xyz";
     Uri uri = uriBuilder.Uri;
     Console.WriteLine(uri);
     // http://xuanthulab.net/path/to/site?lession=1#xyz
 }
    
 

Lớp IPAddress

Lớp IPAddress biểu diễn một địa chỉ IP. Thực chất địa chỉ IP là một mảng byte, có thể lấy mảng byte này bằng GetAddressBytes, tuy nhiên có thể chuyển thành biểu diễn mảng byte đó thành chuỗi các số thập phân, phân cách nhau bởi ký tự . bằng phương thức ToString().

Bạn có thể khởi tạo đối tượng IPAdress bằng cách cung cấp mảng byte biểu diễn IP, hoặc phân tích từ một string biểu diễn IP bằng IPAddress.TryParse

 IPAddress ipaddress;
 if (IPAddress.TryParse(ips, out ipaddress)) {
 
 }

Một số phương thức, thuộc tính

Thành viên

Diễn tả

IPAddress.Broadcast

Địa chỉ broadcast của mạng, đây là IP đặc biệt của mạng, gửi gói tin tới IP này nghĩa là gửi tới tất cả các máy trong mạng

IPAddress.Loopback

Địa chỉ Loopback, không đia qua thiết bị mạng, biểu diễn hostname tên localhost, trỏ đến chính máy host

MapToIPv4()

Convert thành IP4

MapToIPv6()

Convert thành IP6

 public static void IPAddressExample(string ips) {
     IPAddress ipaddress;
     if (IPAddress.TryParse(ips, out ipaddress)) {
         Console.WriteLine($"Broadcast     {IPAddress.Broadcast}");
         Console.WriteLine($"Loopback      {IPAddress.Loopback}");
         Console.WriteLine($"AddressFamily {ipaddress.AddressFamily}");
         Console.WriteLine($"IP4           {ipaddress.MapToIPv4().ToString()}");
         Console.WriteLine($"IP6           {ipaddress.MapToIPv6().ToString()}");
         /*
             Broadcast     255.255.255.255
             Loopback      127.0.0.1
             AddressFamily InterNetwork
             IP4           192.168.0.66
             IP6           ::ffff:192.168.0.66
         */
     }
 }
 

Lớp IPHostEntry

Lớp IPHostEntry biểu diễn địa chỉ mạng host, nó gắn với một DSN.

 IPHostEntry hostInfo = Dns.GetHostEntry("google.com.vn");
 Console.WriteLine(hostInfo.HostName);
 foreach (var ip in hostInfo.AddressList)
 {
    Console.WriteLine(ip);
 }
 /*
     google.com.vn
     216.58.221.227
     2404:6800:4005:800::2003
 */
 

Lớp Dns

Lớp để tương tác với máy chủ DNS để phân giải địa chỉ IP. Xem ví dụ trên.

Giao thức TCP

Giao thức TCP (Transmission Control Protocol), đầu tiên client phải mở một kết nối đến server rồi mới có thể gửi các lệnh, thông điệp. Giao thức HTTP mà ta sử dụng với HttpListener, HttpClient chính là dựa trên TCP mặc dù những lớp này đã ẩn đi (tự động) thực hiện các thao tác mở kết nối cho chúng ta.

Các lớp TCP đưa ra những phương thức đơn giản giúp gửi nhận dữ liệu giữa các máy, nó sử dụng thông tin IP và cổng để kết nối với nhau, ví dụ HTTP sử dụng cổng 80, SMTP dùng cổng 25 ... đó là những cổng dành cho những dịch vụ chính thống do tổ chức IANA quy định (xem www.iana.org), nếu muốn chạy dịch vụ của riêng bạn hãy chọn cổng lớn hơn 1024.

Lớp TcpClient cho phép tạo kết nối TCP. Lớp TcpListener cho phép lắng nghe các yêu cầu TCP gửi đến.

Tạo HTTP Client với TCP

Để truy cập đến máy chủ HTTP (trang web ...) đã thực hiện khá dễ dàng với HttpClient, tuy nhiên nếu muốn dùng trực tiếp giao thức TCP với lớp TcpClient bạn cần một chút hiểu chi tiết về TCP.

Ví dụ sau, sẽ dử dụng lớp TcpClient tạo kết nối tới server, gửi thông điệp HTTP, nhận thông điệp HTTP

 using System;
 using System.Linq;
 using System.Net;
 using System.Threading.Tasks;
 using System.Text;
 using System.Net.Sockets;
 using System.Net.Http;
 using System.IO;
 using System.Net.Security;
 using System.Security.Cryptography.X509Certificates;
 
 namespace TCP
 {
 
     class Program
     {
         // Phương thức này gọi bởi RemoteCertificateValidationDelegate trong quá trình xác thức SSL
         // chỉ dùng khi kết nối HTTPS
 
         public static bool ValidateServerCertificate(
               object sender,
               X509Certificate certificate,
               X509Chain chain,
               SslPolicyErrors sslPolicyErrors)
         {
            Console.WriteLine("ValidateServerCertificate");
            if (sslPolicyErrors == SslPolicyErrors.None)  return true;
             Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
 
             // Do not allow this client to communicate with unauthenticated servers.
             return false;
         }
 
         // Kết nối đến server Tpc bằng TcpClient, đọc nội dung trả về
         public static  async Task ReadHtmlAsync(string url) {
 
             using (var client = new TcpClient())
             {
                 Console.WriteLine($"Start get {url}");
                 Uri uri = new Uri(url);
 
                 var hostAdress = await Dns.GetHostAddressesAsync(uri.Host);
                 IPAddress ipaddrress = hostAdress[0];
                 Console.WriteLine($"Host: {uri.Host}, IP: {ipaddrress}:{uri.Port}");
                 await client.ConnectAsync(ipaddrress.MapToIPv4(), uri.Port);
                 Console.WriteLine("Connected");
                 Console.WriteLine();
 
 
                 Stream stream;
                 if (uri.Scheme == "https")
                 {
                     // SslStream
                     stream = new SslStream(client.GetStream(),false,
                                            new RemoteCertificateValidationCallback (ValidateServerCertificate),
                                            null);
                    (stream as SslStream).AuthenticateAsClient(uri.Host);
                 }
                 else {
                     // NetworkStream
                     stream = client.GetStream();
                 }
 
                 Console.WriteLine($"Get Stream OK: {stream.GetType().Name}");
 
 
                 // Xem: /psr-7-chuan-giao-dien-thong-diep-http.html#HTTPRequest
                 StringBuilder header = new StringBuilder();
                 header.Append($"GET {uri.PathAndQuery} HTTP/1.1
 ");
                 // header.Append($"GET {uri.PathAndQuery} HTTP/2
 ");
                 header.Append($"Host: {uri.Host}
 ");
                 header.Append($"
 ");
 
                 Console.WriteLine("Request:");
                 Console.WriteLine(header);
 
                 byte[]  bsend  = Encoding.UTF8.GetBytes(header.ToString());
                 await stream.WriteAsync(bsend, 0, bsend.Length);
 
                 await stream.FlushAsync();
 
                 Console.WriteLine("Send Message OK");
 
 
                 var ms = new MemoryStream();
                 byte [] buffer = new byte[255];
                 int bytes = -1;
                 do
                 {
                     bytes = await stream.ReadAsync(buffer, 0, buffer.Length);
 
                     // Lưu dữ liệu tải về vào ms
                     ms.Write(buffer, 0, bytes);
 
                     Array.Clear(buffer, 0, buffer.Length);
 
                 } while (bytes != 0);
 
                 Console.WriteLine($"Read OK");
 
                 ms.Seek(0, SeekOrigin.Begin);
                 var reader = new StreamReader(ms);
                 string html = reader.ReadToEnd();
                 Console.WriteLine("Response:");
                 Console.WriteLine(html);
 
             }
         }
         static async Task  Main(string[] args)
         {
             string url = "https://google.com.vn";
             await ReadHtmlAsync(url);
         }
 
     }
 }
 

Tham khảo mã nguồn TcpClient (git) hoặc tải về ex036

Lớp TcpListener - dịch vụ TPC ở server

Lớp TcpListener cho phép tạo dịch vụ lắng nghe yêu cầu TCP gửi đến, lắng nghe trên một cổng nào đó. Ví dụ sau tạo dịch vụ, lắng nghe trên cổng 1950, mặc dù có thể gửi nhận từng byte dữ liệu giữa server và client, tuy nhiên để đơn giản hóa sẽ / đọc ghi từng dòng chuỗi dữ liệu với StreamWriter và StreamReader. Chương trình này chạy phía server để client kết nối, nó khá giống chương trình chat đơn giản

 using System;
 using System.Net;
 using System.Threading.Tasks;
 using System.Net.Sockets;
 using System.IO;
 
 namespace TCP
 {
     class Program
     {
         public class TpcServerAsyncv {
             readonly int PortNumber;
             public TpcServerAsyncv(int portNumber) => PortNumber = portNumber;
 
             // Lắng nghe
             public async Task StartLinster()
             {
                 try
                 {
                     var listener = new TcpListener(IPAddress.Any, PortNumber);
                     Console.WriteLine($"Listener lắng nghe ở cổng {PortNumber}");
                     listener.Start();
 
                     while (true)
                     {
                         Console.WriteLine("Chờ client kết nối ...");
                         // Một client kết nối đến
                         TcpClient client = await listener.AcceptTcpClientAsync();
 
                         // Xử lý quá trình giao tiếp giữa Client và Server
                         Task t = RunClientRequestAsync(client);
                     }
                 }
                 catch (Exception ex)
                 {
                     Console.WriteLine($"Exception of type {ex.GetType().Name}, Message: {ex.Message}");
                 }
             }
             private Task RunClientRequestAsync(TcpClient client)
             {
                  // Ham delegate để task thi hành
                  Action action = async () => {
                     try
                     {
                         using (client)
                         {
                             Console.WriteLine("client kết nối");
                             using (NetworkStream stream = client.GetStream())        // tạo các stream
                             using (StreamWriter  writer = new StreamWriter(stream))  // stream để gửi dữ liệu cho client
                             using (StreamReader  reader = new StreamReader(stream))  // stream để đọc dữ liệu từ client
                             {
                                 writer.AutoFlush = true;
                                 bool exit = false;
                                 while (!exit) {
                                     string data = await reader.ReadLineAsync();
                                     switch (data.ToLower())
                                     {
                                         case "time":
                                             await writer.WriteLineAsync(DateTime.Now.ToLongTimeString());
                                         break;
                                         case "exit": // thoát lặp - ngắt kết nối nếu client gửi dòng exit
                                             exit = true;
                                             await writer.WriteLineAsync("exit");
                                         break;
                                         default:
                                             await writer.WriteLineAsync("Không thấy lệnh");
                                         break;
                                     }
                                 }
 
                             }
                         }
                     }
                     catch (Exception ex)
                     {
                         Console.WriteLine($"Lỗi {ex.GetType().Name}, Message: {ex.Message}");
                     }
                     Console.WriteLine("Client ngắt kế nối");
                 };
 
                 Task task = new Task(action); // tạo task
                 task.Start();                 // chạy  task trên thread
                 return task;
             }
         }
         static async Task  Main(string[] args)
         {
             await (new TpcServerAsyncv(1950)).StartLinster();
         }
 
     }
 }
 

Tham khảo mã nguồn TcpListener (git) hoặc tải về ex037

Kết nối đến TCP server với TcpClient

Để kết nối và giao tiếp với TCP Server mà ta đang chạy ở ví dụ trên bằng TcpListener, có thể dùng lớp TcpClient kết nối, nó cũng đọc ghi dữ liệu với StreamWriterStreamReader

Source code

 using System;
 using System.Net;
 using System.Threading.Tasks;
 using System.Net.Sockets;
 using System.IO;
 
 namespace TCP
 {
     class Program
     {
         public static  async Task StartConnectAsync(IPAddress iPAddress, int Port) {
 
             try {
                 using (var client = new TcpClient())
                 {
 
                     await client.ConnectAsync(iPAddress, Port);
                     Console.WriteLine("Đã kết nối");
 
                     using (NetworkStream stream = client.GetStream())
                     using (StreamWriter writer = new StreamWriter(stream))
                     using (StreamReader reader = new StreamReader(stream))
                     {
                         writer.AutoFlush = true;
                         bool quite = false;
                         while (!quite) {
                             Console.Write("Nhập nội dung (time, exit):");
                             string mgs = Console.ReadLine();
                             if (mgs == "exit")
                                 quite = true;
 
                             await writer.WriteLineAsync(mgs);
                             string mgs_receive = await reader.ReadLineAsync();
                             Console.WriteLine(mgs_receive);
                         }
 
                     }
                 }
             } catch (Exception ex)
             {
                 Console.WriteLine($"Lỗi {ex.GetType().Name}, Message: {ex.Message}");
             }
         }
         static async Task  Main(string[] args)
         {
             IPAddress ip = IPAddress.Parse("127.0.0.1");
             int       port = 1950;
 
             await StartConnectAsync(ip, port);
         }
 
     }
 }
 

Tham khảo mã nguồn TcpClient (git) hoặc tải về ex038

Nguồn tin: Xuanthulab