Như đã giới thiệu các bạn ở bài trước về Lập trình mạng: Xây dựng ứng dụng client – server hướng kết nối (TCP Socket). Bài viết này chỉ giới thiệu đến các bạn cách cơ bản nhất để tạo ra một server và một client có thể kết nối và giao tiếp với nhau. Nhưng ở đây vẫn còn khá nhiều vấn đề cần giải quyết, nổi trội nhất là vấn đề bộ đệm dữ liệu. Hôm nay chúng ta sẽ đi sâu vào vấn đề này và tìm ra cách giải quyết.
Với Client và Server đơn giản, do chúng ta tự nhập ở bài trước thì tất cả các thông điệp đều có kích thước nhỏ nên sẽ không phát sinh ra vấn đề. Nhưng trong thực tế, khi giao tiếp qua lại chúng ta không thể biết được kiểu dữ liệu và kích thước. Vấn đề sẽ xảy ra khi dữ liệu đến lớn hơn kích thước của bộ đệm.
Để giải quyết vấn đề với biên dữ liệu không được bảo vệ, chúng ta phải tìm hiểu một số kỹ thuật để phân biệt các thông điệp. Có Ba kỹ thuật thường dùng để phân biệt các thông điệp được gởi thông qua TCP, hôm nay chúng ta sẽ tìm hiểu về kỹ thuật sử dụng thông điệp với kích thước cố định.
Cách dễ nhất nhưng cũng là cách tốn chi phí nhất để giải quyết vấn đề với các thông điệp TCP là tạo ra các giao thức luôn luôn truyền các thông điệp với kích thước cố định. Bằng cách thiết lập tất cả các thông điệp có cùng kích thước, chương trình TCP nhận có thể biết toàn bộ thông điệp được gởi từ Client. Khi gởi dữ liệu với kích thước cố định, chúng ta phải đảm bảo toàn bộ thông điệp được gởi từ phương thức Send(). Phụ thuộc vào kích thước của bộ đệm TCP và bao nhiêu dữ liệu được truyền, phương thức Send() sẽ trả về số byte mà nó thực sự đã gởi đến bộ đệm TCP. Nếu phương thức Send() chưa gởi hết dữ liệu thì chúng ta phải gởi lại phần dữ liệu còn lại. Việc này thường được thực hiện bằng cách sử dụng vòng lặp while() và trong vòng lặp ta kiểm tra số byte thực sự đã gởi với kích thước cố định.
Code
private static int SendData(Socket s, byte[] data) { int total = 0; int size = data.Length; int dataleft = size; int send; while (total < size) { send = s.Send(data, total, dataleft, SocketFlags.None); total = total + send; dataleft = dataleft - send; } return total; }
Cũng giống như việc gởi dữ liệu, chúng ta phải luôn luôn đảm bảo nhận tất cả dữ liệu trong phương thức Receive(). Bằng cách dùng vòng lặp gọi phương thức Receive() chúng ta có thể nhận được toàn bộ dữ liệu mong muốn.
Code
private static byte[] ReceiveData(Socket s, int size) { int total = 0; int dataleft = size; byte[] data = new byte[size]; int recv; while (total < size) { recv = s.Receive(data, total, dataleft, 0); if (recv == 0) { data = Encoding.ASCII.GetBytes("exit"); break; } total = total + recv; dataleft = dataleft - recv; } return data; }
Phương thức ReceiveData() sẽ đọc dữ liệu với kích thước được đọc là đối số được truyền vào, nếu phương thức Receive() sẽ trả về số byte thực sụ đọc được, nếu số byte thực sự đọc được mà còn nhỏ hơn số byte truyền vào phương thức ReceiveData() thì vòng lặp sẽ tiếp tục cho đến khi số byte đọc được đúng bằng kích thước yêu cầu.
Sau đây là Code của cả chương trình gửi thông điệp đính kèm kích thước cố định.
Server
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; namespace TCP2 { class Program { private static int SendData(Socket s, byte[] data) { int total = 0; int size = data.Length; int dataleft = size; int send; while (total < size) { send = s.Send(data, total, dataleft, SocketFlags.None); total = total + send; dataleft = dataleft - send; } return total; } private static byte[] ReceiveData(Socket s, int size) { int total = 0; int dataleft = size; byte[] data = new byte[size]; int recv; while (total < size) { recv = s.Receive(data, total, dataleft, 0); if (recv == 0) { data = Encoding.ASCII.GetBytes("exit"); break; } total = total + recv; dataleft = dataleft - recv; } return data; } static void Main(string[] args) { byte[] data = new byte[1024]; IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 5000); Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); newsock.Bind(ipep); newsock.Listen(10); Console.WriteLine("Dang cho Client ket noi den..."); Socket client = newsock.Accept(); IPEndPoint newclient = (IPEndPoint)client.RemoteEndPoint; Console.WriteLine("Da ket noi voi Client {0} tai port {1}",newclient.Address, newclient.Port); string welcome = "Hello Client"; data = Encoding.ASCII.GetBytes(welcome); int sent = SendData(client, data); for (int i = 0; i < 5; i++) { data = ReceiveData(client, 12); Console.WriteLine(Encoding.ASCII.GetString(data)); } Console.WriteLine("Da ngat ket noi voi Client {0}", newclient.Address); client.Close(); newsock.Close(); } } }
Client
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Net.Sockets; using System.Net; namespace TPC1 { class Program { private static int SendData(Socket s, byte[] data) { int total = 0; int size = data.Length; int dataleft = size; int send; while (total < size) { send = s.Send(data, total, dataleft, SocketFlags.None); total = total + send; dataleft = dataleft - send; } return total; } private static byte[] ReceiveData(Socket s, int size) { int total = 0; int dataleft = size; byte[] data = new byte[size]; int recv; while (total < size) { recv = s.Receive(data, total, dataleft, 0); if (recv == 0) { data = Encoding.ASCII.GetBytes("exit"); break; } total = total + recv; dataleft = dataleft - recv; } return data; } static void Main(string[] args) { byte[] data = new byte[1024]; int send; IPEndPoint ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5000); Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { server.Connect(ipep); } catch (SocketException e) { Console.WriteLine("Khong the ket noi toi server"); Console.WriteLine(e.ToString()); return; } int recv = server.Receive(data); string stringData = Encoding.ASCII.GetString(data, 0, recv); Console.WriteLine(stringData); send = SendData(server, Encoding.ASCII.GetBytes("Thong diep 1")); send = SendData(server, Encoding.ASCII.GetBytes("Thong diep 2")); send = SendData(server, Encoding.ASCII.GetBytes("Thong diep 3")); send = SendData(server, Encoding.ASCII.GetBytes("Thong diep 4")); send = SendData(server, Encoding.ASCII.GetBytes("Thong diep 5")); Console.WriteLine("Dong ket noi voi server..."); server.Shutdown(SocketShutdown.Both); server.Close(); } } }
Trên đây là bài viết giới thiệu đến các bạn cách giải quyết bằng cách gửi thông điệp kèm thước cố định. Nhưng đây là cách giải quyết lãng phí vì tất cả các thông điệp phải cùng kích thước gây lãng phí băng thông mạng. Bài viết sau chúng ta sẽ giải quyết về vấn đề nay. Hẹn gặp lại các bạn.
CHÚC CÁC BẠN THÀNH CÔNG.