日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

【Java】JavaSocket编程开发聊天室-客户端核心部分

發布時間:2023/12/10 java 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Java】JavaSocket编程开发聊天室-客户端核心部分 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

ClientChat.

本篇文章圍繞聊天室的聊天界面ClientChat,敘述其中各種功能的實現。開始我們還是給出ClientChat最終GUI效果的兩個展示,通過展示來直觀認識各種功能。

關于控件.

JSplitPane分割窗格的使用.

  • 上面展示的GUI中實際上有三個分割窗格,比較明顯的是中間這一道,將整個UI分為左右兩邊的分割窗格。我們首先給出代碼段,結合代碼段和上面的UI來敘述。
//Main chat panel. JPanel MainChatPanel = new JPanel(); MainChatPanel.setLayout(new BorderLayout());//Users list panel. JPanel UserPanel = new JPanel(); UserPanel.setLayout(new BorderLayout());//Split MainChatPanel and OnlineUsersListPanel. JSplitPane SplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,MainChatPanel,UserPanel); SplitPane.setDividerLocation(380); SplitPane.setDividerSize(10); SplitPane.setOneTouchExpandable(true); SplitPane.setEnabled(false); this.add(SplitPane,BorderLayout.CENTER);

上述代碼就對應于將整個界面分割為左右兩部分——MainChatPanel以及UserPanel的分割窗格SplitPane。首先是構造函數JSplitPane(),第一個參數是分割策略,代碼中使用的是水平分割,也就是被分割后的兩個部分是水平并列的關系,后兩個參數則是分割的兩個部分。后續的三個set()方法都是顧名思義能夠知道意思的。setDividerLocation()是設置分割窗格的位置;setDividerSize()設置分割窗格的粗細,實際上剩余的兩個分割窗格的size都是1,所以沒有那么明顯,如果都設置為10的話,效果圖如下所示:

如此可以很清楚地看出三個分割窗格的位置了。setOneTouchExpandable()就對應于中間那個分割窗格上的兩個實心黑三角形,顧名思義,它是設置伸縮策略的,某一部分可以被完全展開,效果圖如下所示:


setEnabled則是分割窗格是否可以自由地上下拖動。實際上這些方法都可以通過自己的試驗,來發現它的效果。

圖標型JButton的生成.

  • 平時常見的JButton都是上圖中【Exit】樣式的,不免讓人覺得單一。注意到GUI的中間部分,有四個圖標型的按鈕,放到上面還會給出提示信息,清新簡潔。

//Shaking button. ButtonAddr = "D:\\NewDesktop\\Shaking.png"; JButton ShakingButton = new JButton(new ImageIcon(ButtonAddr)); ShakingButton.setMargin(new Insets(0,0,0,0)); ShakingButton.setToolTipText("Nudge your friend."); ButtonPanel.add(ShakingButton);

圖標型JButton是從ImageIcon類型封裝而來的,實際上很簡單,只需要給出圖標的路徑即可。setMargin()方法用于設置按鈕邊框和標簽之間的空白,我們更改其中的參數為new Insets(10,10,10,10),可以發現確實按鈕邊框周圍多了一圈空白。

setToolTipText()則是用于設置提示文本,也就是我們前面UI中顯示出的"Nudge your friend",最近在微信上也很火。

簡單文本消息發送.

簡單文本消息的發送分為三步:

  • 發送方在自己的編輯區鍵入向發送的消息,并且按下Send按鈕(也可以是另外的觸發方式);
  • 客戶端發送標記為【聊天】的請求,交付給Server處理,理想的Server能夠判斷出這一聊天信息是群聊的還是私發的,并且進行正確的回復;
  • 客戶端收到回復后,判斷自己是否需要顯示出這一消息(會存在私發消息)。
  • 這部分的代碼過于冗長,只選出比較重要的語句進行展示邏輯關系(直接拷貝是無法運行的,代碼是錯的)。

    public void SendMessage() {//Get text message.String ThisMessage = RemainToSendArea.getText();//Interaction between client and server.if(One_OneChat.isSelected()){ThisMessage.setReceiver(ChosenUser);}ThisMessage.setSender(ThisUser);Plea plea = new Plea();plea.setAction("Chat");plea.setData("Message", ThisMessage);try{ClientToServer.SendMessage(plea);}catch(IOException e){e.printStackTrace();}//This client should display message,//regardless of public chat or private chatRemainToSendArea.setText("");ClientToServer.appendText(ThisMessage.getContent());

    私聊設置.

    私聊的設定類似于騰訊會議中的設定,用戶可以選擇另一個用戶發起私聊,而其余的用戶無法看到他們之間的聊天內容。展示如下,其中Hoe與Mega正在進行私聊:


    而此時,第三位用戶的視角是這樣的:

    顯然Hoe與Mega的對話只在他們兩人的界面上顯示了出來。

    if(One_OneChat.isSelected()) //Chat one to one. {if(null==ChosenUser){JOptionPane.showMessageDialog(ClientChat.this, "Please choose a user.","ERROR", JOptionPane.ERROR_MESSAGE);return;}else if(ClientDataStore.thisUser.getID()==ChosenUser.getID()){JOptionPane.showMessageDialog(ClientChat.this, "Hey,you cannot talk oneself.","ERROR", JOptionPane.ERROR_MESSAGE);return;} else {ThisMessage.setReceiver(ChosenUser); }

    這段代碼在私聊選項框One_OneChat被選中的時候,如果此時選擇的用戶是一個合法的私聊對象,那么客戶端會在即將發送出去的請求中指明這一條消息的接收者,這就是Server用于區分群發消息和私聊消息的標識。在Server的代碼中,有如下的一段:

    if(message.getReceiver()!=null) //Private chat. {//Get receiver's id.ServerRecordClient service = ServerDataStore.OnlineInfoMap.get(message.getReceiver().getID());//Only reply to receiver.SendReply(service, reply); } else //Group chat. {//Reply to all the users except sender.for(Long ID:ServerDataStore.OnlineInfoMap.keySet()){if(message.getSender().getID()==ID){//Skip sender.continue;}else{ServerRecordClient service=ServerDataStore.OnlineInfoMap.get(ID);SendReply(service,reply);}} }

    總結來說就是Server認為沒有指明接收方的消息是群發消息,所以它會向除了發送方以外的所有客戶端發送回復,而如果是一條私聊消息,Server就只會向接收方一個客戶端發送回復。接收到回復的客戶端,會根據回復中的標識,來進行相應的動作。

    if(type==ReplyType.CHAT) {//Chat.Message message =(Message) reply.getData("TextMessage");ClientToServer.appendText(message.getContent()); }

    客戶端會從Server發送的回復中得知這一條消息的內容,之后將其顯示在自己的界面上。那么沒有收到回復的客戶端,自然也談不上顯示消息了。至此,我們大體上敘述完了的文本消息的發送以及接收的過程,并且展示了體現設計思路的代碼。

    窗口抖動發送.

    窗口抖動這一功能我們平時也不少用,ClientChat輔助功能按鈕中從左數第三個就是發送窗口抖動的按鈕,除了在聊天框中顯示出"xxx is shaking xxx"這樣的提示消息之外,也借助于Java-Swing組件中的setLocation方法,模擬了實際的抖動效果。下圖中Hoe在22:37:22抖動了Mega,并且Mega也確實接收到了抖動。動態的抖動效果可以自行下載項目的完整代碼查看。


    我們將執行窗口抖動的方法綁定到ShakingButton上,當該按鈕被觸發并且符合發送抖動的條件時,客戶端就會封裝一個標記為【抖動】的請求,發送到Server,和處理文本消息類似的,Server也會向接收方發送一個回復,接收方的客戶端根據回復中的內容,做出正確的反應。下面是說明邏輯的代碼,首先是發送方請求"抖動":

    //Shaking your friend. ShakingButton.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {Shaking();} });private void shaking() {ThisMessage.setReceiver(ChosenUser);//Send plea which is marked with "Shake".Plea plea = new Plea();plea.setAction("Shake");plea.setData("Message", ThisMessage); }

    Server在接收到【抖動】請求后,如果判斷該請求合理(類似抖動自己這樣的就是不合理的),就會向被抖動方,也就是接收方發出回復:

    //Mark reply with "Shake" reply.setType(ReplyType.SHAKE_WINDOW); reply.setData("Shake", message);//Get receiver's id. ServerRecordClient service = ServerDataStore.OnlineInfoMap.get(message.getReceiver().getID());//Send reply. SendReply(service,reply);

    被抖動方接收到了Server發來的回復后,客戶端就要執行相應的動作了:

    //Start shaking. new ShakeFrame(ThisFrame).StartShake();

    關于ShakeFrame效果的實現,實際上就是通過Java提供的定時器Timer來周期性地使用setLocation()方法更改窗口的位置,高頻的更改就會有抖動的效果。

    ShakeTimer = new Timer((int)(SHAKE_CYCLE/5),new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {long pass = System.currentTimeMillis()-StartTime;double ShakeOffset = (pass%SHAKE_CYCLE)/SHAKE_CYCLE;double Angle = ShakeOffset*Math.PI;int Shake_x = (int)(Math.sin(Angle)*SHAKE_DISTANCE+Location.x);int Shake_y = (int)(Math.sin(Angle)*SHAKE_DISTANCE+Location.y);Frame.setLocation(Shake_x,Shake_y);if(pass>=SHAKE_DURATION){StopShake();}} });ShakeTimer.start();

    在線用戶以及【我】信息展示.

    首先需要明確的是,在用戶進行登錄的時候,我們是可以知道這個用戶是誰的。換言之,我們可以記錄下這一用戶的信息,包括但不限于頭像、昵稱以及賬號,而后我們在初始化ClientChat界面時,就可以完成對【我】的信息的展示。代碼如下:

    if(null!=ClientDataStore.thisUser) {ThisUserLabel.setForeground(Color.BLUE);ThisUserLabel.setIcon(new ImageIcon("D:\\NewDesktop\\" + ClientDataStore.thisUser.getProfile() + ".png"));ThisUserLabel.setText(ClientDataStore.thisUser.getNickname()+ "(" + ClientDataStore.thisUser.getID() + ")");ThisUserLabel.setOpaque(true);ThisUserLabel.setBackground(Color.WHITE); }

    至于所有用戶信息的展示,我們說過用戶在登錄時,客戶端是需要與Server發生交互的,我們在Server中專門設置了一個列表來記錄當前的在線用戶,而Server完全可以在給客戶端發送回復時,將它記錄的關于在線用戶的數據包含在回復中,所以客戶端也能夠知道當前在線的用戶情況。
    當有用戶登錄時,Server會向每一個客戶端發送回復,其中就包含了當前在線用戶的數據:

    ServerDataStore.OnlineUserMap.put(user.getID(), user);reply.setData("OnlineUsers", new CopyOnWriteArrayList<ADT_of_User>(ServerDataStore.OnlineUserMap.values()));

    然后客戶端從收到的回復中,提取出有關在線用戶信息的數據,而后根據數據設置界面上的顯示

    ClientDataStore.onlineUsers=(List<ADT_of_User>)reply.getData("OnlineUsers");OnlineUsersCount.setText("Online Users List 【"+ClientDataStore.onlineUsers.getSize()+"】");

    后續的關于表示每一個用戶的可選項的顯示,就用了Java提供的ListCellRenderer接口,顧名思義,是用于呈現每一個小格子的接口。我們在實現該接口的UserCellRenderer類中有這樣一段代碼,用于設置顯示出來的內容:

    ADT_of_User Abstract_User = (ADT_of_User)obj; String Name = Abstract_User.getNickname()+"【"+Abstract_User.getID()+"】"; setText(Name);

    而后在ClientChat中,我們借助于客戶端接收到的在線用戶的數據,完成了在線用戶列表的顯示:

    OnlineUsersList = new JList(ClientDataStore.onlineUsers); OnlineUsersList.setCellRenderer(new UserCellRenderer()); OnlineUsersList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

    文件發送.

    文件的發送是所用功能中最復雜的一個,其基本流程分為以下三步:

  • 發送方發出【即將發送文件】的請求,選擇好即將發送的文件,接收方得知這一請求;
  • 接收方用戶進行選擇,同意接收或者拒絕接收;
  • 若接收方同意接收,則發送方開始實際傳輸該文件;否則直接結束這一流程。
  • 上述流程的描述,是以發送方和接收方為主體的,但實際上它們之間并沒有直接的交流,它們都需要通過Server來同對方進行交互。

    1.Sender發送【ToSendFile】請求.

    當發送方用戶按下SendFile時,ClientChat界面會彈出文件選擇框讓該用戶選擇即將發送的文件,之后向Server發送一個標記為【即將發送文件】的請求,等待回應。文件選擇框的效果如下所示,在文件發送的最后我們介紹其用法:

    能夠說明邏輯的代碼展示如下:

    //Select file. File file = FileChooser.getSelectedFile(); FileToSend = new FileData();//Set receiver and sender. FileToSend.setSender(ClientDataStore.thisUser); FileToSend.setReceiver(ChosenUser);//Sender send a plea. Plea plea = new Plea();//'plea' maeked with 'ToSendFile'. plea.setAction("ToSendFile"); plea.setData("File", FileToSend);

    2.Server收到【ToSendFile】請求.

    在上一步的代碼中,請求中已經指明了待發送文件的接收者,所以Server可以精準地向該用戶發送回復,并且在回復中標記上【即將發送文件】,從而使得接收方的客戶端能夠進行后續的處理。

    //Mark 'reply' with 'GOING_TO_SEND_FILE'. reply.setType(ReplyType.GOING_TO_SENT_FILE); FileData file = (FileData)plea.getData("File"); reply.setData("SendFile", file);//Get reveiver id. ServerRecordClient service = get(file.getReceiver().getID());//Send reply. SendReply(service,reply);

    3.Receiver收到【GOING_TO_SEND_FILE】回復.

    當Receiver客戶端收到Server發來的回復后,會彈出一個詢問框請求用戶的指示,如果用戶選擇了【同意接收】,那么接收方客戶端會從Server發來的回復中獲取待發送文件的信息,在本地選擇一個保存文件的位置,并且向Server發送一個【AgreeReceiveFile】的請求;如果用戶選擇【拒絕接收】,那么接收方客戶端直接向Server發送一個【RefuseReceiveFile】的請求。

    //Inquiry the user. int Choice = JOptionPane.showConfirmDialog(ThisFrame,SenderInfo+" want to send a file【"+FileName+"】 to you.\nWill you agree?","Accept file.",JOptionPane.YES_NO_OPTION);if(Choice==JOptionPane.YES_OPTION) //Agree to receive file. {//Choose position to save file. FileChooser.showSaveDialog(ThisFrame);//Send a plea marked with 'AgreeReceiveFile'.plea.setAction("AgreeReceiveFile"); } else {//Send a plea marked with 'RefuseReceiveFile'.plea.setAction("RefuseReceiveFile"); }

    4.Server收到【Agree/RefuseReceiveFile】請求.

    Server收到來自Receiver的【同意接收】或者【拒絕接收】請求后,都需要向Sender發送一個回復,用于讓Sender客戶端進行后續的處理。當Receiver發送【同意接收】請求后,Server一方面向Sender發送一個【接收方同意接收】的回復,指示Sender進行文件的實際傳輸;另一方面給Receiver發送一個【做好接收準備】的回復,Sender即將實際傳輸文件過來。

    //Reply to sender.[AGREE_RECEIVE_FILE]. reply.setType(ReplyType.AGREE_RECEIVE_FILE); ServerRecordClient SenderRecord = get(file.getSender().getID()); SendReply(SenderRecord,reply);//Reply to receiver.[RECEIVE_FILE]. anotherReply.setType(ReplyType.RECEIVE_FILE); ServerRecordClient ReceiverRecord = get(file.getReceiver().getID()); SendReply(ReceiverRecord,anotherReply);

    5.Sender收到【AGREE_RECEIVE_FILE】回復.

    當Sender收到表明接收方已經同意接收文件的【AGREE_RECEIVE_FILE】回復后,就通過Sender和Server之間建立的Socket連接中的輸出流進行實際的文件傳輸。

    socket = new Socket(SendFile.getDestIP(),SendFile.getDestPort()); Buffer_OS = new BufferedOutputStream(socket.getOutputStream()); Buffer_OS.write(File);

    6.Receiver收到【RECEIVE_FILE】回復.

    當Receiver收到表明自己應該做好接受準備的【RECEIVE_FILE】回復后,就通過自己和Server之間的Socket連接,將實際的文件寫到在第3步中已經選定好的保存位置。

    Ssocket = new ServerSocket(SendFile.getDestPort()); socket = Ssocket.accept(); Buffer_OS = new BufferedOutputStream(new FileOutputStream(SendFile.getDestFilename())); Buffer_OS.write(File);

    ★至此整個文件發送、接收流程已經結束,在下面展示出整個流程的UI界面。
    首先是發送方選定要發送的文件:

    接收方收到詢問:

    接收方同意接收,選定保存位置:

    整個傳輸過程完成:

    關于JFileChooser.

    這是一個JavaSwing中很強大的控件,提供的就是文件選擇功能,并且語法簡單易于使用。

    //Create a FileChooser JFileChooser FileChooser = new JFileChooser();//Select a file. if(FileChooser.showOpenDialog(ClientChat.this)==JFileChooser.APPROVE_OPTION) {File file = FileChooser.getSelectedFile();FileToSend = new FileData();FileToSend.setSender(ThisUser);FileToSend.setReceiver(ChosenUser); }

    總結

    以上是生活随笔為你收集整理的【Java】JavaSocket编程开发聊天室-客户端核心部分的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。