java實現(xiàn)基于Tcp的socket聊天程序
對于步入編程行業(yè)不深的初學(xué)者或是已經(jīng)有所領(lǐng)會的人來說,當(dāng)學(xué)習(xí)一項新的技術(shù)的時候,非??释幸粋€附上注釋完整的Demo。本人深有體會,網(wǎng)上的例子多到是很多,但是很雜不完整,寫代碼這種東西來不得半點馬虎,要是錯了一點,那也是運行不了的。這對于初學(xué)者來說更加的頭疼,因為他根本不知道錯在哪里,盲目的改只能錯上加錯。最后不得不去找找看看有沒有能夠直接運行的例子再加以模仿。
下面是博主在學(xué)習(xí)Java的socket時寫的一個完整的例子,并且?guī)狭送暾淖⑨尅K且粋€簡單的聊天程序,但是它可以設(shè)置任意多用戶同時登錄,然后相互兩兩交流。博主僅僅在自己電腦上實現(xiàn)同時登錄,然后兩兩相互交流。
程序的大體思路是這樣的:
①該用戶作為服務(wù)端也就是被請求連接端和主動請求連接其他端時不一樣的,其次有可能被其他的用戶連接很多次,那么你作為服務(wù)端,就會有很多連接,同樣的道理,你作為客戶端也會有很多的連接。為了程序更加通俗易懂,博主寫的時候,設(shè)置了很多容器,將不一樣的東西分開放置。做到解耦合,不然到后面自己都分不清了。
②你可以一次寫兩個類,client1,client2,,client1先作為服務(wù)端,client2作為客戶端,客戶端去連接服務(wù)端,從而實現(xiàn)client1的服務(wù)端功能和client2的客戶端功能。每次實現(xiàn)一個功能就先將服務(wù)端和客戶端的功能整合一下,互換角色,看是否存在錯誤。
③在實現(xiàn)了兩個用戶的情況下再去寫第三個類client3,代碼就是復(fù)制粘貼。當(dāng)然你也可以直接創(chuàng)建一個client3的類,然后直接在類的main方法中改了端口號,和用戶名就行。第三個實現(xiàn)后,第四個第五個也就實了。
下面是具體的代碼:
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;
/**
* 被請求連接時稱為服務(wù)器;主動連接對方時稱為客戶端。
* @author Administrator
*
*/
public class Client4 extends JFrame implements ActionListener{
private static int sign = 0; //用來記錄選項卡的標(biāo)簽,0標(biāo)示第一個。
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; //存放服務(wù)器線程的容器。即存放被請求連接時所創(chuàng)建的線程的容器。
private ArrayList<ChatThread> clientThreads; //存放客戶端線程的容器。即存放主動連接對方時所創(chuàng)建的線程的容器。
private ArrayList<ServerSocket> servers; //服務(wù)對象的容器。
private ArrayList<MyJTextArea> serverTextAreas; //作為服務(wù)器時,存放所創(chuàng)建的對話記錄顯示區(qū)域的容器。
private ArrayList<MyJTextArea> clientTextAreas; //作為客戶端時,岑芳所創(chuàng)建的對話記錄顯示區(qū)域的容器。
private ArrayList<Socket> serverSockets; //存放被請求連接時所創(chuàng)建的socket的容器。
private ArrayList<Socket> clientSockets; //存放主動請求連接時所創(chuàng)立的socket的容器。
private ArrayList<PrintWriter> serverPWriters; //存放服務(wù)端輸出流對象的容器。
private ArrayList<BufferedReader> serverBReaders; //存放服務(wù)端輸入流對象的容器。
private ArrayList<PrintWriter> clientPWriters; //存放客戶端輸出流對象的容器。
private ArrayList<BufferedReader> clientBReaders; //存放客戶端輸入流對象的容器。
private ArrayList<Integer> ports; //存放作為服務(wù)器時已連接的端口。
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("主機(jī)");
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("發(fā)送");
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); //設(shè)置等待的textfield不可以編輯。
hostTF.setEnabled(false); //設(shè)置連接的ip地址不可編輯,當(dāng)然這里可以更改成其他電腦的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);
//主線程進(jìn)入之后 在server.accept()阻塞等待客戶端來連接。
while(true){
ServerSocket server = new ServerSocket(port); //作為服務(wù)器,開發(fā)自己供連接的端口。
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+"成功"); //將連接成功的消息發(fā)送給請求方,提醒對方連接成功。
serverPWriter.println(name); //將自己的名稱發(fā)送給對方,方便對方設(shè)置選項卡的名稱。
String content = serverBReader.readLine(); //此時對方也發(fā)送了上面兩條消息過來。讀入對方發(fā)送過來的提醒消息。
String name2 = serverBReader.readLine(); //讀取對方的名稱。方便后面設(shè)置選項卡的名稱。
System.out.println(content);
System.out.println(name2);
MyJTextArea serverTextArea = new MyJTextArea(sign);
sign++; //new了一個textArea后,sign自動增加1,好和選項卡對應(yīng),
//知道這個選項卡加到哪個容器了,是服務(wù)器的還是客戶端的。
serverTextAreas.add(serverTextArea);
this.tab.addTab(name2,new JScrollPane(serverTextArea));//在選項卡窗格中添加一個選項卡。
serverTextArea.setEditable(false); //設(shè)置對話記錄顯示區(qū)域不可編輯。
serverTextArea.setLineWrap(true); //設(shè)置對話記錄顯示區(qū)域自動換行。
serverTextArea.append(content+"\n"); //在對話記錄區(qū)域輸出連接成功這條消息。
ChatThread thread = new ChatThread(); //new了一個線程去執(zhí)行run方法,用于和對方交流,對方也會開啟一個線程來和你交流。
//這里要開啟新線程的原因是main線程經(jīng)過一輪后會在上面accept方法處阻塞。
serverThreads.add(thread);
thread.start(); //啟動該線程,方便接收對方發(fā)來的消息。
port++; //端口號加一,開放下一個供連接的端口。
waitPortTF.setText(port+""); //更新顯示等待的下個端口。
}
}
private class ChatThread extends Thread {
private String[] serverContents = new String[10]; //當(dāng)作為服務(wù)端時,用來存放相互發(fā)送消息時的一句話。
private String[] clientContents = new String[10]; //當(dāng)作為客戶端時,用來存放相互發(fā)送消息時的一句話。
private boolean isServerThread = true; //判斷當(dāng)前在執(zhí)行run方法的線程是不是服務(wù)端線程。
@Override
public void run() {
while(true){
if(serverThreads.size()>0){ //判斷當(dāng)前的線程是否是服務(wù)線程。先判斷是否大于0,是為了防止serverThreads
for(int i=0;i<serverThreads.size();i++){ //報數(shù)組越界。
if(Thread.currentThread() == serverThreads.get(i)){ //拿當(dāng)前線程和服務(wù)端容器里的線程去比,看是否是服務(wù)端的線程。
isServerThread = true;
}
}
}
if(clientThreads.size()>0){ //判斷當(dāng)前的線程是否是客戶線程。 先判斷是否大于0,是為了防止clientThreads
for(int i=0;i<clientThreads.size();i++){ //報數(shù)組越界。
if(Thread.currentThread() == clientThreads.get(i)){ //拿當(dāng)前線程和客戶端容器里的線程去比,看是否是客戶端的線程。
isServerThread = false;
}
}
}
if(isServerThread){ //如果是服務(wù)端的線程,將readline方法接受到的值賦給相應(yīng)的content。
for(int i=0;i<serverThreads.size();i++){
if(Thread.currentThread() == serverThreads.get(i)){ //判斷具體是服務(wù)端里的那條線程。
try {
serverContents[i] = serverBReaders.get(i).readLine();//將對方發(fā)送過來的消息賦值給這條線程的接受消息字符串。
} catch (IOException e) {
e.printStackTrace();
return; //出現(xiàn)異常時直接退出方法。
}
if(serverContents[i]==null){ //在自己點擊離線按鈕時,serverContents[i]為null,
return; //因此在這里進(jìn)行處理,避免后面報錯。
}
if(serverContents[i].equals("關(guān)閉")){ //接收到對方因點擊離開按鈕而發(fā)出的消息“離開”,關(guān)閉自己的連接。
sendTF.setText("已斷開連接");
serverPWriters.get(i).close();
try {
serverBReaders.get(i).close();
serverSockets.get(i).close();
} catch (IOException e) {
e.printStackTrace();
}
return; //關(guān)閉完后退出。
}
serverTextAreas.get(i).append(serverContents[i]+"\n");//將接受到的消息顯示在自己的對話記錄區(qū)域。
break;
}
}
}else{ //如果是客戶線程,將readline方法接受到的值賦給相應(yīng)的content。
for(int i=0;i<clientThreads.size();i++){
if(Thread.currentThread() == clientThreads.get(i)){ //判斷具體是客戶端中的哪一條線程。
try {
clientContents[i] = clientBReaders.get(i).readLine();//拿到對方發(fā)送過來的消息并保存給自己的字符串。
} catch (IOException e) {
e.printStackTrace();
}
if(clientContents[i] == null){ //當(dāng)自己點擊離線按鈕時,clientContents[i]會為null,
return; //為了防止下面報錯,在這里進(jìn)行處理。
}
if(clientContents[i].equals("關(guān)閉")){ //接收到對方因點擊離線按鈕而發(fā)出的消息“關(guān)閉”,而關(guān)閉自己的連接。
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");//作為客戶端時,將接受到的消息顯示在對話記錄顯示區(qū)域。
break;
}
}
}
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
switch(e.getActionCommand()){
case "連接端口": //如果是連接端口,則執(zhí)行以下操作。
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+"成功"); //將連接成功的消息發(fā)送給對方,提醒對方連接成功。
clientPWriter.println(name); //將自己的名稱發(fā)送給對方,方便對方設(shè)置選項卡的名稱。
System.out.println(11111111);
String content = clientBReader.readLine(); //讀入對方發(fā)送過來的提醒消息。對方已執(zhí)行了上面兩條語句,發(fā)送了對應(yīng)的消息。
System.out.println(22222222);
String name2 = clientBReader.readLine(); //讀取對方的名稱。
System.out.println(content);
System.out.println(name2);
MyJTextArea clientTextArea = new MyJTextArea(sign);
sign++; //配和選項卡的index,記錄每個選項卡加到那個容器里去了,是服務(wù)器的容器還是客戶端的容器。
clientTextAreas.add(clientTextArea);
this.tab.addTab(name2,new JScrollPane(clientTextArea)); //在選項卡窗格中添加一個選項卡。
clientTextArea.setEditable(false); //設(shè)置對話記錄區(qū)域不可編輯。
clientTextArea.setLineWrap(true); //設(shè)置對話記錄區(qū)域自動換行。
clientTextArea.append(content+"\n"); //在對話記錄區(qū)域顯示連接成功這條消息。
ChatThread clientThread = new ChatThread();
clientThreads.add(clientThread);
clientThread.start(); //啟動該線程,方便和對方相互發(fā)送消息,因為主線程已經(jīng)在上面accept()處阻塞。
} catch (IOException e1) {
e1.printStackTrace();
}
break;
case "發(fā)送":
if(serverTextAreas.size()>0){ //如果是被請求連接時而創(chuàng)建的選項卡要發(fā)送消息。即服務(wù)端。
for(int i=0;i<serverTextAreas.size();i++){
if(tab.getSelectedIndex() == serverTextAreas.get(i).getSign()){ //通過獲取當(dāng)前選項卡的index去和jtextArea的sign比,因為他們的index和sign是匹配的,
String sendContent = sendTF.getText(); //從而確定它是服務(wù)端的哪條線程,該用那對輸入輸出流進(jìn)行發(fā)送和接受消息。
if(serverSockets.get(i).isClosed()){ //如果已斷開連接,則直接返回。
sendTF.setText("已斷開連接");
return;
}
if(sendContent.equals("")){ //如果發(fā)送的內(nèi)容為空則直接接結(jié)束。
sendTF.setText("請輸入內(nèi)容");
return;
}else{
serverPWriters.get(i).println(name+": "+sendContent); //將發(fā)送消息框中的消息發(fā)送出去,并在前面加上自己的姓名。
serverTextAreas.get(i).append("我: "+sendContent+"\n");//在自己的對話記錄區(qū)域加上這句話。
sendTF.setText(""); //將發(fā)送消息框中的數(shù)據(jù)清空。
return;
}
}
}
}
if(clientTextAreas.size()>0){ //如果是客戶端。
for(int i=0;i<clientTextAreas.size();i++){
if(tab.getSelectedIndex() == clientTextAreas.get(i).getSign()){ //通過獲取當(dāng)前選項卡的index去和jtextArea的sign比,因為他們的index和sign是匹配的,
String sendContent = sendTF.getText(); //從而確定它是客戶端的哪條線程,該用那對輸入輸出流進(jìn)行發(fā)送和接受消息。
if(clientSockets.get(i).isClosed()){ //如果連接已斷開則直接返回。
sendTF.setText("已斷開連接");
return;
}
if(sendContent.equals("")){ //如果發(fā)送的內(nèi)容為空則直接接結(jié)束。
sendTF.setText("請輸入內(nèi)容");
return;
}else{
clientPWriters.get(i).println(name+": "+sendContent); //將發(fā)送消息框中的消息發(fā)送出去,并在前面加上自己的姓名。
clientTextAreas.get(i).append("我: "+sendContent+"\n"); //在自己的對話記錄區(qū)域加上這句話
sendTF.setText(""); //將發(fā)送消息框中的數(shù)據(jù)清空。
return;
}
}
}
}
break;
case "離開":
if(serverTextAreas.size()>0){ //如果是服務(wù)端。
for(int i=0;i<serverTextAreas.size();i++){
if(tab.getSelectedIndex() == serverTextAreas.get(i).getSign()){ //更前面一樣的道理,判斷是服務(wù)端的那個選項卡需要閉關(guān)。
serverPWriters.get(i).println("關(guān)閉"); //發(fā)送關(guān)閉消息,提醒對方也要關(guān)閉該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()){ //更前面一樣的道理,判斷是客戶端的那個選項卡需要閉關(guān)。
clientPWriters.get(i).println("關(guān)閉"); //發(fā)送關(guān)閉消息,提醒對方也要關(guān)閉該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數(shù)組越界。
for(int i=0;i<serverTextAreas.size();i++){
if(tab.getSelectedIndex() == serverTextAreas.get(i).getSign()){ //跟上面一樣的道理,判斷當(dāng)前選項卡是屬于服務(wù)端還是客戶端。
if(serverSockets.get(i).isClosed()){ //先判斷是否斷開連接,否則不允許關(guān)閉。
tab.remove(i); //刪除當(dāng)前選項卡。
}else{
sendTF.setText("請先關(guān)閉當(dāng)前的連接");
return;
}
}
}
}
if(clientTextAreas.size()>0){ //為了防止下面的serverTextAreas數(shù)組越界。
for(int i=0;i<clientTextAreas.size();i++){
if(tab.getSelectedIndex() == clientTextAreas.get(i).getSign()){ //跟上面一樣的道理,判斷當(dāng)前選項卡是屬于服務(wù)端還是客戶端。
if(clientSockets.get(i).isClosed()){ //先判斷是否斷開連接,否則不允許關(guān)閉。
tab.remove(i); //刪除當(dāng)前選項卡。
}else{
sendTF.setText("請先關(guān)閉當(dāng)前的連接");
return;
}
}
}
}
break;
default:
break;
}
}
public static void main(String[] args) throws IOException{
new Client4("喜洋洋");
}
}
效果圖:

源碼下載:Java基于Tcp的socket聊天程序
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- java中UDP簡單聊天程序?qū)嵗a
- 詳解基于java的Socket聊天程序——客戶端(附demo)
- java網(wǎng)絡(luò)編程學(xué)習(xí)java聊天程序代碼分享
- java基于TCP協(xié)議實現(xiàn)聊天程序
- java基于C/S模式實現(xiàn)聊天程序(客戶端)
- 詳解基于java的Socket聊天程序——服務(wù)端(附demo)
- 詳解基于java的Socket聊天程序——初始設(shè)計(附demo)
- java實現(xiàn)簡單TCP聊天程序
- 基于Java的Socket多客戶端Client-Server聊天程序的實現(xiàn)
- 用Java實現(xiàn)聊天程序
相關(guān)文章
使用Flyway進(jìn)行Java數(shù)據(jù)庫版本控制的操作指南
今天我們將深入探討如何使用Flyway進(jìn)行Java數(shù)據(jù)庫版本控制,Flyway是一個流行的數(shù)據(jù)庫遷移工具,用于管理和自動化數(shù)據(jù)庫模式的演變,文中通過代碼示例介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2024-07-07
詳解Spring Cloud Finchley版中Consul多實例注冊的問題處理
這篇文章主要介紹了詳解Spring Cloud Finchley版中Consul多實例注冊的問題處理,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08
java中使用Files.readLines()處理文本中行數(shù)據(jù)方式
這篇文章主要介紹了java中使用Files.readLines()處理文本中行數(shù)據(jù)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12

