您好,登錄后才能下訂單哦!
這篇文章主要介紹了java如何實現基于Tcp的socket聊天程序,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
對于步入編程行業不深的初學者或是已經有所領會的人來說,當學習一項新的技術的時候,非常渴望有一個附上注釋完整的Demo。本人深有體會,網上的例子多到是很多,但是很雜不完整,寫代碼這種東西來不得半點馬虎,要是錯了一點,那也是運行不了的。這對于初學者來說更加的頭疼,因為他根本不知道錯在哪里,盲目的改只能錯上加錯。最后不得不去找找看看有沒有能夠直接運行的例子再加以模仿。
下面是博主在學習Java的socket時寫的一個完整的例子,并且帶上了完整的注釋。它是一個簡單的聊天程序,但是它可以設置任意多用戶同時登錄,然后相互兩兩交流。博主僅僅在自己電腦上實現同時登錄,然后兩兩相互交流。
程序的大體思路是這樣的:
①該用戶作為服務端也就是被請求連接端和主動請求連接其他端時不一樣的,其次有可能被其他的用戶連接很多次,那么你作為服務端,就會有很多連接,同樣的道理,你作為客戶端也會有很多的連接。為了程序更加通俗易懂,博主寫的時候,設置了很多容器,將不一樣的東西分開放置。做到解耦合,不然到后面自己都分不清了。
②你可以一次寫兩個類,client1,client2,,client1先作為服務端,client2作為客戶端,客戶端去連接服務端,從而實現client1的服務端功能和client2的客戶端功能。每次實現一個功能就先將服務端和客戶端的功能整合一下,互換角色,看是否存在錯誤。
③在實現了兩個用戶的情況下再去寫第三個類client3,代碼就是復制粘貼。當然你也可以直接創建一個client3的類,然后直接在類的main方法中改了端口號,和用戶名就行。第三個實現后,第四個第五個也就實了。
下面是具體的代碼:
package jack; import java.awt.List; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.time.format.TextStyle; import java.util.ArrayList; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.JToolBar; import javax.swing.plaf.basic.BasicTabbedPaneUI.TabbedPaneLayout; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter.DEFAULT; import org.omg.CORBA.PRIVATE_MEMBER; /** * 被請求連接時稱為服務器;主動連接對方時稱為客戶端。 * @author Administrator * */ public class Client4 extends JFrame implements ActionListener{ private static int sign = 0; //用來記錄選項卡的標簽,0標示第一個。 private JToolBar toolBar1,toolBar2; private JLabel waitPortLabel,hostLabel,portLabel; private JTextField waitPortTF,hostTF,portTF,sendTF; private JTabbedPane tab; private JButton sendB,leaveB,deleteB,connB; private ArrayList<ChatThread> serverThreads; //存放服務器線程的容器。即存放被請求連接時所創建的線程的容器。 private ArrayList<ChatThread> clientThreads; //存放客戶端線程的容器。即存放主動連接對方時所創建的線程的容器。 private ArrayList<ServerSocket> servers; //服務對象的容器。 private ArrayList<MyJTextArea> serverTextAreas; //作為服務器時,存放所創建的對話記錄顯示區域的容器。 private ArrayList<MyJTextArea> clientTextAreas; //作為客戶端時,岑芳所創建的對話記錄顯示區域的容器。 private ArrayList<Socket> serverSockets; //存放被請求連接時所創建的socket的容器。 private ArrayList<Socket> clientSockets; //存放主動請求連接時所創立的socket的容器。 private ArrayList<PrintWriter> serverPWriters; //存放服務端輸出流對象的容器。 private ArrayList<BufferedReader> serverBReaders; //存放服務端輸入流對象的容器。 private ArrayList<PrintWriter> clientPWriters; //存放客戶端輸出流對象的容器。 private ArrayList<BufferedReader> clientBReaders; //存放客戶端輸入流對象的容器。 private ArrayList<Integer> ports; //存放作為服務器時已連接的端口。 private int port = 2041; //指定自己開放的第一個端口號,方便其他人連接。 private String name; //儲存自己的名稱。 public Client4(String name) throws IOException{ super(name); this.name = name; toolBar1 = new JToolBar(); toolBar2 = new JToolBar(); waitPortLabel = new JLabel("等待端口"); hostLabel = new JLabel("主機"); portLabel = new JLabel("端口"); waitPortTF = new JTextField("2041"); hostTF = new JTextField("127.0.0.1"); portTF = new JTextField(5); sendTF = new JTextField(); tab = new JTabbedPane(); sendB = new JButton("發送"); leaveB = new JButton("離線"); deleteB = new JButton("刪除頁"); connB = new JButton("連接端口"); servers = new ArrayList<ServerSocket>(); serverTextAreas = new ArrayList<MyJTextArea>(); clientTextAreas = new ArrayList<MyJTextArea>(); serverSockets = new ArrayList<Socket>(); clientSockets = new ArrayList<Socket>(); serverPWriters = new ArrayList<PrintWriter>(); serverBReaders = new ArrayList<BufferedReader>(); clientPWriters = new ArrayList<PrintWriter>(); clientBReaders = new ArrayList<BufferedReader>(); serverThreads = new ArrayList<ChatThread>(); clientThreads = new ArrayList<ChatThread>(); ports = new ArrayList<Integer>(); toolBar1.add(waitPortLabel); toolBar1.add(waitPortTF); toolBar1.add(hostLabel); toolBar1.add(hostTF); toolBar1.add(portLabel); toolBar1.add(portTF); toolBar1.add(connB); toolBar2.add(sendTF); toolBar2.add(sendB); toolBar2.add(leaveB); toolBar2.add(deleteB); waitPortTF.setEnabled(false); //設置等待的textfield不可以編輯。 hostTF.setEnabled(false); //設置連接的ip地址不可編輯,當然這里可以更改成其他電腦的ip地址。 this.getContentPane().add(toolBar1, "North"); //添加工具欄到最上方。 this.getContentPane().add(tab,"Center"); //添加選項卡窗格。 this.getContentPane().add(toolBar2,"South"); //添加工具欄到下方。 this.setBounds(200, 200, 350, 300); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); sendB.addActionListener(this); leaveB.addActionListener(this); deleteB.addActionListener(this); connB.addActionListener(this); //主線程進入之后 在server.accept()阻塞等待客戶端來連接。 while(true){ ServerSocket server = new ServerSocket(port); //作為服務器,開發自己供連接的端口。 servers.add(server); Socket serverSocket = server.accept(); //等待對方連接。 serverSockets.add(serverSocket); ports.add(port); //將已連接的端口加入容器。 PrintWriter serverPWriter = new PrintWriter(serverSocket.getOutputStream(),true);//初始化輸出流對象。 serverPWriters.add(serverPWriter); BufferedReader serverBReader = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()));//初始化輸入流對象。 serverBReaders.add(serverBReader); serverPWriter.println("連接"+name+"成功"); //將連接成功的消息發送給請求方,提醒對方連接成功。 serverPWriter.println(name); //將自己的名稱發送給對方,方便對方設置選項卡的名稱。 String content = serverBReader.readLine(); //此時對方也發送了上面兩條消息過來。讀入對方發送過來的提醒消息。 String name2 = serverBReader.readLine(); //讀取對方的名稱。方便后面設置選項卡的名稱。 System.out.println(content); System.out.println(name2); MyJTextArea serverTextArea = new MyJTextArea(sign); sign++; //new了一個textArea后,sign自動增加1,好和選項卡對應, //知道這個選項卡加到哪個容器了,是服務器的還是客戶端的。 serverTextAreas.add(serverTextArea); this.tab.addTab(name2,new JScrollPane(serverTextArea));//在選項卡窗格中添加一個選項卡。 serverTextArea.setEditable(false); //設置對話記錄顯示區域不可編輯。 serverTextArea.setLineWrap(true); //設置對話記錄顯示區域自動換行。 serverTextArea.append(content+"\n"); //在對話記錄區域輸出連接成功這條消息。 ChatThread thread = new ChatThread(); //new了一個線程去執行run方法,用于和對方交流,對方也會開啟一個線程來和你交流。 //這里要開啟新線程的原因是main線程經過一輪后會在上面accept方法處阻塞。 serverThreads.add(thread); thread.start(); //啟動該線程,方便接收對方發來的消息。 port++; //端口號加一,開放下一個供連接的端口。 waitPortTF.setText(port+""); //更新顯示等待的下個端口。 } } private class ChatThread extends Thread { private String[] serverContents = new String[10]; //當作為服務端時,用來存放相互發送消息時的一句話。 private String[] clientContents = new String[10]; //當作為客戶端時,用來存放相互發送消息時的一句話。 private boolean isServerThread = true; //判斷當前在執行run方法的線程是不是服務端線程。 @Override public void run() { while(true){ if(serverThreads.size()>0){ //判斷當前的線程是否是服務線程。先判斷是否大于0,是為了防止serverThreads for(int i=0;i<serverThreads.size();i++){ //報數組越界。 if(Thread.currentThread() == serverThreads.get(i)){ //拿當前線程和服務端容器里的線程去比,看是否是服務端的線程。 isServerThread = true; } } } if(clientThreads.size()>0){ //判斷當前的線程是否是客戶線程。 先判斷是否大于0,是為了防止clientThreads for(int i=0;i<clientThreads.size();i++){ //報數組越界。 if(Thread.currentThread() == clientThreads.get(i)){ //拿當前線程和客戶端容器里的線程去比,看是否是客戶端的線程。 isServerThread = false; } } } if(isServerThread){ //如果是服務端的線程,將readline方法接受到的值賦給相應的content。 for(int i=0;i<serverThreads.size();i++){ if(Thread.currentThread() == serverThreads.get(i)){ //判斷具體是服務端里的那條線程。 try { serverContents[i] = serverBReaders.get(i).readLine();//將對方發送過來的消息賦值給這條線程的接受消息字符串。 } catch (IOException e) { e.printStackTrace(); return; //出現異常時直接退出方法。 } if(serverContents[i]==null){ //在自己點擊離線按鈕時,serverContents[i]為null, return; //因此在這里進行處理,避免后面報錯。 } if(serverContents[i].equals("關閉")){ //接收到對方因點擊離開按鈕而發出的消息“離開”,關閉自己的連接。 sendTF.setText("已斷開連接"); serverPWriters.get(i).close(); try { serverBReaders.get(i).close(); serverSockets.get(i).close(); } catch (IOException e) { e.printStackTrace(); } return; //關閉完后退出。 } serverTextAreas.get(i).append(serverContents[i]+"\n");//將接受到的消息顯示在自己的對話記錄區域。 break; } } }else{ //如果是客戶線程,將readline方法接受到的值賦給相應的content。 for(int i=0;i<clientThreads.size();i++){ if(Thread.currentThread() == clientThreads.get(i)){ //判斷具體是客戶端中的哪一條線程。 try { clientContents[i] = clientBReaders.get(i).readLine();//拿到對方發送過來的消息并保存給自己的字符串。 } catch (IOException e) { e.printStackTrace(); } if(clientContents[i] == null){ //當自己點擊離線按鈕時,clientContents[i]會為null, return; //為了防止下面報錯,在這里進行處理。 } if(clientContents[i].equals("關閉")){ //接收到對方因點擊離線按鈕而發出的消息“關閉”,而關閉自己的連接。 sendTF.setText("已斷開連接"); clientPWriters.get(i).close(); try { clientBReaders.get(i).close(); clientSockets.get(i).close(); } catch (IOException e) { e.printStackTrace(); } return; } clientTextAreas.get(i).append(clientContents[i]+"\n");//作為客戶端時,將接受到的消息顯示在對話記錄顯示區域。 break; } } } } } } @Override public void actionPerformed(ActionEvent e) { switch(e.getActionCommand()){ case "連接端口": //如果是連接端口,則執行以下操作。 try { Socket clientSocket = new Socket(hostTF.getText(),Integer.parseInt(portTF.getText()));//拿到工具欄一中填的端口號并生成socket去連接對方。 clientSockets.add(clientSocket); PrintWriter clientPWriter = new PrintWriter(clientSocket.getOutputStream(),true);//初始化輸出流對象。 clientPWriters.add(clientPWriter); BufferedReader clientBReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));//初始化輸入流對象。 clientBReaders.add(clientBReader); clientPWriter.println("連接"+name+"成功"); //將連接成功的消息發送給對方,提醒對方連接成功。 clientPWriter.println(name); //將自己的名稱發送給對方,方便對方設置選項卡的名稱。 System.out.println(11111111); String content = clientBReader.readLine(); //讀入對方發送過來的提醒消息。對方已執行了上面兩條語句,發送了對應的消息。 System.out.println(22222222); String name2 = clientBReader.readLine(); //讀取對方的名稱。 System.out.println(content); System.out.println(name2); MyJTextArea clientTextArea = new MyJTextArea(sign); sign++; //配和選項卡的index,記錄每個選項卡加到那個容器里去了,是服務器的容器還是客戶端的容器。 clientTextAreas.add(clientTextArea); this.tab.addTab(name2,new JScrollPane(clientTextArea)); //在選項卡窗格中添加一個選項卡。 clientTextArea.setEditable(false); //設置對話記錄區域不可編輯。 clientTextArea.setLineWrap(true); //設置對話記錄區域自動換行。 clientTextArea.append(content+"\n"); //在對話記錄區域顯示連接成功這條消息。 ChatThread clientThread = new ChatThread(); clientThreads.add(clientThread); clientThread.start(); //啟動該線程,方便和對方相互發送消息,因為主線程已經在上面accept()處阻塞。 } catch (IOException e1) { e1.printStackTrace(); } break; case "發送": if(serverTextAreas.size()>0){ //如果是被請求連接時而創建的選項卡要發送消息。即服務端。 for(int i=0;i<serverTextAreas.size();i++){ if(tab.getSelectedIndex() == serverTextAreas.get(i).getSign()){ //通過獲取當前選項卡的index去和jtextArea的sign比,因為他們的index和sign是匹配的, String sendContent = sendTF.getText(); //從而確定它是服務端的哪條線程,該用那對輸入輸出流進行發送和接受消息。 if(serverSockets.get(i).isClosed()){ //如果已斷開連接,則直接返回。 sendTF.setText("已斷開連接"); return; } if(sendContent.equals("")){ //如果發送的內容為空則直接接結束。 sendTF.setText("請輸入內容"); return; }else{ serverPWriters.get(i).println(name+": "+sendContent); //將發送消息框中的消息發送出去,并在前面加上自己的姓名。 serverTextAreas.get(i).append("我: "+sendContent+"\n");//在自己的對話記錄區域加上這句話。 sendTF.setText(""); //將發送消息框中的數據清空。 return; } } } } if(clientTextAreas.size()>0){ //如果是客戶端。 for(int i=0;i<clientTextAreas.size();i++){ if(tab.getSelectedIndex() == clientTextAreas.get(i).getSign()){ //通過獲取當前選項卡的index去和jtextArea的sign比,因為他們的index和sign是匹配的, String sendContent = sendTF.getText(); //從而確定它是客戶端的哪條線程,該用那對輸入輸出流進行發送和接受消息。 if(clientSockets.get(i).isClosed()){ //如果連接已斷開則直接返回。 sendTF.setText("已斷開連接"); return; } if(sendContent.equals("")){ //如果發送的內容為空則直接接結束。 sendTF.setText("請輸入內容"); return; }else{ clientPWriters.get(i).println(name+": "+sendContent); //將發送消息框中的消息發送出去,并在前面加上自己的姓名。 clientTextAreas.get(i).append("我: "+sendContent+"\n"); //在自己的對話記錄區域加上這句話 sendTF.setText(""); //將發送消息框中的數據清空。 return; } } } } break; case "離開": if(serverTextAreas.size()>0){ //如果是服務端。 for(int i=0;i<serverTextAreas.size();i++){ if(tab.getSelectedIndex() == serverTextAreas.get(i).getSign()){ //更前面一樣的道理,判斷是服務端的那個選項卡需要閉關。 serverPWriters.get(i).println("關閉"); //發送關閉消息,提醒對方也要關閉該socket連接。 sendTF.setText("已斷開連接"); serverPWriters.get(i).close(); try { serverBReaders.get(i).close(); serverSockets.get(i).close(); } catch (IOException e1) { e1.printStackTrace(); } break; } } } if(clientTextAreas.size()>0){ //如果是客戶端。 for(int i=0;i<clientTextAreas.size();i++){ if(tab.getSelectedIndex() == clientTextAreas.get(i).getSign()){ //更前面一樣的道理,判斷是客戶端的那個選項卡需要閉關。 clientPWriters.get(i).println("關閉"); //發送關閉消息,提醒對方也要關閉該socket連接。 sendTF.setText("已斷開連接"); clientPWriters.get(i).close(); try { clientBReaders.get(i).close(); clientSockets.get(i).close(); } catch (IOException e1) { e1.printStackTrace(); } break; } } } break; case "刪除頁": if(serverTextAreas.size()>0){ //為了防止下面的serverTextAreas數組越界。 for(int i=0;i<serverTextAreas.size();i++){ if(tab.getSelectedIndex() == serverTextAreas.get(i).getSign()){ //跟上面一樣的道理,判斷當前選項卡是屬于服務端還是客戶端。 if(serverSockets.get(i).isClosed()){ //先判斷是否斷開連接,否則不允許關閉。 tab.remove(i); //刪除當前選項卡。 }else{ sendTF.setText("請先關閉當前的連接"); return; } } } } if(clientTextAreas.size()>0){ //為了防止下面的serverTextAreas數組越界。 for(int i=0;i<clientTextAreas.size();i++){ if(tab.getSelectedIndex() == clientTextAreas.get(i).getSign()){ //跟上面一樣的道理,判斷當前選項卡是屬于服務端還是客戶端。 if(clientSockets.get(i).isClosed()){ //先判斷是否斷開連接,否則不允許關閉。 tab.remove(i); //刪除當前選項卡。 }else{ sendTF.setText("請先關閉當前的連接"); return; } } } } break; default: break; } } public static void main(String[] args) throws IOException{ new Client4("喜洋洋"); } }
效果圖:
感謝你能夠認真閱讀完這篇文章,希望小編分享的“java如何實現基于Tcp的socket聊天程序”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。