Nhóm 9 – Chat Server With Multi Thread – B Ộ GIÁO DỤC VÀ ĐÀO TẠO TRƯỜNG ĐẠI HỌC XÂY DỰNG HÀ NỘI KHOA – StuDocu

B Ộ GIÁO DỤC VÀ ĐÀO TẠO

TRƯỜNG ĐẠI HỌC XÂY DỰNG HÀ NỘI

KHOA CÔNG NGHỆ THÔNG TIN

Bài Tập Lớn

Xây dựng ứng dụng Chat Client – Server

Giảng Viên Hướng Dẫn : Hoàng Nam Thắng

Nhóm: Nguyễn Xuân Thưởng 1121050299

Vũ Hoàng Tuấn KIệt 1121050218
Nguyễn Trung Kiên 1121050228

Lớp: Khoa học máy tính – K

Hà Nội, Ngày 19 Tháng 9 Năm 2022

Mục Lục

LỜI NÓI ĐẦU

Chương 1. GIỚI THIỆU
1. MỤC TIÊU CỦA ĐỀ TÀI.
1. GIẢI QUYẾT ĐỀ TÀI.
Chương 2 : CƠ SỞ LÝ THUYẾT
2. CÁC GIAO THỨC MẠNG
2.1. Giao thức IP
2.1. Giao thức UDP
2.1. Giao thức TCP
2. MÔ HÌNH CLIENT-SERVER
2. LẬP TRÌNH SOCKET
Chương 3: MÔ TẢ ỨNG DỤNG
3. GIỚI THIỆU ỨNG DỤNG
3. KIẾN TRÚC ỨNG DỤNG
3.2. Mô hình tổng thể
3.2. Gói server
3.2. Gói client
3. MÔ HÌNH THÔNG ĐIỆP GIỮA CLIENT VÀ SERVER
Chương 4. CÀI ĐẶT VÀ KẾT QUẢ THỬ NGHIỆM
4. KẾT QUẢ THỬ NGHIỆM
4. CÁC GIAO DIỆN CHÍNH CỦA CHƯƠNG TRÌNH
4. KẾT QUẢ THỬ NGHIỆM TRÊN MẠNG LAN
Chương 5. KẾT LUẬN VÀ HƯỚNG PHÁT TRIỂN
5. KẾT LUẬN VÀ KẾT QUẢ ĐẠT ĐƯỢC
5.1. Kết quả đạt được
5. ƯU KHUYẾT ĐIỂM
5.2. Ưu điểm
5.2. Khuyết điểm
5. HƯỚNG PHÁT TRIỂN

2 Mô hình Client – Server
Phương thức truyền tin trong Java:
Nó sử dụng phương thức truyền tin có kết nối thông qua 2 đối tượng là:
Socket(Client) và ServerSocket(Server). Tạo 1 Socket sử dụng để kết nối tới 1
SocketServer.
Client Socket được tạo ra thông qua 1 hàm khởi tạo(Contructor) của lớp Socket:
Socket client=new Socket(Destination Address,Port)

Trong đó:

  • Destination Address là địa chỉ của máy muốn kết nối tới.
    • Port là số hiệu cổng TCP đòi hỏi phải có một ServerSocket đang lắng nghe yêu
      cầu trên đó.
      Để tạo ra một ServerSocket sử dụng câu lệnh sau:
      ServerSocket SSocket=new ServerSocket(Port,Number of Connection)
      Trong đó:
  • Port là số hiệu cổng sẽ chờ để phục vụ
    • Number of Connection là số kết nối chấp nhận phục vụ cùng một lúc.

Một SocketServer sẽ lắng nghe trên một cổng. Khi nhận được một yêu cầu từ socket
(Client Socket) nó sẽ kiểm tra xem còn chấp nhận được kết nối đó không, chưa vượt
quá số kết nối mà nó có thể phục vụ, nếu được nó sẽ tạo ra một Socket để tạo liên kết
với Client yêu cầu bằng phương thức:

Socket client=SSocket();

Các Socket cung cấp 1 giao diện để đọc ghi dữ liệu thông qua 1 luồng kết nối đã
được thiết lập 2 máy tính có thể trao đổi dữ liệu thông qua các đối tượng:

BufferedReader in=new BufferedReader(new
InputStreamReader(client()));

PrintWriter out=PrintWriter(client(),true);

Khi thực hiện xong cần hủy bỏ các liên kết để trả lại tài nguyên cho hệ thống, chúng
ta sử dụng phương thức:

In();

Out();

Client();

gửi các gói tin cho một máy khác mà trước đó nó chưa từng liên lạc với. Giao thức IP
cung cấp một dịch vụ gửi dữ liệu không đảm bảo (còn gọi là cố gắng cao nhất ), nghĩa là
nó hầu như không đảm bảo gì về gói dữ liệu. Gói dữ liệu có thể đến nơi mà không còn
nguyên vẹn, nó có thể đến không theo thứ tự (so với các gói khác được gửi giữa hai máy
nguồnvà đích đó), nó có thể bị trùng lặp hoặc bị mất hoàn toàn. Nếu một phần mềm ứng
dụng cần được bảo đảm, nó có thể được cung cấp từ nơi khác, thường từ các giao thức
giao vận nằm phía trên IP. Các thiết bị định tuyến liên mạng chuyển tiếp các gói tin IP
qua các mạng tầng liên kết dữ liệu được kết nối với nhau. Việc không có đảm bảo về gửi
dữ liệu có nghĩa rằng các chuyển mạch gói có thiết kế đơn giản hơn. (Lưu ý rằng nếu
mạng bỏ gói tin, làm đổi thứ tự hoặc làm hỏng nhiều gói tin, người dùng sẽ thấy hoạt
động mạng trở nên kém đi. Hầu hết các thành phần của mạng đều cố gắng tránh để xảy ra
tình trạng đó. Đó là lý do giao thức này còn được gọi là cố gắng cao nhất. Tuy nhiên, khi
lỗi xảy ra không thường xuyên sẽ không có hiệu quả đủ xấu đến mức người dung nhận
thấy được). Giao thức IP rất thông dụng trong mạng Internetcông cộng ngày nay. Giao
thức tầng mạng thong dụng nhất ngày nay là IPv4; phiên bản từ 0 đến 3 hoặc bị hạn chế,
hoặc không được sử dụng. Phiên bản 5 được dùng làm giao thức dòng (stream) thử
nghiệm. Còn có các phiên bản khác, nhưng chúng thường dành là các giao thức thử
nghiệm và không được sử dụng rộng rãi.

Kề từ khi chính thức được đưa vào sử dụng và được định nghĩa trong kiến nghị
RFC791 năm 1981 đến nay, cho tới bây giờ phiên bản này vẫn đang được sử dụng rộng
rãi và cũng đã góp phần tạo ra sự phát triển bùng nổ của các mạng máy tính.

2.1. Giao thức TCP

Là giao thức hướng kết nối, nó cung cấp một hoạt động truyền tin tin cậy. TCP
chịu trách nhiệm phân chia dữ liệu gửi thành các segment tại máy gửi và lắp gép các
segment lại tại máy đích, trong quá trình truyền có thể truyền lại bất cứ segment nào nếu
máy đích chưa nhận được. Gói tin TCP có dạng sau:

Source port: Số hiệu của cổng gọi (16 bits).
Destination Port : Số hiệu của cổng đích (16 bits).
Sequence Number: Chữa số đảm bảo tuần tự chính xác của dữ liệu đến, giống như
số thứ tự (32 bits).

Acknowledgment Number (ACK): dùng trong các gói dữ liệu hồi đáp của máy
nhận cho máy gửi, báo hiệu để máy gửi biết lượng dữ liệu mà máy nhận đã nhận được và
yêu cầu gửi dữ liệu tiếp theo (32 bits).

Header Length: Số lượng các từ 32 bit trong header (32 bits).
Reserved : Set thành zero (6 bits).
Code Bits: Các chức năng điều khiển như là thiết lập và kết thúc một phiên, nó
giống như cờ gồm 6 bits.1ờ URG.2ờ ACK dùng để xác nhận.3ờ PSH (push) yêu
cầu xóa vùng đệm.4ờ RST (Reset) tái thiết lập.5ờ SYN (Synchronic) đồng bộ.6ờ
FIN (finish) Kết thúc, sử dụng khi muốn hủy kết nối.

Mô hình truyền tin này thực hiện truyền hai thông điệp qua lại giữa client và
server một cách đồng bộ hóa. Chương trình server nhận được thông điệp từ client thì nó
phát ra yêu cầu client chuyển sang trạng thái chờ (tạm dừng) cho tới khi client nhận được
thông điệp đáp ứng do server gửi về. 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).
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 truy xuất tới tài nguyên phần cứng và phần mềm dùng chung. Bản thân các trình
quản lý tài nguyê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ình khá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ình phá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ài nguyê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 được yê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ên
trong 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ường

phầ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ình
client/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ến trì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:

Truyền một yêu cầu từ tiến trình client tới tiến trình server
Yêu cầu được server xử lý
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êu cầ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ạm dừ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)

Quá trình giao tiếp client và server có thể diễn ra theo một trong hai chế độ: bị
phong tỏ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/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ệnh
nhậ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 *** 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ến trì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)

Sơ đồ tương tác giữa Server – Client theo giao thức TCP

Trong giai đoạn truyền nhận dữ liệu, việc trao đổi dữ liệu giữa Client và Server
phải tuân thủ theo giao thức của ứng dụng.

Ghi chú:
Nếu chúng ta phát triển ứng dụng theo các giao thức đã định nghĩa sẵn, chúng ta
phải tham khảo và tuân thủ đúng những qui định của giao thức (tham khảo trong các tài
liệu RFC (Request For Comments)).

Nếu xây dựng ứng dụng dạng Peer -to-Peer, thì một ứng dụng phải có cả chức
năng client và server trong mô hình trên.
Mô hình truyền tin socket
Khi lập trình, ta cần quan tâm đến chế độ bị phong tỏa, vì nó có thể dẫn đến tình
huố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 và 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ầng
giao 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ì giao thức TCP phải có trách nhiệm truyền lại,..ược lại, giao thức UDP có tốc độ
truyền tin rất nhanh 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óng liê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 sẽ sử dụng lớp DatagramSocket và DatagramPacket.

Truyền tin hướng liên kết nghĩa là cần có giai đoạn thiết lập liên kết và giải phóng
liên kết trước khi truyền tin. Dữ liệu được truyền trên mạng Internet dưới dạng các gói
(packet) có kích thước hữu hạn được gọi là datagram. Mỗi datagram chứa một header và
một payload. Header chứa địa chỉ và cổng cần truyền gói tin đến, cũng như địa chỉ và
cổng xuất phát của gói tin, và các thông tin khác được sử dụng để đảm bảo độ tin cậy
truyền tin, payload chứa dữ liệu. Tuy nhiên do các datagram có chiều dài hữu hạn nên
thường phải phân chia dữ liệu thành nhiều gói và khôi phục lại dữ liệu ban đầu từ các gói
ở nơi nhận. Trong quá trình truyền tin có thể có thể có một hay nhiều gói bị mất hay bị
hỏng và cần phải truyền lại các gói tin đến không theo đúng trình tự. Để tránh những điều
này, việc phân chia dữ liệu thành các gói, tạo các header, phân tích header của các gói

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.

Khi việc truyền dữ liệu hoàn thành, một và cả hai phía ngắt liên kết. Một số giao
thứ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ột liên
kết đơn.

Socket cho Client

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 ở xa.

Trong hàm này tham số host là hostname kiểu String, nếu host không xác định và
máy chủ tên miền không hoạt động thì constructor đưa ra ngoại lệ
UnknownHostException. Vì một lý do nào đó mà không thể mở được socket thì
constructor sẽ đưa ra ngoại lệ IOException. Có nhiều nguyên nhân khiến cho một liên kết
thất bại: host mà ta đang cố gắng kết nối tới không chấp nhận liên kết, kết nối Internet có
thể bị ngắt, vấn đề định tuyến 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

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ổng port,
sau đó nó thực hiện kết nối tới host. Nó đưa ra ngoại lệ IOException nhưng không đưa ra

ngoại lệ UnknownHostException. Constructor đưa ra ngoại lệ trong trườ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ối
tớ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ởi hai
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ễn
bởi một đối tượng InetAddress.

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úng thô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, 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()

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ình mạ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, khi
chương trình kết thú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ác chương
trình chạy trong khoảng thời gian vô hạn. Để đóng một socket ta có thể dùng phươ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 tin
InetAddress, địa chỉ cục bộ, và số hiệu cổng cục bộ thông qua các phưong thức
getInetAddress(), getPort(), getLocalHost(), và getLocalPort(). Tuy nhiên khi ta gọi các
phương thức getInputStream() getOutputStream() để đọc dữ liệu từ luồng đọc
InputStream ghi dữ liệu OuputStream thì ngoại lệ IOException được đưa ra.

Các socket đóng một nửa (Half-closed socket)

Phươ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 là luồng nhập là luồng xuất. Bắt đầu từ
Java 1, các phương thưc shutdownInput() và shutdownOutput() cho phép ta thực hiệ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ồng kết nối tới nó sao cho.

Trong Java đư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()

Thiết lập các tùy chọn cho Socket

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
    nhanh nhấ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 tin khá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.

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ếu
    SO_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.

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ới khi nhận đủ số byte. Bằng cách thiết lập phương thức SO_TIMEOUT, ta sẽ đảm bảo