Lập trình Socket cho giao thức TCP
Trang 1Chương 6
Lập trình Socket cho giao thức TCP
1 Mô hình client/server
Mô hình được phổ biến nhất và được chấp nhận rộng rãi trong các hệ thống phân tán
là mô hình client/server Trong mô hình này sẽ có một tập các tiến trình mà mỗi tiến trìnhđóng vai trò như là một trình quản lý tài nguyên cho một tập hợp các tài nguyên cho trước vàmột tập hợp các tiến trình client trong đó mỗi tiến trình thực hiện một tác vụ nào đó cần truyxuất tới tài nguyên phần cứng hoặc phần mềm dùng chung Bản thân các trình quản lý tàinguyên cần phải truy xuất tới các tài nguyên dùng chung được quản lý bởi một tiến trìnhkhác, vì vậy một số tiến trình vừa là tiến trình client vừa là tiến trình server Các tiến trìnhphát ra các yêu cầu tới các server bất kỳ khi nào chúng cần truy xuất tới một trong các tàinguyên của các server Nếu yêu cầu là đúng đắn thì server sẽ thực hiện hành động đượcyêu cầu và gửi một đáp ứng trả lời tới tiến trình client
Mô hình client/server cung cấp một cách tiếp cận tổng quát để chia sẻ tài nguyêntrong các hệ thống phân tán Mô hình này có thể được cài đặt bằng rất nhiều môi trườngphần cứng và phần mềm khác nhau Các máy tính được sử dụng để chạy các tiến trìnhclient/server có nhiều kiểu khác nhau và không cần thiết phải phân biệt giữa chúng; cả tiếntrình client và tiến trình server đều có thể chạy trên cùng một máy tính Một tiến trình server
có thể sử dụng dịch vụ của một server khác
Mô hình truyền tin client/server hướng tới việc cung cấp dịch vụ Quá trình trao đổi dữliệu bao gồm:
1 Truyền một yêu cầu từ tiến trình client tới tiến trình server
2 Yêu cầu được server xử lý
3 Truyền đáp ứng cho client
Mô hình truyền tin này liên quan đến việc truyền hai thông điệp và một dạng đồng bộhóa cụ thể giữa client và server Tiến trình server phải nhận thức được thông điệp được yêucầu ở bước một ngay khi nó đến và hành động phát ra yêu cầu trong client phải được tạmdừng (bị phong tỏa) và buộc tiến trình client ở trạng thái chờ cho tớ khi nó nhận được đápứng do server gửi về ở bước ba
Mô hình client/server thường được cài đặt dựa trên các thao tác cơ bản là gửi (send)
và nhận (receive)
Trang 2Hình 4.1Quá trình giao tiếp client và server có thể diễn ra theo một t rong hai chế độ: bị phongtỏa (blocked) và không bị phong tỏa (non-blocked).
Chế độ bị phong tỏa (blocked):
Trong chế độ bị phong tỏa, khi tiến trình client hoặc server phát ra lệnh gửi dữ liệu(send), việc thực thi của tiến trình sẽ bị tạm ngừng cho tới khi tiến trình nhận phát ra lệnhnhận dữ liệu (receive)
Tương tự đối với tiến trình nhận dữ liệu, nếu tiến trình nào đó (client hoặc server) phát
ra lệnh nhận dữ liệu, mà tại thời điểm đó chưa có dữ liệu gửi tới thì việc thực thi của tiếntrình cũng sẽ bị tạm ngừng cho tới khi có dữ liệu gửi tới
Chế độ không bị phong tỏa (non-blocked)
Trong chế độ này, khi tiến trình client hay server phát ra lệnh gửi dữ liệu thực sự, việcthực thi của tiến trình vẫn được tiến hành mà không quan tâm đến việc có tiến trình nào phát
ra lệnh nhận dữ liệu đó hay không
Tương tự cho trường hợp nhận dữ liệu, khi tiến trình phát ra lệnh nhận dữ liệu, nó sẽnhận dữ liệu hiện có, việc thực thi của tiến trình vẫn được tiến hành mà không quan tâm đếnviệc có tiến trình nào phát ra lệnh gửi dữ liệu tiếp theo hay không
2 Các kiến trúc Client/Server
2.1 Client/Server hai tầng (two-tier client/server)
Kiến trúc client/server đơn giản nhất là kiến trúc hai tầng Trong thực tế hầu hết cáckiến trúc client/server là kiến trúc hai tầng Một ứng dụng hai tầng cung cấp nhiều trạm làmviệc với một tầng trình diễn thống nhất, tầng này truyền tin với tầng lưu trữ dữ liệu tập trung.Tầng trình diễn thông thường là client, và tầng lưu trữ dữ liệu là server
Hầu hết các ứng dụng Internet như là email, telnet, ftp thậm chí là cả Web là các ứngdụng hai tầng Phần lớn các lập trình viên trình ứng dụng viết các ứng dụng client/server có
xu thế sử dụng kiến trúc này
Tiến trình đang phong tỏa
Tiến trình đang xử lý Request message
Request message
Reply Execution Wait
Server Client
Trang 3Trong ứng dụng hai tầng truyền thống, khối lượng công việc xử lý được dành chophía client trong khi server chỉ đơn giản đóng vai trò như là chương trình kiểm soát luồngvào ra giữa ứng dụng và dữ liệu Kết quả là không chỉ hiệu năng của ứng dụng bị giảm đi dotài nguyên hạn chế của PC, mà khối lượng dữ liệu truyền đi trên mạng cũng tăng theo Khitoàn bộ ứng dụng được xử lý trên một PC, ứng dụng bắt buộc phải yêu cầu nhiều dữ liệutrước khi đưa ra bất kỳ kết quả xử lý nào cho người dùng Nhiều yêu cầu dữ liệu cũng làmgiảm hiệu năng của mạng Một vấn đề thường gặp khác đối với ứng dụng hai tầng là vấn đềbảo trì Chỉ cần một thay đổi nhỏ đối với ứng dụng cũng cần phải thay đổi lại toàn bộ ứngdụng client và server.
Trang 4Theo kiến trúc ba tầng, một ứng dụng được chia thành ba tầng tách biệt nhau về mặtlogic Tầng đầu tiên là tầng trình diễn thường bao gồm các giao diện đồ họa Tầng thứ hai,còn được gọi là tầng trung gian hay tầng tác nghiệp Tầng thứ ba chứa dữ liệu cần cho ứngdụng Tầng thứ ba về cơ bản là chương trình thực hiện các lời gọi hàm để tìm kiếm dữ liệucần thiết Tầng trình diễn nhận dữ liệu và định dạng nó để hiển thị Sự tách biệt giữa chứcnăng xử lý với giao diện đã tạo nên sự linh hoạt cho việc thiết kế ứng dụng Nhiều giao diệnngười dùng được xây dựng và triển khai mà không làm thay đổi logic ứng dụng
Tầng thứ ba chứa dữ liệu cần thiết cho ứng dụng Dữ liệu này có thể bao gồm bất kỳnguồn thông tin nào, bao gồm cơ sở dữ liệu như Oracale, SQL Server hoặc tài liệu XML
2.3 Kiến trúc n-tầng
Kiến trúc n-tầng được chia thành các tầng như sau:
Tầng giao diện người dùng: quản lý tương tác của người dùng với ứng dụng
Tầng logic trình diễn: Xác định cách thức hiển thị giao diện người dùng và các yêucầu của người dùng được quản lý như thế nào
Tầng logic tác nghiệp: Mô hình hóa các quy tắc tác nghiệp,
Tầng các dịch vụ hạ tầng: Cung cấp một chức năng bổ trợ cần thiết cho ứng dụngnhư các thành phần (truyền thông điệp, hỗ trợ giao tác)
3 Mô hình truyền tin socket
Hình 4.46
và nhậnClose()
Socket()
Bind()
Connect()
Các chức năng gửi
và nhậnClose()
1
345
72
Trang 5Khi lập trình, ta cần quan tâm đến chế độ bị phong tỏa, vì nó có thể dẫn đến tìnhhuống một tiến trình nào đó sẽ rơi vào vòng lặp vô hạn của quá trình gửi hoặc nhận.
Trong chương 1 chúng ta đã biết hai giao thức TCP và UDP là các giao thức tầnggiao vận để truyền dữ liệu Mỗi giao thức có những ưu và nhược điểm riêng Chẳng hạn,giao thức TCP có độ tin cậy truyền tin cao, nhưng tốc độ truyền tin bị hạn chế do phải có giaiđoạn thiết lập và giải phóng liên kết khi truyền tin, khi gói tin có lỗi hay bị thất lạc thì giaothức TCP phải có trách nhiệm truyền lại,…Ngược lại, giao thức UDP có tốc độ truyền tin rấtnhanh vì nó chỉ có một cơ chế truyền tin rất đơn giản: không cần phải thiết lập và giải phóngliên kết Khi lập trình cho TCP ta sử dụng các socket luồng, còn đối với giao thức UDP ta
Thật may mắn, ta không cần phải tự thực hiện công việc này Socket là một cuộc cáchmạng của Berkeley UNIX Chúng cho phép người lập trình xem một liên kết mạng như làmột luồng mà có thể đọc dữ liệu ra hay ghi dữ liệu vào từ luồng này
Về mặt lịch sử Socket là một sự mở rộng của một trong những ý tưởng quan trọngnhất của UNIX: tất cả các thao tác vào/ra giống như vào ra tệp tin đối với người lập trình,cho dù ta đang làm việc với bàn phím, màn hình đồ họa, một file thông thường, hay một liênkết mạng Các Socket che dấu người lập trình khỏi các chi tiết mức thấp của mạng như môikiểu đường truyền, các kích thước gói, yêu cầu truyền lại gói, các địa chỉ mạng
Một socket có thể thực hiện bảy thao tác cơ bản:
Kết nối với một máy ở xa (ví dụ, chuẩn bị để gửi và nhận dữ liệu)
Chấp nhận liên kết từ các máy ở xa trên cổng đã được gán
Lớp Socket của Java được sử dụng bởi cả client và server, có các phương thứctương ứng với bốn thao tác đầu tiên Ba thao tác cuối chỉ cần cho server để chờ các clientliên kết với chúng Các thao tác này được cài đặt bởi lớp ServerSocket Các socket choclient thường được sử dụng theo mô hình sau:
Một socket mới được tạo ra bằng cách sử dụng hàm Socket()
Socket cố gắng liên kết với một host ở xa
Mỗi khi liên kết được thiết lập, các host ở xa nhận các luồng vào và luồng ra từsocket, và sử dụng các luồng này để gửi dữ liệu cho nhau Kiểu liên kết này được gọi
là song công (full-duplex)-các host có thể nhận và gửi dữ liệu đồng thời Ý nghĩa của
dữ liệu phụ thuộc vào giao thức
Trang 6 Khi việc truyền dữ liệu hoàn thành, một hoặc cả hai phía ngắt liên kết Một số giaothức, như HTTP, đòi hỏi mỗi liên kết phải bị đóng sau mỗi khi yêu cầu được phục vụ.Các giao thức khác, chẳng hạn FTP, cho phép nhiều yêu cầu được xử lý trong mộtliên kết đơn.
4 Socket cho Client
4.1 Các constructor
public Socket(String host, int port) throws UnknownHostException, IOException
Hàm này tạo một socket TCP với host và cổng xác định, và thực hiện liên kết với host
System.err.println(e);
} catch(IOException e){
có thể ngăn ngừa các gói tin của ta tới đích
Ví dụ: Viết chương trình để kiểm tra trên 1024 cổng đầu tiên những cổng nào đang cóserver hoạt động
try{
Socket s=new Socket(host,i);
System.out.println("Co mot server dang hoat dong tren cong:"+i); }
Trang 7catch(UnknownHostException e){
System.err.println(e);
} catch(IOException e){
System.err.println(e);
} }
}
}
public Socket(InetAddress host, int port)throws IOException
Tương tự như constructor trước, constructor này tạo một socket TCP với thông tin làđịa chỉ của một host được xác định bởi một đối tượng InetAddres và số hiệu cổngport, sau đó nó thực hiện kết nối tới host Nó đưa ra ngoại lệ IOException nhưngkhông đưa ra ngoại lệ UnknownHostException Constructor đưa ra ngoại lệ trongtrường hợp không kết nối được tới host
public Socket (String host, int port, InetAddress interface, int localPort) throws IOException, UnknownHostException
Constructor này tạo ra một socket với thông tin là địa chỉ IP được biểu diễn bởi mộtđối tượng String và một số hiệu cổng và thực hiện kết nối tới host đó Socket kết nốitới host ở xa thông qua một giao tiếp mạng và số hiệu cổng cục bộ được xác định bởihai tham số sau Nếu localPort bằng 0 thì Java sẽ lựa chọn một cổng ngẫu nhiên cósẵn nằm trong khoảng từ 1024 đến 65535
public Socket (InetAddress host, int port, InetAddress interface, int localPort) throws IOException, UnknownHostException
Constructor chỉ khác constructor trên ở chỗ địa chỉ của host lúc này được biểu diễnbởi một đối tượng InetAddress
4.2 Nhận các thông tin về Socket
Đối tượng Socket có một số trường thông tin riêng mà ta có thể truy nhập tới chúngthông qua các phương thức trả về các thông tin này
public InetAddress getInetAddress()
Cho trước một đối tượng Socket, phương thức getInetAddress() cho ta biết host ở xa
mà Socket kết nối tới, hoặc liên kết đã bị ngắt thì nó cho biết host ở xa mà Socket đãkết nối tới
public int getPort()
Phương thức này cho biết số hiệu cổng mà Socket kết nối tới trên host ở xa
public int getLocalPort()
Thông thường một liên kết thường có hai đầu: host ở xa và host cục bộ Để tìm ra sốhiệu cổng ở phía host cục bộ ta gọi phương thức getLocalPort()
public InetAddress getLocalAddress()
Phương thức này cho ta biết giao tiếp mạng nào mà một socket gắn kết với nó
public InputStream getInputStream() throws IOException
Phương thức geInputStream() trả về một luồng nhập để đọc dữ liệu từ một socket vàochương trình Thông thường ta có thể gắn kết luồng nhập thô InputStream tới một
Trang 8luồng lọc hoặc một luồng ký tự nhằm đưa các chức năng tiện ích (chẳng hạn như cácluồng InputStream, hoặc InputStreamReader) Để tâng cao hiệu năng, ta có thể đệm
dữ liệu bằng cách gắn kết nó với luồng lọc BufferedInputStream hoặcBufferedReader
public OutputStream getOutputStream() throws IOException
Phương thức getOutputStream() trả về một luồng xuất thô để ghi dữ liệu từ ứng dụng
ra đầu cuối của một socket Thông thường, ta sẽ gắn kết luồng này với một luồng tiệnlợi hơn như lớp DataOuputStream hoặc OutputStreamWriter trước khi sử dụng nó Đểtăng hiệu quả ghi
Hai phương thức getInputStream() và getOutputStream() là các phương thức chophép ta lấy về các luồng dữ liệu nhập và xuất Như đã đề cập ở chương 3 vào ra trong Javađược tiến hành thông qua các luồng, việc làm việc với các socket cũng không phải là mộtngoại lệ Để nhận dữ liệu từ một máy ở xa ta nhận về một luồng nhập từ socket và đọc dữliệu từ luồng đó Để ghi dữ liệu lên một máy ở xa ta nhận về một luồng xuất từ socket và ghi
dữ liệu lên luồng Dưới đây là hình vẽ để ta hình dung trực quan hơn
Hình 4.5
4.3 Đóng Socket
Đến thời điểm ta đã có đầy đủ các thông tin cần thiết để triển khai một ứng dụng phíaclient Khi viết một chương trình ứng dụng phía client tất cả mọi công việc đều chuyển vềviệc quản lý luồng và chuyển đổi dữ liệu từ luồng thành dạng thức mà người sử dụng có thểhiểu được Bản thân các socket rất đơn giản bởi vì các phần việc phức tạp đã được che dấu
đi Đây chính là lý do để socket trở thành một lựa chọn có tính chiến lược cho lập trìnhmạng
public void close() throws IOException
Các socket được đóng một cách tự động khi một trong hai luồng đóng lại, hoặc khichương trình kết thúc, hoặc khi socket được thu hồi bởi gabbage collector Tuy nhiên, thực
tế cho thấy việc cho rằng hệ thống sẽ tự đóng socket là không tốt, đặc biệt là khi cácchương trình chạy trong khoảng thời gian vô hạn Để đóng một socket ta có thể dùngphương thức close()
Mỗi khi một Socket đã bị đóng lại, ta vẫn có thể truy xuất tới các trường thông tinInetAddress, địa chỉ cục bộ, và số hiệu cổng cục bộ thông qua các phưong thứcgetInetAddress(), getPort(), getLocalHost(), và getLocalPort() Tuy nhiên khi ta gọi cácphương thức getInputStream() hoặc getOutputStream() để đọc dữ liệu từ luồng đọcInputStream hoặc ghi dữ liệu OuputStream thì ngoại lệ IOException được đưa ra
Các socket đóng một nửa (Half-closed socket)
InputStream
OutputStream
SocketChương
trình
Trang 9Phương thức close() đóng cả các luồng nhập và luồng xuất từ socket Trong một sốtrường hợp ta chỉ muốn đóng một nửa kết nối, hoặc là luồng nhập hoặc là luồng xuất Bắtđầu từ Java 1.3, các phương thưc shutdownInput() và shutdownOutput() cho phép ta thựchiện điều này.
public void shutdownInput() throws IOException
public void shutdownOutput() throws IOException
Các phương thức này không thực sự ngắt liên kết Tuy nhiên, nó chỉ điều chỉnh luồngkết nối tới nó sao cho
Trong Java 1.4 đưa thêm vào hai phương thức các luồng nhập và luồng xuất mở hayđóng
public boolean isInputShutdown()
public boolean isOutputShutdown()
4.4 Thiết lập các tùy chọn cho Socket
4.4.1 TCP_NODELAY
public void setTcpNoDelay(boolean on) throws SocketException
public boolean getTcpNoDelay() throws SocketException
Thiết lập giá trị TCP_NODELAY là true để đảm bảo rằng các gói tin được gửi đi nhanhnhất có thể mà không quan tâm đến kích thước của chúng Thông thường, các gói tin nhỏđược kết hợp lại thành các gói tin lớn hơn trước khi được gửi đi Trước khi gửi đi một gói tinkhác, host cục bộ đợi để nhận các xác thực của gói tin trước đó từ hệ thống ở xa
4.4.2 SO_LINGER
public void setSoLinger(boolean on, int seconds) throws SocketException
public int getSoLinger() throws SocketException
Tùy chọn SO_LINGER xác định phải thực hiện công việc gì với datagram vẫn chưađược gửi đi khi một socket đã bị đóng lại Ở chế độ mặc định, phương thức close() sẽ cóhiệu lực ngay lập tức; nhưng hệ thống vẫn cố gắng để gửi phần dữ liệu còn lại NếuSO_LINGER được thiết lập bằng 0, các gói tin chưa được gửi đi bị phá hủy khi socket bịđóng lại Nếu SO_LINGER lớn hơn 0, thì phương thức close() phong tỏa để chờ cho dữ liệuđược gửi đi và nhận được xác thực từ phía nhận Khi hết thời gian qui định, socket sẽ bịđóng lại và bất kỳ phần dữ liệu còn lại sẽ không được gửi đi
4.4.3 SO_TIMEOUT
public void setSoTimeout(int milliseconds) throws SocketException
public int getSoTimeout() throws SocketException
Thông thường khi ta đọc dữ liệu từ mộ socket, lời gọi phương thức phong tỏa cho tớikhi nhận đủ số byte Bằng cách thiết lập phương thức SO_TIMEOUT, ta sẽ đảm bảo rằng lờigọi phương thức sẽ không phong tỏa trong khoảng thời gian quá số giây quy định
4.5 Các phương thức của lớp Object
Lớp Socket nạp chồng phương thức chuẩn của lớp java.lang.Object, toString() Vì cácsocket là các đối tượng tạm thời và thường chỉ tồn tại khi liên kết tồn tại
public String toString()
Phương thức toString() tạo ra một xâu ký tự như sau:
Socket[addr=www.oreilly.com/198.122.208.11,port=80,localport=50055]
Phương thức này thường hữu ích cho việc gỡ rối
4.6 Các ngoại lệ Socket
Trang 10Hầu hết các phương thức của lớp Socket được khai báo đưa ra ngoại lệ IOException,hoặc lớp con của lớp IOExcepton là lớp SocketException.
4.7 Các lớp SocketAddress
Lớp SocketAddress bắt đầu có từ phiên bản Java 1.4, biểu diễn một đầu cuối của liênkết Lớp SocketAddress là một lớp trừu tượng mà không có phương thức nào ngoàiconstrutor mặc định Lớp này có thể được sử dụng cho cả các socket TCP và socket khôngphải là TCP Các lớp con của lớp SocketAddress cung cấp thông tin chi tiết hơn thích hợpcho kiểu socket Trong thực tế, chỉ hỗ trợ TCP/IP
Mục đích chính của lớp SocketAddress là cung cấp một nơi lưu trữ các thông tin liênkết socket tạm thời (như địa chỉ IP và số hiệu cổng) có thể được sử dụng lại để tạo ra socketmới
public SocketAddress getRemoteSocketAddress()
public SocketAddress getLocalSocketAddress()
Cả hai phương thức này trả về giá trị null nếu socket vẫn chưa kết nối tới
5 Lớp ServerSocket
Lớp ServerSocket có đủ mọi thứ ta cần để viết các server bằng Java Nó có cácconstructor để tạo các đối tượng ServerSocket mới, các phương thức để lắng nghe các liênkết trên một cổng xác định, và các phương thức trả về một Socket khi liên kết được thiết lập,
vì vậy ta có thể gửi và nhận dữ liệu
Vòng đời của một server
1 Một ServerSocket mới được tạo ra trên một cổng xác định bằng cách sử dụngmột constructor ServerSocket
2 ServerSocket lắng nghe liên kết đến trên cổng đó bằng cách sử dụng phươngthức accept() Phương thức accept() phong tỏa cho tới khi một client thực hiệnmột liên kết, phương thức accept() trả về một đối tượng Socket mà liên kếtgiữa client và server
3 Tùy thuộc vào kiểu server, hoặc phương thức getInputStream(),getOutputStream() hoặc cả hai được gọi để nhận các luồng vào ra để truyềntin với client
4 server và client tương tác theo một giao thức thỏa thuận sẵn cho tới khi ngắtliên kết
5 Server, client hoặc cả hai ngắt liên kết
6 Server trở về bước hai và đợi liên kết tiếp theo
5.1 Các constructor
public ServerSocket(int port) throws IOException, BindException
Constructor này tạo một socket cho server trên cổng xác định Nếu port bằng 0, hệthống chọn một cổng ngẫu nhiên cho ta Cổng do hệ thống chọn đôi khi được gọi là cổng vôdanh vì ta không biết số hiệu cổng Với các server, các cổng vô danh không hữu ích lắm vìcác client cần phải biết trước cổng nào mà nó nối tới (giống như người gọi điện thoại ngoàiviệc xác định cần gọi cho ai cần phải biết số điện thoại để liên lạc với người đó)
Ví dụ: Để tạo một server socket cho cổng 80
try{
Trang 11ServerSocket httpd = new ServerSocket(80);
try{
ss= new ServerSocket(i);
ss.close();
} catch(IOException e) {
System.out.println("Co mot server tren cong "+i);
} }
5.2 Chấp nhận và ngắt liên kết