Java Socket Chat

Đây là dự án nhỏ của em về chat giữa 3 client với nhau ( trong đó 2 client chung 1 user nhưng user đó dùng 2 thiết bị nên là 2 client , client còn lại của user khác )

vì là dự án test hệ thống nên mọi thao tác đều trên console và khi yêu cầu load lịch sử sẽ ấn “1” nhập vào console và hệ thống load lại 5 tin lịch sử gửi về cho user ( để minh họa load lịch sử )

thì các vấn đề em gặp phải hiện tại là

  1. khi 2 user cùng gửi tin nhắn lên server cùng lúc thì ID khởi tạo sẽ bị trùng trong file , ở phương án này em tách luồng gửi tin nhắn đi của user thành 2 luồng khác

luồng 1 gửi tin luôn lên server

luồng 2 tạo ID và lưu ID+tin nhắn vào file ( luồng này dùng single thread excutor để đảm bảo từng tin nhắn được tạo riêng 1 ID )

cách này mà gộp 1 luồng vừa gửi vừa tạo ID thì nếu 2 user spam tin nhắn thì sẽ có độ trễ vì bị khựng lại 1 nhịp để khởi tạo ID cho từng tin một

  1. Lỗi trùng lặp tin khi load lịch sử mà có tin nhắn mới gửi tới gây lỗi , ví dụ A đang load tin nhắn lịch sử 1 2 3 4 5 , thì B gửi tin thứ 6 tới , chen vào 1 trong các tin kia sẽ gây ra lỗi thành 1 2 6 3 4 5 mà lẽ ra phải là 1 2 3 4 5 6

xử lý bằng cách mỗi lần nhận tín hiệu lịch sử sẽ gửi tổng size sẽ load , như chương trình config là 5 tin một lần load

mỗi tin lịch sử sẽ có dòng “old message” đi kèm , quay về input của user , đếm đủ 5 old message thì in hết ra từ map , trong quá trình while (true) chạy map.put từng tin vào ( trong quá trình while chạy mà có tin từ user nào tới cũng vào map hết và sort theo ID nên ko lo bị loạn tin )

tuy nhiên anh mentor bảo cách của em vẫn có nhiều lỗi , xem lại 2 đoạn trên thuộc input/output phía client , em nhờ mọi người chẩn đoán xem những lỗi tiềm ẩn có thể nằm ở đâu ạ , cảm ơn mọi người

link git dẫn thẳng tới luồng outputDataToServer của user ạ

dự án đi theo mô hình MVC

sao mentor của bạn không nói luôn là lỗi tiềm ẩn gì mà bạn phải đi hỏi? sao giống nói vu vơ thế

As far as I can remember, you asked this question months ago… and I told you that your package consists of numerous small APIs and nobody has time to read and examine the interrelations of these APIs to find the problems. And that is the case.

  1. Lỗi trùng lặp tin khi load lịch sử mà có tin nhắn mới gửi tới gây lỗi , ví dụ A đang load tin nhắn lịch sử 1 2 3 4 5 , thì B gửi tin thứ 6 tới , chen vào 1 trong các tin kia sẽ gây ra lỗi thành 1 2 6 3 4 5 mà lẽ ra phải là 1 2 3 4 5 6

This indicates the lack of synchronization. You are working with threads, so you must know how to synchronize the process. I am surprised that you are learning Java, designing a threading package with numerous APIs, but do not know how to synchronize the process where the problems occur.
There are two ways to solve your problems:

  1. Use synchronized(this) {…} where the IO occurs
  2. Use Collections.synchronizedList and/or ConcurrentHashMap if you have to work with the list or map. That’s all I can help you with.

As I said, too many small APIs and I don’t want to spend my time reading such small APIs to find where the sync is missing.
It seems to me that your mentor is not good enough to recognize this and point it out to you.

1 Like

I got a solution to the problem you cited

Handled by each time receiving a historical signal, it will send the total size to load, as the program configures 5 messages per load.

Each historical message will have an “old message” line attached, returned to the user’s input, counted 5 old messages, then printed them all from the map, while (true) running map.put each message (during the process). While running, any message from any user will be entered into the map and sorted by ID so you don’t have to worry about message chaos.)

but the mentor said he could find a bunch of bugs

here my class to handle this promble

You seem to be unaware of the concurrency that requires a lot of synchronization. If you want to do the synchronization yourself, you’ll need to develop your own locking mechanism, which requires a high level of understanding of concurrency. Or you can use the available Java functions:

sorts by ID, so you don’t have to worry about message chaos.

For this alone, you risk a synchronization problem that is more complex than using a TreeMap. Let’s start with the TreeMap in conjunction with synchronized(this):

synchronized(this) {
    while ((messageFromServer = inFromServer.readLine()) != null) {
        ....
        map.put(currentMessageId, messageFromServer);
        ....
   }
}

sorts by ID, so you don’t have to worry about message chaos.

I don’t understand what you’re trying to say about message chaos here. A map is accessed by a key (or ID) and if you want to sort at all costs you can still do:

  • by keys (IDs): List keys = new ArrayList(map.keySet()) with Collections.sort();

then you can use the ConcurrentHashMap (because it’s “lightning fast”) and do the sorting afterwards

   ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<>();
       ...
       synchronized(this) {
         ...
       }
       ...
    }
    //
    public synchronized void printMessage(Map<Integer, String> map) {
        List<Integer> keys = new ArrayList(map.keySet());
        Collections.sort(keys);
        for (Integer id : keys) {
            System.out.println(map.get(id));
        }
        map.clear();
    }

It seems you are unclear about the IO flow between my program’s client/server

The reason I don’t use synchronized is because when a client connects to the server, it will have 4 separate IO threads.
in there :
2 IO threads on the client side
2 IO threads on the server side

and similarly, 2 clients connecting to the server will have 8 separate IOs, …

more specific

When user A connects to the server, there will be 2 IO streams initiated on the client side (here A) + IO on the server side.

A sends historical load signal -> signal to server -> receiving server -> server sends each in turn
old message back to A

At the same time

B also sends a message to A -> the message goes to the server -> the server sends it back to A immediately

This means that adding sync won’t do anything, because there aren’t many threads accessing it at the same time, but simply A’s input just keeps receiving messages (including historical messages)

No. I understand that. Your problem is overuse of threads, and that leads to unpredictable problems (or bugs as your mentor said). It doesn’t matter how many threads you use, but it does matter if they (the threads) are not synchronized. Why doesn’t your server track the IOs in Collections.synchronizedLists so that the right IOs can be picked out, to which client they should be forwarded? And as I said, you get confused and lost in the maze of numerous small modules (APIs) that should be replaced by simple methods. I give you a socket chat package that I developed in 2014 for my students (click HERE to download the iForum.zip). The package has only 3 modules: Authenticate.java, iChat.java and iForum.java.
Just compile them and run on a CMD window and create 3 chatters as following:

c:\>javaw iForum 9999
c:\>javaw iChat localhost:9999  <=== register with any id and any password
c:\>javaw iChat localhost:9999  <=== register with any id and any password
c:\>javaw iChat localhost:9999  <=== register with any id and any password


Sorry: The provided link for iForum.zip was corrupted. It has been corrected.

Theo thiển ý của tui là áp dụng Queue, và đừng băn khoăn cái việc concurrent hay asyn, sync xiếc gì hết. Ở đây là cái message nào tới trước xử lý trước, tới sau xử lý sau.

Và như vậy sẽ không có cái bug tưởng tượng nào kiểu “2 user gửi message cùng lúc”. Tui đố ông nào làm được cái này cùng lúc, đừng nói khả năng đánh máy “nhanh hơn điện”. Tui dám chắc sẽ lệch nhau 1 mili giây trở lên, với khoảng thời gian 1ms đó đối với máy tính đã là không cùng lúc.

Và cái việc trả về history thì cũng chẳng phải là vấn đề gì ghê gớm, chỉ cần căn cứ vào ID hoặc thời gian (trong queue ngược) và móc về mà thôi, dù người chat kia có đang gõ thêm thì cũng phải đợi đó, chừng nào đến mới xử lý, còn chưa đến thì bỏ qua. Bên client cũng không phải lần nào cũng load history, chỉ là lần đăng nhập đầu tiên vào khởi tạo chat mà thôi, còn thì history nằm ở client trong queue và cứ đẩy lùi lên, cái nào rớt ra ngoài giới hạn 5 thì kệ nó, cuộn lên mà thôi.

2024-12-06-07-54-10

Theo mình biết thì socket là sử dụng stateful không phải là stateless nên cũng không có chuyện xảy ra kiểu “râu ông nọ cắm cằm bà kia” mà người viết code lo ngại. Anh ta chỉ lo ngại khi anh ta hoàn toàn không biết đến stateful là gì và tận dụng nó như thế nào.

Chắc chắn client gửi lên thì bên server sẽ tóm được message, và cứ đẩy vào hàng đợi queue mà xử lý từng em một cứ như các bé đi nhận quà hoặc người ta vào cây xăng đổ xăng mà thôi. Vấn đề ở đây là do ở VN người ta không xếp hàng mà nhảy lộn xộn vào => mấy ông lập trình viên đi đổ xăng bị thằng khác chen ngang => hoang tưởng rằng máy tính cũng xử lý giống mấy gã bơm xăng ở cây xăng? Đối với máy tính: tui xử lý đồng thời nhưng ông lập trình viên chỉ đơn nhiệm thì ông làm ơn viết code đơn nhiệm đi cho rành rẽ trước theo học một khóa Introduction to Parallel Computing do Intel tổ chức.

3 Likes

Java Message System (JMS) and Java Message Queue (JMQ) are too much for this small chat app. A real overkill, I would say. By the way: JMS/JMQ requires a high level of knowledge and not like in this app here.

83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?