C# – Lập trình Socket giao tiếp TCP client/server

Trong lập trình, Socket là một API (Application Programming Interface) cung cấp các phương thức để giao tiếp thông qua mạng. Trước khi bắt đầu tìm hiểu và viết một ví dụ đơn giản về socket, bạn có thể tham khảo bài  viết “ png”>

Example v1: Gửi nhận dữ liệu dạng byte[]

Lớp NetworkStream và  Socket cung cấp các phương thức gửi và nhận dữ liệu dạng mảng byte. Vì vậy bạn cần phải thực hiện các bước chuyển đổi dữ liệu sang dạng byte và ngược lại. Trong ví dụ sau tôi sử dụng dữ liệu dạng văn bản ASCII trong console, và dùng các lớp trong namespace System.Text để chuyển đổi. Có hai cách bạn có thể áp dụng:

–       Dùng các static property của lớp abstract System.Text.Encoding với các phương thức GetString() và GetBytes().

–       Tạo đối tượng có kiểu XXXEncoding (thừa kế từ System.Text.Encoding). Ví dụ: UTF8Encoding, ASCIIEncoding,…

Một ví dụ gửi nhận dữ liệu đơn giản nhất sử dụng TCPListener, Socket (phía server) và TCPClient, NetworkStream (phía client) dạng mảng byte với địa chỉ loop-back 127.0.0.1 trên cùng một máy.

Tạo hai dự án console là Y2Server và Y2Client với nội dung sau:

Y2Server.cs (v1):

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

public class Y2Server {

private const int BUFFER_SIZE=1024;
private const int PORT_NUMBER=9999;

static ASCIIEncoding encoding=new ASCIIEncoding();

public static void Main() {
try {
IPAddress address = IPAddress.Parse("127.0.0.1");

TcpListener listener=new TcpListener(address,PORT_NUMBER);

// 1. listen
listener.Start();

Console.WriteLine("Server started on "+listener.LocalEndpoint);
Console.WriteLine("Waiting for a connection...");

Socket socket=listener.AcceptSocket();
Console.WriteLine("Connection received from " + socket.RemoteEndPoint);

// 2. receive
byte[] data=new byte[BUFFER_SIZE];
socket.Receive(data);

string str=encoding.GetString(data);

// 3. send
socket.Send(encoding.GetBytes("Hello "+str));

// 4. close
socket.Close();
listener.Stop();

}
catch (Exception ex) {
Console.WriteLine("Error: " + ex);
}
Console.Read();
}
}

Y2Client.cs (v1):

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Net.Sockets;

public class Y2Client{

    private const int BUFFER_SIZE=1024;
    private const int PORT_NUMBER=9999;

    static ASCIIEncoding encoding= new ASCIIEncoding();

    public static void Main() {

        try {
            TcpClient client = new TcpClient();

            // 1. connect
            client.Connect("127.0.0.1",PORT_NUMBER);
            Stream stream = client.GetStream();

            Console.WriteLine("Connected to Y2Server.");
            Console.Write("Enter your name: ");

            string str = Console.ReadLine();

            // 2. send
            byte[] data=encoding.GetBytes(str);

            stream.Write(data,0,data.Length);

            // 3. receive
            data =new byte[BUFFER_SIZE];
            stream.Read(data,0,BUFFER_SIZE);

            Console.WriteLine(encoding.GetString(data));

              // 4. Close
stream.Close();
            client.Close();
        }

        catch (Exception ex) {
            Console.WriteLine("Error: " + ex);
        }

        Console.Read();
    }
}

Để kiểm tra ví dụ, bạn chạy server trước, cửa sổ console của server sẽ hiển thị:

Server started on 127. 0.0.1:9999

Waiting for a connection…

Tiếp đến cho chạy client, nếu kết nối thành công, server sẽ hiển thị thêm dòng thông báo tương tự như sau:

Connection received from 127.0.0.1:2578

Chuyển qua cửa sổ console của client và nhập tên của bạn vào, nếu nhận được dữ liệu, server sẽ gửi trả lại dòng thông điệp “Hello [Your Name]”

Connected to Y2Server.

Enter your name: Yin Yang

Hello Yin Yang

Ngay sau bước này, cả server và client đều thực hiện đóng kết nối.
Example v2: Sử dụng StreamReader và StreamWriter

Sẽ tiện lợi hơn nếu ta sử dụng StreamReader và StreamWriter để gửi nhận dữ liệu mà không cần bước chuyển đổi qua lại mảng byte. Các đối tượng StreamReader và StreamWriter có thể được khởi tạo trực tiếp từ NetworkStream. Thuộc tính AutoFlush của StreamWriter thường được đặt là true để tự động gửi dữ liệu mà không cần đợi bộ đệm đầy hoặc bạn phải gọi thủ công phương thức Flush().

Ví dụ sau sử dụng vòng lặp để thực hiện gửi nhận dữ liệu liên tục giữa server/client cho đến khi client nhập vào chuỗi “exit”:

Y2Server.cs (v2):

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;

public class Y2Server {

    private const int BUFFER_SIZE=1024;
    private const int PORT_NUMBER=9999;

    static ASCIIEncoding encoding=new ASCIIEncoding();

    public static void Main() {
        try {
            IPAddress address = IPAddress.Parse("127.0.0.1");

            TcpListener listener=new TcpListener(address,PORT_NUMBER);

            // 1. listen
            listener.Start();

            Console.WriteLine("Server started on "+listener.LocalEndpoint);
            Console.WriteLine("Waiting for a connection...");

            Socket socket=listener.AcceptSocket();
            Console.WriteLine("Connection received from " + socket.RemoteEndPoint);

            var stream = new NetworkStream(socket);
            var reader=new StreamReader(stream);
            var writer=new StreamWriter(stream);
            writer.AutoFlush=true;

            while(true)
            {
                // 2. receive
                string str=reader.ReadLine();
                if(str.ToUpper()=="EXIT")
                {
                    writer.WriteLine("bye");
                    break;
                }
                // 3. send
                writer.WriteLine("Hello "+str);
            }
            // 4. close
            stream.Close();
            socket.Close();
            listener.Stop();
        }
        catch (Exception ex) {
            Console.WriteLine("Error: " + ex);
        }
        Console.Read();
    }
}

Y2Client.cs (v2):

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Net.Sockets;

public class Y2Client{

    private const int BUFFER_SIZE=1024;
    private const int PORT_NUMBER=9999;

    static ASCIIEncoding encoding= new ASCIIEncoding();

    public static void Main() {

        try {
            TcpClient client = new TcpClient();

            // 1. connect
            client.Connect("127.0.0.1",PORT_NUMBER);
            Stream stream = client.GetStream();

            Console.WriteLine("Connected to Y2Server.");
            while(true)
            {
                Console.Write("Enter your name: ");

                string str = Console.ReadLine();
                var reader=new StreamReader(stream);
                var writer=new StreamWriter(stream);
                writer.AutoFlush=true;

                // 2. send
                writer.WriteLine(str);

                // 3. receive
                str=reader.ReadLine();
                Console.WriteLine(str);
                if(str.ToUpper()=="BYE")
                    break;
            }
            // 4. close
            stream.Close();
            client.Close();
        }

        catch (Exception ex) {
            Console.WriteLine("Error: " + ex);
        }

        Console.Read();
    }
}

Bạn chạy ví dụ này giống như ví dụ đầu tiên và gõ ‘exit’ vào client để thoát ứng dụng.