Đường Đến Tim Crush Bằng Java - Socket (Part 1)
Trong cuộc đời, điều quan trọng nhất của hạnh phúc chính là “Tình yêu”. Khi yêu, chúng ta trao cho nhau những tình cảm đặc biệt và tạo ra một sự kết nối bền chặt với nửa còn lại. Kết nối là sự tiếp xúc, trao đổi của hai người, cho đi yêu thương và nhận lại yêu thương. Lập trình cũng tương tự như tình yêu, thứ kết nối trong tình yêu thì trong lập trình được gọi là: Socket.
Hiểu đơn giản Socket là gì ?
Socket là một điểm cuối (end-point) của liên kết truyền thông hai chiều (two-way communication) giữa hai chương trình chạy trên mạng. Các Socket cung cấp kỹ thuật giao tiếp giữa hai máy tính sử dụng TCP và UDP. Một chương trình Client tạo một socket trên đầu cuối của giao tiếp và cố gắng để kết nối socket đó tới một Server Socket trên máy khác.
Ơ , vậy TCP và UDP là cái quái gì ?
TCP / UDP
Vậy TCP và UDP là gì? Nó có vai trò như thế nào trong việc truyền dữ liệu qua mạng? Chúng ta cần hiểu rõ hai khai niệm này .
- Theo Wikipedia , TCP là viết tắt của cụm từ
Transmission Control Protocol
- Giao thức điều khiển vận chuyển, TCP cho phép các máy tính cùng mạng có thể kết nối với nhau để trao đổi các package (gói tin). TCP đảm bảo cho việc chuyển các gói tin đúng thứ tự và đáng tin cậy bằng quy trình bắt tay ba bước. (bắt tay ba bước là gì thị bạn nên đọc thêm tài liệu bạn nhớ) - Cũng theo Wikipedia , UDP là viết tắt của
User Datagram Protocol
- Giao thức gói dữ liệu người dùng, UDP cho phép những chương trình trên mạng máy tính có thể truyền dữ liệu đến máy khác một cách nhanh chóng nhưng không đáng tin cậy vì thứ tự đến của các dữ liệu có thể sai và khi mất dữ lệu sẽ mặc kệ mà không truyền lại như TCP. - Sự khác nhau của hai giao thức này là: UDP nhanh hơn nhưng dữ liệu không đảm bảo toàn vẹn. TCP thì đảm bảo toàn vẹn dữ liệu nhưng mà chậm hơn.
Các bước khi một Socket kết nối đến một ServerSocket
- Server khởi tạo một ServerSocket với cổng cho phép.
- Server gọi lệnh
Accept()
đểể chờ Scoket kết nối đến. - Ở phía Client, sau khi ServerSocket được tạo và đang đứng từ hôm qua để chờ Socket tới, Client tạo một Socket với cổng và IP của ServerSocket để kết nối. Bước này xảy ra ngay khi bạn khởi tạo Socket - nghĩa là bạn khởi tạo, đồng thời cũng kết nối luôn đến ServerSocket.
- Nếu kết nối thành công , bạn có thể sử dụng Socket này để liên lạc với Server.
- Sau khi hàm Accept() nhận được kết nối, một instance của Socket đó được tạo và lưu trên Server tại thời điểm Runtime.
- Và cuối cùng là Server và Client có thể thoải mái giao tiếp, trò truyện, trao cho nhau những tài liệu học tập nặng đến hàng Gygabyte, vv...
- Kết nối sẽ bị ngắt nếu một trong hai dừng chương trình, Server hoặc Client đơn phương đóng Socket (tình ta chấm dứt từ đây ~~).
Hình minh họa cho socket
Sau khi chém gió một hồi, thứ tôi muốn anh em nhớ đó là :
- Socket là gì? Là công cụ giao tiếp hai chiều của hai thiết bị - Tức là cả hai máy có thể trao đổi thông tin và nhận thông tin từ nhau, sử dụng giao thức TCP và UDP.
- Socket để làm gì? Chúng ta có thể dùng Socket để gửi data (File , message ,...) từ máy này sang máy khác. Đó có thể là một số Int, một String, một Ọbject, ....
- Socket hoạt động như thế nào? Socket là một chiếc ống, bạn có thể đọc dữ liệu trong đó bằng
InputStream
, ghi dữ liệu vào nó để truyền đến máy khác bằngOutputStream
. - Muốn Socket hoạt động thì phải làm thế nào? Trước tiên phải có ServerSocket, khởi tạo ServerSocket trong mạng lan. Sau đó là tạo Socket kết nối đến IP và Port của ServerSocket.
Các Method hay sử dụng trong lập trình Socket
Trước khi khai sáng và dẫn dắt các bạn đến với Project đó, tôi sẽ cung cấp cho các bạn những kiến thức cơ bản nhất về Socket.
Vậy nên, ở phần này, thứ tôi muốn chia sẻ vởi bạn không phải là một mớ các định nghĩa, nguyên lý mà đó là những thứ mà tôi hay dùng khi làm việc với Socket.
ServerSocket
Để tạo một ServerSocket, chúng ta có thể tạo bằng một trong 4 cách
public ServerSocket(int port ) throw IOException
: Cách này giúp chúng ta có thể tạo một ServerSocket với một cổng đã xác định, nếu cổng đó đã có ứng dụng khác sử dụng, ngoại lệ sẽ xuất hiện.public ServerSocket(int port, int backlog , InetAddress address) throw IOException
: khi nào chúng ta nên dùng Constructor này? khi mà Server có quá nhiều địa chỉ IP và chỉ rõ ràng rằng: IP nào sẽ được Server dùng để chập nhận Socket kết nối vào.public ServerSocket() throw IOException
: Tạo một ServerSocket không giới hạn, sử dụng method Bind() khi đã kết nối đến Server.public ServerSocket(int port, int backlog) throw IOException
: Mở rộng của constructor trên, int backlog là tham số định nghĩa số Socket tối đa đang vào để lưu giữ trong hàng đợi ( nghe cứ như đám Friendzone của Crush nhỉ ).
Các method thông dụng của ServerSocket.
Int getLocalPort()
: đương nhiên rồi, nhìn tên hàm bạn biết ngay là lấy địa chỉ cổng của Server rồi. Ơ mà khoan, chẳng phải ở constructor có truyền vào cổng hay sao? chả nhẽ lại có ông não cá vàng đến nỗi khôn nhớ mình truyền vào cổng nào à? Không bạn ơi, method này dùng khi bạn khởi tạo Server mà không truyền Port ( cổng ). Server sẽ tự động tìm một cổng đang free để làm cổng kết nối của Server, và hàm này giúp bạn biết được cái cổng ngẫu nhiên đó.Socket accept() throw IOException
: "Chào Client , Anh dứng đây từ chiều @@". Nói không ngoa khi đây là một trong những hàm quan trọng nhất trong lập trình socket. Server thì phải có Client kết nối, hàm này cho phép đợi Socket kết nối đến và trả về cho Server chính cái Socket đó. Nhưng nếu không có Socket thì sao? Thì em ấy sẽ đợi vĩnh viễn (khi nào app bị tắt thì dừng) hoặc đơi trong một thời gian có hạn, thứ sẽ được trình bày ở bên dưới.v
oid setToTimeout(int timeOut)
: Đây, hàm này để xác định thời gian chờ socket tối đa khi accept() được gọi. Khi quá thời gian chờ, accept() sẽ thôi không chờ nữa.
Socket
Sau đây là những kiến thức cơ bản và những hàm thông dụng của Socket.
Constructor : Cũng như ServerSocket, để khởi tạo một Socket kết nối đến Server, chúng ta có 5 cách và tôi sẽ giới thiệu cho các bạn 3 cách phổ biến.
public Socket(String Host, int port) throw UnknowhostException, IOException
: Cho phép khởi tạo và kết nối Socket đến máy có địa chỉ IP truyền vào, tại cổng xác định.public Socket()
: tạo một Socket không tham số, muốn kết nối đến Server, ta phải dùng lệnh Connect().public Socket(InetAddress Host, int port) throw IOException
: về nguyên lý, constructor này cũng laàm những công việc như cái trên, khác một điều là địa chỉ của Server được biểu thị qua Class InetAddress.
Các hàm cơ bản:
public InetAddress getInetAddress()
: Lấy về địa chỉ của Server mà Socket kết nối tới.public int getPort()
: trả về cổng mà socket kết nối trên Server.public int getLoaclPort()
: trả về cổng mà socket kết nối trên thiết bị nội bộ.public InputSteam getInputStream()
: Trả vể InputStream của Socket - Thứ có thể đọc Data từ OutputStream phía máy dang kết nối.public OutputStream getOutputStream()
: trả về OutputStream đển bạn ghi dữ liệu vào đó, thứ mà truyền đến InputStream phía máy đang kết nối.public voi close() throw IOException
: đóng Socket hiện tại, ngắt kết nối đến Server.public void connect(SocketAddress host, int timeout) throw IOException
: Như đã nói ở trên, connect() chỉ nên dùng khi bạn khai bảo một Socket không tham số. Nhớ kĩ nhé, có tham số thì không cần dùng.
Ví dụ minh họa
Sau khi nhai xong cái đống kiến thức có vẻ mau quên kia, tôi dám cá anh em vẫn chưa thể viết được một Project nhỏ về gửi tin nhắn qua Socket. Nhưng không sao, tôi vẫn đứng đây từ chiều.
Và dưới đây là code mẫu về Server.
public class Server {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
// Khởi tạo một socket với cổng 9999
ServerSocket server = new ServerSocket(9999);
// chờ cho Socket khác kết nối đến
Socket socket = server.accept();
// tạo đối tượng DataInputStream để đọc dữ liệu
DataInputStream dIn = new DataInputStream(socket.getInputStream());
// Tạo đối tượng DataOutputStream để truyền dữ liệu đi
DataOutputStream dOut = new DataOutputStream(socket.getOutputStream());
while (true) {
System.out.println("Enter message : ");
// nhập tin nhắn và gửi cho client
String mesage = sc.nextLine();
// hàm WriteUTF() của OutputStream cho phép nó gửi Data dạng String đến InputStream của Client
dOut.writeUTF(mesage);
System.out.println("You say : " + mesage);
// đọc tin nhắn từ client , dùng ReadUTF để đọc String
String client_message = dIn.readUTF();
System.out.println("Client say : " + client_message);
}
}
}
Code mẫu về Client
public class Client {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
// tao socket kết nối đển Server
Socket socket = new Socket("localhost", 9999);
// tạo đối tượng DataInputStream để đọc dữ liệu
DataInputStream dIn = new DataInputStream(socket.getInputStream());
// Tạo đối tượng DataOutputStream để truyền dữ liệu đi
DataOutputStream dOut = new DataOutputStream(socket.getOutputStream());
while (true) {
System.out.println("Enter message : ");
// nhập tin nhắn và gửi cho server
String mesage = sc.nextLine();
// hàm WriteUTF() của OutputStream cho phép nó gửi Data dạng String đến InputStream của Client
dOut.writeUTF(mesage);
System.out.println("You say : " + mesage);
// đọc tin nhắn từ client , dùng ReadUTF để đọc String
String client_message = dIn.readUTF();
System.out.println("Client say : " + client_message);
}
}
}
Tạm kết
Như vậy, tôi đã cung cấp cho anh em kiến thức cơ bản về làm việc với Socket. Nếu anh em muốn tự phát triển bài toán lên, anh em nên tìm hiểu thêm về Java IO (cụ thể là InputStream và OutputStream). Nhưng nếu anh em chưa làm chủ được sức mạnh, thì chính tôi sẽ hướng dẫn anh em phát triển bài toán khó hơn một chút, đó là cho thêm tính năng gửi File và thêm giao dện "nung ninh" trong phần 2 tới. Cảm ơn anh em đã dành thời gian đọc bài, nếu có gì muốn góp ý hay có ý tưởng về những project với Java, hãy để lại comment để cùng nhau trao đổi nhé :)