Trong các ứng dụng có kiến trúc máy khách-máy chủ, máy chủ cung cấp một số dịch vụ, chẳng hạn như xử lý các truy vấn cơ sở dữ liệu hoặc gửi thông tin thời tiết hiện tại. Máy khách sử dụng dịch vụ do máy chủ cung cấp, hiển thị kết quả truy vấn cơ sở dữ liệu cho người dùng hoặc đưa ra thông tin về thời tiết hiện tại cho người dùng. Giao tiếp xảy ra giữa máy khách và máy chủ phải đáng tin cậy. Đó là, không có dữ liệu nào có thể bị hủy và nó phải đến phía máy khách theo cùng thứ tự mà máy chủ đã gửi nó.

TCP cung cấp một kênh liên lạc point-to-point đáng tin cậy mà các ứng dụng máy chủ-máy khách trên Internet sử dụng để liên lạc với nhau. Để giao tiếp qua TCP, chương trình máy khách và chương trình máy chủ sẽ thiết lập kết nối với nhau. Mỗi chương trình liên kết một socket vào cuối kết nối của nó. Để giao tiếp, máy khách và máy chủ mỗi lần đọc và ghi vào socket được liên kết với kết nối.

Thuật ngữ lập trình socket dùng để chỉ các chương trình viết thực thi trên nhiều máy tính trong đó các thiết bị được kết nối với nhau bằng mạng.

Có hai giao thức truyền thông mà người ta có thể sử dụng để lập trình socket

  1. Giao thức gói dữ liệu người dùng (UDP)
  2. Giao thức điều khiển truyền (TCP)

Trong khuôn khổ bài viết, tôi xin giới thiệu tổng quan về socket và lập trình giao tiếp giữa client và server thông qua socket trong Java bằng ví dụ cơ bản.

Socket là gì?

Socket  là một điểm cuối của liên kết giao tiếp hai chiều giữa hai chương trình đang chạy trên mạng. Các lớp socket được sử dụng để thể hiện kết nối giữa chương trình máy khách và chương trình máy chủ. 

Điểm cuối này bao gồm một địa chỉ IP và số cổng kết nối. Gói java.net cung cấp hai lớp – Socket và ServerSocket – tương ứng triển khai phía máy khách của kết nối và phía máy chủ của kết nối.

Socket hoạt động như thế nào?

Thông thường, một máy chủ hoạt động có một socket liên kết với một số cổng cụ thể. Máy chủ khi hoạt động sẽ chờ và lắng nghe socket của máy khách thực hiện tạo request.

Trong khi đó, bên phía máy khách khi hoạt động đã biết hostname – tên của máy chủ và port number – số cổng mà máy chủ đang nghe. Để thực hiện một yêu cầu kết nối, máy khách sử dụng hostname và port number này để cố gắng kết nối với máy chủ. Bên cạnh đó, máy khách cũng phải tự khai báo cổng mà nó sử dụng trong suốt quá trình kết nối.

Nếu máy chủ chấp nhận kết nối này, nó sẽ tạo một socket được liên kết với port mà nó sử dụng, đồng thời điểm cuối của kết nối này là địa chỉ và port của máy khách.

Về phía máy khách sau khi đã có kết nối với máy chủ, môt socket cũng được tạo ra và máy khách có thể dùng socket này để liên lạc với máy chủ.

Vậy giữa máy chủ và máy khách hiện thời đã có thể liên lạc được với nhau thông qua việc ghi hoặc đọc dữ liệu từ socket vừa được tạo.

Làm việc với Socket trong Java

Gói java.net cung cấp class Socket, nhằm thực hiện các kết nối hai chiều bên phía máy khách để các ứng dụng Java trong mạng có thể giao tiếp với nhau.  Lớp này nằm trên một triển khai phụ thuộc nền tảng, nó ẩn đi mọi chi tiết về hệ thống tách biệt với chương trình Java, bằng cách này thay vì dựa vào mã gốc, các chương trình Java của bạn có thể giao tiếp qua mạng một cách độc lập.

Thêm vào đó java.net cung cấp class SocketServer, phía máy chủ có thể sử dụng socket này để nghe và nhận các yêu cầu kết nối từ phía máy khách.

Đọc và ghi dữ liệu từ socket

Sau đây là một ứng dụng demo để diễn tả quá trình đọc ghi dữ liệu từ socket giữa Server và Client.

Client-side

Đầu tiên ta tạo một class Client, có nhiệm vụ kết nối đến Server bằng cách mở một Socket có tham số là hostname và port mà server sử dụng:

Tiếp theo client sẽ đọc dữ liệu người dùng nhập vào và chuyển dữ liệu này tới server bằng cách ghi vào socket bằng việc mở một luồng ghi vào stream bằng PrintWriter

Những dữ liệu này sau khi được ghi vào socket sẽ được server tiếp nhận và xử lý sau đó lại gửi trả kết quả vào socket để client mở luồng đọc cho BufferedReader.

Ở đây client đã thực hiện hai việc là đọc và ghi từ socket thông qua các luồng vào ra, qua đó có thể gửi và nhận dữ liệu từ server.

Tiếp theo ta đi đến cách triển khai socket trên server.

Server-side

Tương tự như bên phía client, server đầu tiên cũng tạo một socket nhưng sử dụng Class ServerSocket thay vì Socket ở phía client.

Khởi tạo một socket với tham số là số cổng mà server sử dụng:

Tới đây một socket sẽ được tạo ra khi server lắng nghe được một yêu cầu kết nối từ client thông qua cổng đã chỉ định. Khi có yêu cầu, server sẽ chấp nhận kết nối này bằng phương thức accept() và kết quả khi thực hiện phương thức này là một socket được kết nối với điểm cuối là client.

Tương tự như bên phía client, ta cũng xây dựng các luồng vào ra cho socket bên phía server

Dữ liệu gửi từ client sẽ được lấy từ luồng vào thông qua phương thức readLine() của BufferedReader được tạo trên đó. Sau khi xử lý, dữ liệu tiếp tục được ghi vào luồng ra của socket trên server thông qua phương thức println() của PrintWriter để trả về phía Client.

Luồng xử lý này có thể được diễn tả như sau:

Kết quả của quá trình xử lý ta có được:

Trong ứng dụng trên người dùng phía client sẽ nhập vào tên và server sẽ gửi lại lời chào cho client

Chương trình sẽ chạy đến khi nào gặp điều kiện end-of-input, ta có thể đặt thêm điều kiện kết thúc kết nối và đóng tất cả các luồng bằng cách thêm các phương thức close() cho luồng in/out

Sau khi kết thúc quá trình kết nối, Java runtime sẽ tự động đóng các reader và writer tương ứng với socket  và với luồng nhập xuất đồng thời đóng socket kết nối với server.

Trên đây là một ví dụ minh họa quá trình giao tiếp giữa Client và Server thông qua việc thiết lập các socket. Tóm tắt lại chúng ta cần làm những bước sau:

  1. Mở socket cho cả hai phía
  2. Mở các luồng đọc ghi cho socket
  3. Đọc và ghi dữ liệu từ luồng dựa vào giao thức của server
  4. Đóng luồng
  5. Đóng socket

Author: Bình Nguyễn

Xem thêm các tài liệu và bài chia sẻ tại đây.


Hãy tham gia nhóm Học lập trình để thảo luận thêm về các vấn đề cùng quan tâm.