目录

规划

Java GUI设计

Java Socket

Java 服务器


利用 IntelliJ IDEA软件为例

首先,我们应当了解,像运行在两台电脑或者手机终端这样的程序一般是没有办法直接相互发送信息的,一般是需要第三方服务器提供服务,就像QQ或者微信那样,来为这两个终端程序提供转发服务,

因此,我们设计的程序包括服务端(Server)和客户端(Client),两个客户端程序信息交流过程实际上就是各自和服务器信息交流的过程,服务器提供转发服务

规划

我们应当先规划程序的逻辑,在所有关键点中,程序的可靠性是最重要的!如果一个程序由于各种bug,造成连接断开,或者程序崩溃,或者甚至不知道对方是否在线,那么这常常让人很不舒服

因此我计划将客户端和服务器的每次交流分为两行内容,第一行为发送信息的类型(消息,状态,异常),第二行为具体内容(消息内容,状态信息,异常信息).同样,服务器或者客户端在接受消息也是每次接受两行的内容对两行内容分别处理

其次应当考虑的逻辑是,要设计一个多用户的系统,使能够多个用户同时使用一台服务器进行信息交流,我计划是这样的,每一个用户自行分配一个ID,ID在0~1023之间,每一对奇偶数构成一对连接(0和1,2和3,4和5......)并将此ID发送服务器,如果两个客户端为一对奇偶数,那么能够相互发送和接受消息,同一个ID只能被用一次,除非占用该ID的客户端断开连接

Java GUI设计

先无需管他逻辑怎样实现,先把UI界面做完整

把主方法填完整

package MyProject;
public class Client {public static void main(String []args){}
}

创建ClientFrame界面类和ClientPanel面板类,使其分别继承与JFrame和JPanel

class ClientFrame extends JFrame {public ClientPanel cp;//声明ClientPanel的一个变量public ClientFrame(){setSize(700,700);//设置界面大小setLocationRelativeTo(null);//使界面默认显示在屏幕中央setResizable(false);//使界面大小不能被改变,否则可能会使布局错乱setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置在关闭窗口时的操作,即关闭窗口是退出程序cp=new ClientPanel(this);//将ClientPanel实例化,并将自身传递给ClientPanel对象,供改变内容时调用add(cp);//将面板对象添加至此窗口对象setVisible(true);//默认是不显示窗口的,需要手动去显示,一定要防止最后面,否则会导致面板的内容无法显示}
}
class ClientPanel extends JPanel{public ClientFrame cf;//用来保存ClientFrame对象 public ClientPanel(ClientFrame cf){this.cf=cf;//赋值传过来的对象}
}

除去具体的控件,界面已经大致完整

在mian方法中实例化一个ClientFrame对象,就可以看到界面了

public class Client {public static void main(String []args){ClientFrame cf=new ClientFrame();//实例化ClientFrame对象cf.setTitle("聊天程序-未连接至服务器");//设置标题}
}

效果如下

然后我们为ClientPanel类添加控件,形成最终的交互界面

class ClientPanel extends JPanel implements ActionListener {public ClientFrame cf;//用来保存ClientFrame对象public Connection cc;public JLabel l1=new JLabel("服务器地址:");public JLabel l2=new JLabel("服务器端口:");public JLabel l3=new JLabel("为自己设置ID(0-1023)");//供身份识别,进行一对一的信息交流public JTextField t1=new JTextField("");//输入ip地址public JTextField t2=new JTextField("11999");//输入端口号public JTextField t3=new JTextField("");//输入IDpublic JButton b1=new JButton("连接");public JTextArea ta1=new JTextArea("");//消息文本框public JTextArea ta2=new JTextArea("");//消息编辑界面public JTextArea ta3=new JTextArea("");//程序运行状态日志框public JScrollPane s1=new JScrollPane();//定义可滚动面板public JScrollPane s2=new JScrollPane();public JScrollPane s3=new JScrollPane();public JButton b2=new JButton("发送");public ClientPanel(ClientFrame cf){this.cf=cf;//赋值传过来的对象cc=new Connection(this);setLayout(null);//设置空布局,使用xy坐标确定绝对位置l1.setBounds(5,5,70,20);//设置文本框的位置x,y和大小width,heightadd(l1);//将此控件加入此面板t1.setBounds(80,5,100,20);add(t1);l2.setBounds(190,5,70,20);add(l2);t2.setBounds(270,5,50,20);add(t2);l3.setBounds(330,5,130,20);add(l3);t3.setBounds(470,5,50,20);add(t3);b1.setBounds(560,5,100,20);add(b1);s1.setBounds(5,30,670,500);//设置可滚动面板的位置和大小add(s1);//将此面板加入s1.getViewport().add(ta1);//将文本框附加在滚动面板上ta1.setEditable(false);//设置消息框不可被编辑s2.setBounds(5,540,400,110);add(s2);s2.getViewport().add(ta2);b2.setBounds(410,540,100,110);add(b2);s3.setBounds(515,540,155,110);add(s3);s3.getViewport().add(ta3);ta3.setEditable(false);//设置消息框不可被编辑b1.addActionListener(this);//将按钮添加ActionListener监听器接口,这样,点击按钮的时候,触发事件会通过接口方法actionPerformed执行b2.addActionListener(this);}
}

展现的界面如下

然后我们为按钮添加事件,要对按钮添加事件,需要实现ActionListener接口,这样按钮所触发的事件会通过接口的actionPerformed方法传递过来,通过比较传过来的参数e,获取e.getSource(),和按钮比较,就能知道是哪个按钮被按下了,然后我们调用相关的方法即可

class ClientPanel extends JPanel implements ActionListener {
//*******************重复代码省略*******************@Overridepublic void actionPerformed(ActionEvent e) {if(e.getSource()==b1){//判断是哪个按钮被按下,就和那个按钮比较一下Connect();//连接按钮被按下,就执行连接程序}else if(e.getSource()==b2){//发送按钮被按下,就执行发送程序SendMessage();}}public void Connect(){try {String address = t1.getText();//获取输入框的服务器IP地址int port = Integer.parseInt(t2.getText());//将String类型端口号转换为int类型if(port<0||port>65535){//判断端口是否在正常的范围内throw new InputMismatchException();}String ID = t3.getText();//获取ID码if(Integer.parseInt(ID)<0||Integer.parseInt(ID)>1023){//检测正常范围throw new InputMismatchException();}cc.connect(address,port,ID);//如果上面有任何异常,就会被下面捕获,而不会执行到此处的代码,因此,此处执行不会有其他异常}catch (NumberFormatException e){SendLogs("端口格式错误!");}catch (InputMismatchException e){SendLogs("端口不在0~65535正常范围内或者ID不在0~1023范围内");}}public void SendMessage(){String str=ta2.getText().replace("\n","\\n");//由于客户端和服务器每次交流消息分为两行内容,第一行为发送类型(消息,状态码,异常),第二行为内容,为避免用户输入多行信息造成的程序识别信息混乱,这里将用户输入的回车符转义为\ncc.SendData(Connection.Message,str);//将转义后的信息发送出去InsertMessage(true,ta2.getText());//并在聊天框中显示自己发送的内容ta2.setText("");//清除输入框的内容}public void AcceptMessage(String s){String S=s.replace("\\n","\n");//将发送信息时转义过的字符串\n重新转换为回车符InsertMessage(false,S);//在聊天框中显示对方发送的信息}public void InsertMessage(boolean IsMe,String s){//向消息面板插入新的消息if(!IsMe) {ta1.setText(ta1.getText() + "\n" + "对方:" + s);//如果是对方发送的消息,就在消息的前面标注是对方发送的}else{ta1.setText(ta1.getText() + "\n" + "我:" + s);//如果是自己发送的消息,就在消息的前面标注是自己发送的}}public void SendLogs(String s){//发送日志到面板的方法ta3.setText(ta3.getText()+"\n"+s);//设置日志框的内容为之前的内容,换行,再加上新的内容}
}

至此,UI界面及其方法接口完成

Java Socket

然后我们需要为这个程序添加具体的逻辑

我们需要新建两个类,一个是connection类,负责整个逻辑,一个是接收消息线程类,这样逻辑层次关系清晰,分工明确


class Connection{Socket sk;//定义Socket变量skClientPanel cp;//储存cp变量,用于调用方法InputStream is;//储存输入流OutputStream os;//储存输出流PrintWriter pw;//储存PrintWriter对象,用于发送文字信息BufferedReader br;//储存BufferedReader对象,用于接收文字信息AcceptThread at;//定义接收消息线程类public static final String Message = "M";//定义消息类型M,即下一行内容为发送的消息public static final String Status =  "S";//定义状态码,即下一行传输的内容不是消息,而是连接状态码public static final String Exception = "E";//定义异常码,即下一行返回服务器遇到的异常public static final String StatusID= "ID";//ID标志,其后附加自身的ID一并发送过去public static final String StatusSUCCESS="SU";//服务器返回的状态码,连接成功的标志public static final String StatusPARTYDISCONNECT="PD";//服务器返回的状态码,对方断开连接的标志public static final String StatusPARTYCONNECT="PC";//服务器返回的状态码,对方成功连接并处在同一频道内的标志public Connection(ClientPanel cp){this.cp=cp;//接收并储存对象}public void connect(String address,int port,String ID){//连接按钮所执行的操作try{sk=new Socket(address,port);//建立一个新的连接is=sk.getInputStream();//获取输入流os=sk.getOutputStream();//获取输出流pw=new PrintWriter(os);//获取PrintWriter对象br=new BufferedReader(new InputStreamReader(is));//获取BufferedReader对象SendData(Status,StatusID+ID);//向服务器发送自己设定的ID码at=new AcceptThread(this);//开始新的线程接收消息}catch (Exception e){String s=e.getMessage();if (s.equals("Connection refused: connect")) {cp.SendLogs("无法连接至服务器,请确认服务器是否开启,请稍后再试");}else if(s.equals("Connection timed out: connect")){cp.SendLogs("无法连接至服务器,请确认服务器是否开启,请稍后再试");} else{cp.SendLogs("其他错误原因:"+e);}}}public void SendData(String Type, String Content){pw.println(Type);//发送第一行内容为类型pw.println(Content);//第二行为内容pw.flush();//发送信息}public void AcceptMessage(String s){cp.AcceptMessage(s);}public void AcceptStatus(String s){//将状态信息显示在窗口标题以及日志栏中switch (s) {case StatusSUCCESS:cp.SendLogs("连接服务器成功");cp.cf.setTitle("聊天程序-已连接至服务器-对方未在线");break;case StatusPARTYDISCONNECT:cp.SendLogs("对方已断开连接");cp.cf.setTitle("聊天程序-已连接至服务器-对方未在线");break;case StatusPARTYCONNECT:cp.SendLogs("对方已连接");cp.cf.setTitle("聊天程序-已连接至服务器-对方在线");break;}}public void AcceptException(String s){cp.SendLogs("服务器异常:"+s);}
}
class AcceptThread extends Thread{Connection cc;//记录Connection对象,用于调用方法public AcceptThread(Connection cc){this.cc=cc;//接受并赋值Connection对象start();//开始一个新的线程,从run()开始执行}@Overridepublic void run() {while(true){//无限循环接收消息if(cc.sk!=null) {//如果未连接服务器,那么忽略接收消息的代码try {String s = cc.br.readLine();//读取第一行信息if (s != null) {//避免读取为空产生异常switch (s) {case Connection.Status: {//如果是状态码String ss = cc.br.readLine();//读取下一行while (ss == null) {//保证读取的有效行ss = cc.br.readLine();}cc.AcceptStatus(ss);//处理状态码break;}case Connection.Message: {//如果接收的是消息类型String ss = cc.br.readLine();//读取下一行while (ss == null) {ss = cc.br.readLine();}cc.AcceptMessage(ss);//接收消息break;}case Connection.Exception: {//如果接收的是异常String ss = cc.br.readLine();while (ss == null) {ss = cc.br.readLine();}cc.AcceptException(ss);//那么将服务器的异常信息打印在日志框内break;}}}} catch (IOException e) {if(e.getMessage().equals("Connection reset")){cc.cp.SendLogs("服务器连接已断开,可能原因:服务器已关闭");}else{System.out.println("IO错误:" + e);}break;}}}}
}

至此,该聊天系统的客户端程序已经做完成

Java 服务器

如果我们需要两个客户端程序能够交流,必须需要服务器(Server),服务器分为公网服务器,和私网服务器,如果我们需要在世界各地都能用这个程序交流,那么必须有一台公网服务器执行服务程序,否则,就只能在一个小范围的网络中使用,当然,我们的电脑就可以作为一个私网服务器

完整的服务器代码如下(修改后):

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;public class Server{public static Connection []Rec=new Connection[1024];//记录1024个连接对象public static void main(String []args) throws IOException {ServerSocket ss=new ServerSocket(11999);//监听11999端口while(true){//无限循环Socket sk=ss.accept();//一直监听并获取Socket对象的连接DealThread dt=new DealThread(sk);//建立一个新的线程去执行逻辑任务,主线程仍然监听}}public static void Destroy(int ID){//当用户断开连接时,销毁储存在表中的对象Rec[ID]=null;}
}
class DealThread extends Thread{Socket sk;//储存Socket对象,供随时调用Connection cc;//存储Connection对象,供随时调用public DealThread(Socket sk){this.sk=sk;//传入并保存对象start();//执行新的线程}@Overridepublic void run() {cc=new Connection(sk,this);//新建线程开始执行,利用Connection的构造方法实例化一个对象}public void Insert(int ID){//将新连接完成的Connect对象储存在Server.Rec的表中System.out.println("已接受ID:"+ID);//将连接成功的客户端的ID显示出来if(Server.Rec[ID]!=null){//如果这个ID在数据表中已经被占用,那么就发出异常信息,提示客户端该ID已经占用cc.SendData(Connection.Exception,"This ID has been occupied by others. The connection will be disconnected. Please change the ID and try again");}else{if(Server.Rec[GetOther(ID)]==null){//如果互为奇偶数的另一个数没有被占用cc.SendData(Connection.Status,Connection.StatusSUCCESS);//那么发送状态码连接成功System.out.println("连接成功");//显示Server.Rec[ID]=cc;//储存在Rec表中cc.ID=ID;//将自身的ID储存在Connection里,以供备用}else{cc.SendData(Connection.Status,Connection.StatusSUCCESS);//如果互为奇偶数的另一个数被占用Server.Rec[ID]=cc;//储存在Rec表中Server.Rec[GetOther(ID)].SetParty(cc);//两个Connect对象相互传递,使之能够利用对方的Connection向对方传输信息cc.SetParty(Server.Rec[GetOther(ID)]);//两个Connect对象相互传递,使之能够利用对方的Connection向对方传输信息cc.ID=ID;//将自身的ID储存在Connection里,以供备用}}}public int GetOther(int ID){//获取和这个数互为奇偶数的数if(ID%2==0){return ID+1;}else{return ID-1;}}
}
class Connection{Socket sk;//定义Socket变量skConnection party;DealThread dt;int ID;InputStream is;//储存输入流OutputStream os;//储存输出流BufferedWriter bw;//储存PrintWriter对象,用于发送文字信息BufferedReader br;//储存BufferedReader对象,用于接收文字信息AcceptThread at;//定义接收消息线程类public static final String Message = "M";//和客户端相同public static final String Status =  "S";public static final String Exception = "E";public static final String StatusID= "ID";public static final String StatusSUCCESS="SU";public static final String StatusPARTYDISCONNECT="PD";public static final String StatusPARTYCONNECT="PC";public Connection(Socket sk,DealThread dt){this.sk=sk;//传入并储存对象this.dt=dt;//传入并储存对象try{is=sk.getInputStream();//获取输入流os=sk.getOutputStream();//获取输出流bw =new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));//获取PrintWriter以供发送信息br=new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));//获取BufferedReader以供接受信息}catch (Exception e){//捕获可能存在的错误System.out.println("服务程序错误!原因:"+e);}at=new AcceptThread(this);//开始新的线程监听消息}public void SetParty(Connection cc){//设置对方的Connection,通过调用对方的Connection的SendData()向对方发送消息this.party=cc;//设置SendData(Status,StatusPARTYCONNECT);//发送状态码,对方已经在线}public void SendData(String Type, String Content){try {bw.write(Type + "\n");//向客户端发送信息类型bw.write(Content + "\n");//向客户端发送信息内容bw.flush();//发送}catch (Exception e){System.out.println(e.getMessage());}}public void AcceptMessage(String s){if(party!=null){party.SendData(Message,s);//如果用户发送信息,检测party是否被赋值,如果不为空,说明对方在线}else{SendData(Exception,"对方不在线!");//如果为空,说明对方不在线}}public void AcceptStatus(String s){//检测ID状态码if(s.startsWith(StatusID)){dt.Insert(Integer.parseInt(s.substring(2)));//将附加在ID后面的ID码截取出来}}
}
class AcceptThread extends Thread{Connection cc;//储存Connection,供调用public AcceptThread(Connection cc){this.cc=cc;//传入Connection对象start();}@Overridepublic void run() {while(true){if(cc.sk!=null) {try {String s = cc.br.readLine();if (s != null) {if (s.equals(Connection.Status)) {//检测获取的信息类型String ss = cc.br.readLine();while (ss == null) {ss = cc.br.readLine();}cc.AcceptStatus(ss);//处理状态码} else if (s.equals(Connection.Message)) {//检测获取的信息类型String ss = cc.br.readLine();while (ss == null) {ss = cc.br.readLine();}cc.AcceptMessage(ss);//接受信息,并利用对方的Connection向对方发送信息}}} catch (IOException e) {if(e.getMessage().equals("Connection reset")){System.out.println(cc.ID+"连接已断开");if(cc.party!=null){//如果产生异常(Connection reset),说明连接断开,向对方发送状态码说明该用户已经不在线cc.party.SendData(Connection.Status,Connection.StatusPARTYDISCONNECT);//发送状态码}Server.Destroy(cc.ID);//销毁储存的IDbreak;//终止无限循环}else{System.out.println("IO错误:" + e);//显示其他可能存在的异常}}}}}
}

至此,项目完成

完整的客户端代码如下(修改后):

package MyProject;import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.InputMismatchException;public class Client {public static void main(String []args){ClientFrame cf=new ClientFrame();//实例化ClientFrame对象cf.setTitle("聊天程序-未连接至服务器");//设置标题}
}
class ClientFrame extends JFrame {public ClientPanel cp;//声明ClientPanel的一个变量public ClientFrame(){setSize(700,700);//设置界面大小setLocationRelativeTo(null);//使界面默认显示在屏幕中央setResizable(false);//使界面大小不能被改变,否则可能会使布局错乱setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//设置在关闭窗口时的操作,即关闭窗口是退出程序cp=new ClientPanel(this);//将ClientPanel实例化,并将自身传递给ClientPanel对象,供改变内容时调用add(cp);//将面板对象添加至此窗口对象setVisible(true);//默认是不显示窗口的,需要手动去显示,一定要防止最后面,否则会导致面板的内容无法显示}
}
class ClientPanel extends JPanel implements ActionListener {public ClientFrame cf;//用来保存ClientFrame对象public Connection cc;public JLabel l1=new JLabel("服务器地址:");public JLabel l2=new JLabel("服务器端口:");public JLabel l3=new JLabel("为自己设置ID(0-1023)");//供身份识别,进行一对一的信息交流public JTextField t1=new JTextField("");//输入ip地址public JTextField t2=new JTextField("11999");//输入端口号public JTextField t3=new JTextField("");//输入IDpublic JButton b1=new JButton("连接");public JTextArea ta1=new JTextArea("");//消息文本框public JTextArea ta2=new JTextArea("");//消息编辑界面public JTextArea ta3=new JTextArea("");//程序运行状态日志框public JScrollPane s1=new JScrollPane();//定义可滚动面板public JScrollPane s2=new JScrollPane();public JScrollPane s3=new JScrollPane();public JButton b2=new JButton("发送");public ClientPanel(ClientFrame cf){this.cf=cf;//赋值传过来的对象cc=new Connection(this);setLayout(null);//设置空布局,使用xy坐标确定绝对位置l1.setBounds(5,5,70,20);//设置文本框的位置x,y和大小width,heightadd(l1);//将此控件加入此面板t1.setBounds(80,5,100,20);add(t1);l2.setBounds(190,5,70,20);add(l2);t2.setBounds(270,5,50,20);add(t2);l3.setBounds(330,5,130,20);add(l3);t3.setBounds(470,5,50,20);add(t3);b1.setBounds(560,5,100,20);add(b1);s1.setBounds(5,30,670,500);//设置可滚动面板的位置和大小add(s1);//将此面板加入s1.getViewport().add(ta1);//将文本框附加在滚动面板上ta1.setEditable(false);//设置消息框不可被编辑s2.setBounds(5,540,400,110);add(s2);s2.getViewport().add(ta2);b2.setBounds(410,540,100,110);add(b2);s3.setBounds(515,540,155,110);add(s3);s3.getViewport().add(ta3);ta3.setEditable(false);//设置消息框不可被编辑b1.addActionListener(this);//将按钮添加ActionListener监听器接口,这样,点击按钮的时候,触发事件会通过接口方法actionPerformed执行b2.addActionListener(this);}@Overridepublic void actionPerformed(ActionEvent e) {if(e.getSource()==b1){Connect();}else if(e.getSource()==b2){SendMessage();}}public void Connect(){try {String address = t1.getText();int port = Integer.parseInt(t2.getText());//将String类型端口号转换为int类型if(port<0||port>65535){//判断端口是否在正常的范围内throw new InputMismatchException();}String ID = t3.getText();//获取ID码if(Integer.parseInt(ID)<0||Integer.parseInt(ID)>1023){//检测正常范围throw new InputMismatchException();}cc.connect(address,port,ID);//如果上面有任何异常,就会被下面捕获,而不会执行到此处的代码,因此,此处执行不会有其他异常}catch (NumberFormatException e){SendLogs("端口格式错误!");}catch (InputMismatchException e){SendLogs("端口不在0~65535正常范围内或者ID不在0~1023范围内");}}public void SendMessage(){String str=ta2.getText().replace("\n","\\n");//由于客户端和服务器每次交流消息分为两行内容,第一行为发送类型(消息,状态码,异常),第二行为内容,为避免用户输入多行信息造成的程序识别信息混乱,这里将用户输入的回车符转义为\ncc.SendData(Connection.Message,str);//将转义后的信息发送出去InsertMessage(true,ta2.getText());//并在聊天框中显示自己发送的内容ta2.setText("");//清除输入框的内容}public void AcceptMessage(String s){String S=s.replace("\\n","\n");//将发送信息时转义过的字符串\n重新转换为回车符InsertMessage(false,S);//在聊天框中显示对方发送的信息}public void InsertMessage(boolean IsMe,String s){//向消息面板插入新的消息if(!IsMe) {ta1.setText(ta1.getText() + "\n" + "对方:" + s);//如果是对方发送的消息,就在消息的前面标注是对方发送的}else{ta1.setText(ta1.getText() + "\n" + "我:" + s);//如果是自己发送的消息,就在消息的前面标注是自己发送的}}public void SendLogs(String s){//发送日志到面板的方法ta3.setText(ta3.getText()+"\n"+s);//设置日志框的内容为之前的内容,换行,再加上新的内容}
}
class Connection{Socket sk;//定义Socket变量skClientPanel cp;//储存cp变量,用于调用方法InputStream is;//储存输入流OutputStream os;//储存输出流BufferedWriter bw;//储存PrintWriter对象,用于发送文字信息BufferedReader br;//储存BufferedReader对象,用于接收文字信息AcceptThread at;//定义接收消息线程类public static final String Message = "M";//定义消息类型M,即下一行内容为发送的消息public static final String Status =  "S";//定义状态码,即下一行传输的内容不是消息,而是连接状态码public static final String Exception = "E";//定义异常码,即下一行返回服务器遇到的异常public static final String StatusID= "ID";//ID标志,其后附加自身的ID一并发送过去public static final String StatusSUCCESS="SU";//服务器返回的状态码,连接成功的标志public static final String StatusPARTYDISCONNECT="PD";//服务器返回的状态码,对方断开连接的标志public static final String StatusPARTYCONNECT="PC";//服务器返回的状态码,对方成功连接并处在同一频道内的标志public Connection(ClientPanel cp){this.cp=cp;//接收并储存对象}public void connect(String address,int port,String ID){//连接按钮所执行的操作try{sk=new Socket(address,port);//建立一个新的连接is=sk.getInputStream();//获取输入流os=sk.getOutputStream();//获取输出流bw =new BufferedWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8));//获取PrintWriter对象br=new BufferedReader(new InputStreamReader(is,  StandardCharsets.UTF_8));//获取BufferedReader对象SendData(Status,StatusID+ID);//向服务器发送自己设定的ID码at=new AcceptThread(this);//开始新的线程接收消息}catch (Exception e){String s=e.getMessage();if (s.equals("Connection refused: connect")) {cp.SendLogs("无法连接至服务器,请确认服务器是否开启,请稍后再试");}else if(s.equals("Connection timed out: connect")){cp.SendLogs("无法连接至服务器,请确认服务器是否开启,请稍后再试");} else{cp.SendLogs("其他错误原因:"+e);}}}public void SendData(String Type, String Content){try {bw.write(Type + "\n");//发送第一行内容为类型bw.write(Content + "\n");//第二行为内容bw.flush();//发送信息}catch (Exception e){cp.SendLogs("发生错误:"+e.getMessage());}}public void AcceptMessage(String s){cp.AcceptMessage(s);}public void AcceptStatus(String s){//将状态信息显示在窗口标题以及日志栏中switch (s) {case StatusSUCCESS:cp.SendLogs("连接服务器成功");cp.cf.setTitle("聊天程序-已连接至服务器-对方未在线");break;case StatusPARTYDISCONNECT:cp.SendLogs("对方已断开连接");cp.cf.setTitle("聊天程序-已连接至服务器-对方未在线");break;case StatusPARTYCONNECT:cp.SendLogs("对方已连接");cp.cf.setTitle("聊天程序-已连接至服务器-对方在线");break;}}public void AcceptException(String s){cp.SendLogs("服务器异常:"+s);}
}
class AcceptThread extends Thread{Connection cc;//记录Connection对象,用于调用方法public AcceptThread(Connection cc){this.cc=cc;//接受并赋值Connection对象start();//开始一个新的线程,从run()开始执行}@Overridepublic void run() {while(true){//无限循环接收消息if(cc.sk!=null) {//如果未连接服务器,那么忽略接收消息的代码try {String s = cc.br.readLine();//读取第一行信息if (s != null) {//避免读取为空产生异常switch (s) {case Connection.Status: {//如果是状态码String ss = cc.br.readLine();//读取下一行while (ss == null) {//保证读取的有效行ss = cc.br.readLine();}cc.AcceptStatus(ss);//处理状态码break;}case Connection.Message: {//如果接收的是消息类型String ss = cc.br.readLine();//读取下一行while (ss == null) {ss = cc.br.readLine();}cc.AcceptMessage(ss);//接收消息break;}case Connection.Exception: {//如果接收的是异常String ss = cc.br.readLine();while (ss == null) {ss = cc.br.readLine();}cc.AcceptException(ss);//那么将服务器的异常信息打印在日志框内break;}}}} catch (IOException e) {if(e.getMessage().equals("Connection reset")){cc.cp.SendLogs("服务器连接已断开,可能原因:服务器已关闭");}else{System.out.println("IO错误:" + e);}break;}}}}
}

程序完成截图1:利用本地电脑作为服务器,在同一台电脑上运行两个客户端

(左侧用户已登录)

(当右侧用户登录时,两个用户马上各自受到对方在线的消息)

(发送信息)

(发送多行信息)

(右侧用户退出)

程序完成截图2:

利用公网服务器在两台任意电脑上运行客户端通信:

执行服务器程序

在两台电脑上运行客户端:

客户端1:

(用户1连接成功)

用户2:

(用户2连接成功,用户1和用户2建立连接)

(注:图片是之后添加进去的,源代码会存在中文乱码的问题,不过,只需要把所有的PrintWriter替换为BufferWriter并使用UTF-8编码即可解决)

如果本文对你有用,请帮忙点赞,让更多人看到,下方链接获取完整的资源(Java源代码,Class文件,Jar文件),制作不易,请多多支持,谢谢

https://download.csdn.net/download/A3010367369/85202654

利用Java GUI,结合Java Socket,Java多线程,服务器,制作一个简单的具有界面的多用户实时聊天程序(从GUI,到Socket,到多线程,到服务器,项目级别详细教程)相关推荐

  1. 如何用Java设计一个简单的窗口界面(学习中.1)

    如何用Java设计一个简单的窗口界面 一.前言 二.简单了解 1.Swing简介 2.框架(frame) 3.层次 三.步骤 1.打开eclipse,依次创建项目,包,类. 2.代码 2.1最简单的可 ...

  2. java qq ui界面_java swing 创建一个简单的QQ界面教程

    记录自己用java swing做的第一个简易界面. LoginAction.java package com.QQUI0819; import javax.swing.*; import java.a ...

  3. java计算课程学分绩点,一个简单的计算选修课程绩点的程序,欢迎大家指点下.

    一个简单的计算选修课程绩点的程序,欢迎大家指点下. package 选修课程; /** * * @author Administrator */ public class Student {    / ...

  4. 用计算机怎么做成绩表,利用Excel制作一个简单的学生成绩表.doc

    利用Excel制作一个简单的学生成绩表 教学设计表 学科 信息技术 授课年级 八年级 学校 教师姓名 章节名称初中信息技术八年级上册第7课(第1节)计划学时1学时学习内容分析学习Excel的基础知识, ...

  5. 如何利用laragon框架制作一个简单的应用?

    如何利用laragon框架制作一个简单的应用? 一.搭建环境 1. 安装Laragon 1.1 打开安装包用的语言 选择自己习惯用的语言 1.2 选择安装地址 1.3 选择Next,开始install ...

  6. 利用EasyDL制作一个简单的图片识别小项目

    主要是利用EasyDL制作一个简单的傻瓜式猫狗图片识别,利用EasyDL,只需要几步简单的点击即可 *主要的步骤: 1.准备数据 2.训练模型 3.部署 4.H5 * 1.首先创建两个文件夹cat和d ...

  7. pr如何跳到关键帧_PR教程 如何利用pr制作一个简单的动画

    Adobe Premiere Pro简称PR,是一款非常不错的视频制作编辑软件,如何如何利用pr制作一个简单的动画,这里小编为大家带来了pr动画制作教程--利用pr制作动画,一起来学习一下吧! Ado ...

  8. 【Java】Java GUI制作Windows桌面程序,利用windowbuilder生成界面,使用exe4j打包成可执行文件,使用Inno Setup打包成安装包,超级详细教程

    目录 1.GUI插件 1.1 下载GUI绘制插件 1.2 eclipse中配置windowbuilder插件 2.绘制GUI界面 2.1 建立一个GUI的项目 3.配置Maven项目 3.1新建一个M ...

  9. 【Java】远程调用、线程池手写一个简单服务器

    " 关键字:远程调用.序列化.反序列化.反射.动态代理.客户端.服务端.线程池 > 思考题:带着这几个问题可以先思考,然后看完文章再去理解,也可以在评论区讨论喔~ 反射和动态代理关系和 ...

最新文章

  1. int、bigint、smallint 和 tinyint
  2. mxnet基础到提高(7)--卷积神经网络基础(2)
  3. RedHat7.0启动后黑屏
  4. MySQL排序ORDER BY与分页LIMIT,SQL,减少数据表的网络传输量,完整详细可收藏
  5. python db api_dbapi · PyPI
  6. linux命令界面输入不了密码,如何在 Linux 中不输入密码运行 sudo 命令
  7. Confluence 6 配置备份
  8. Chrome辅助工具-JSONView
  9. java回调函数机制
  10. (时间表达式)定时任务Quartz 之 cron表达式及在线生成器
  11. 利用python进行身份证号码大全_身份证号码设置显示格式,我用了最笨的办法,你有什么好办法吗?...
  12. 计算机文本自定义,自定义文本编辑器
  13. 约翰·亨尼斯(John Hennessy)—斯坦福大学-美国斯坦福大学校长介绍
  14. cql oracle,Cassandra CQL中的Where和Order By子句
  15. 【航天物流组参赛ReadMe.md】
  16. python能用来制作游戏吗_python 做游戏开发怎么样?
  17. RK3588 VOP-SPLIT分屏模式介绍
  18. [Ubuntu]使用DataDog集成跟踪Django项目
  19. [转帖]房博士教你购房(六)
  20. 物 理 学 简 介(一)

热门文章

  1. 浅谈SAVI技术之基本特性
  2. php updatexml,php操作XML增删查改
  3. 华为p9连接计算机,华为手机怎么连接电脑数据传输照片(1分钟教会你步骤非常简单)...
  4. 小程序设置字体大小和字体加粗
  5. Python | 多态
  6. Nginx日志配置远程Syslog采集
  7. mysql连接查询 内连接查询 外连接查询
  8. CAD软件大神教你修改线型和线宽的方法
  9. 【91xcz】分享:安卓存在各种安全漏洞和恶意软件
  10. 风控模型-风险预警模型