1.1 主程序入口
在主程序入口处,通过设置MyWindow的第一个参数,如果为true则为服务器,如果为false,则为客户端,当然也可以设置第二个参数,区分客户端和服务器的窗口标题。
public class JavaMain {public static void main(String[] args) {MyWindow w=new MyWindow(false,"QQ聊天"); //运行时将false改成true, 先启动服务端,然后再改成false启动客户端w.setNet("192.168.1.103", 12345);} }
1.2 界面程序
界面程序根据主程序传来的参数不同而创建客户端和服务器窗口,根据界面的构造函数中第一个参数,isServer设置服务器窗体或者是客户端窗体。
public class MyWindow extends JFrame {private static final long serialVersionUID = 1L;// 定义一个成员变量Client myClient = null;Server myServer=null;JTextArea area=null;// 设置默认的IP地址和端口private String ipAddress="127.0.0.1";private int nPort=50000;private boolean isServer=false;// 构造函数public MyWindow(boolean isServer,String title) {this.isServer=isServer;setTitle(title);setSize(300, 400);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);this.setLayout(null);initComponents();setVisible(true);}// 为外界提供设置IP地址和端口的方法public void setNet(String ip,int port){ipAddress=ip;nPort=port;// 对通信接口初始化 initCommication();}// 通过构造函数,将端口和文本显示区传递给myServer对象public void initCommication(){if (isServer) {myServer=new Server(nPort,area);}else{myClient=new Client(ipAddress,nPort,area);}}public void initComponents() {// 添加一个文本区area = new JTextArea();area.setBounds(10, 20, 260, 200);add(area);// 添加一个文本框final JTextField text = new JTextField();text.setBounds(10, 240, 260, 30);add(text);// 添加一个发送按钮JButton button = new JButton("发送");button.setBounds(10, 290, 80, 30);button.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {if (isServer) {myServer.sendServerMsg(text.getText());}else{myClient.sendMsg(text.getText());}}});add(button);} }
1.3 服务器类
服务器类比较复杂,这里用到了内部类,内部类的好处就是能随时访问外部类的成员和方法,而无需通过传参数的方法达到目的。
// 监听主线程 public class Server extends Thread {// 服务器Socketprivate ServerSocket serverSocket = null;private ArrayList<ServerThread> clientList = null;// 显示区private JTextArea jTextArea = null;// 构造函数public Server(int port, JTextArea area) {jTextArea = area;try {// 开始绑定端口serverSocket = new ServerSocket(port);// 初始化客户端连接的列表clientList = new ArrayList<ServerThread>();} catch (IOException e) {System.err.println("服务器端口初始化失败!\n");}jTextArea.setText("服务器成功启动,等待客户连接!\n");start(); // 启动线程 }public void sendServerMsg(String msg) {jTextArea.append(msg + "\n");for (int i = clientList.size() - 1; i >= 0; i--) {clientList.get(i).getWriter().println("服务器:" + msg);clientList.get(i).getWriter().flush();}}// 线程程序public void run() {while (true) {try {// 用阻塞的方式,等待用户连接请求Socket socket = serverSocket.accept();// 启动一条为客户端服务的线程ServerThread svthread = new ServerThread(socket);svthread.start();// 将该客户加入列表中 clientList.add(svthread);} catch (IOException e) {e.printStackTrace();}}}// 服务线程(内部类),用于处理客户端的服务线程class ServerThread extends Thread {// 当前正在连接的SocketSocket socket = null;// 当前连接的Socket的输入和输出流(数据出入口)private PrintWriter writer = null;private BufferedReader reader = null;// 构造函数public ServerThread(Socket s) {socket = s;try {// 获取输入输出流reader = new BufferedReader(new InputStreamReader(s.getInputStream()));writer = new PrintWriter(s.getOutputStream());// 在此可以写接收用户端的信息,解析出来(IP地址) } catch (Exception e) {e.printStackTrace();}}// 获得输入流,供外界调用public BufferedReader getReader() {return reader;}// 获得输出流,供外界调用public PrintWriter getWriter() {return writer;}// 获得socketpublic Socket getSocket() {return socket;}// 线程服务程序public void run() {// 创建一个变量,用于接收客户端发来的信息String message = null;while (true) {try {// 读取输入流message = reader.readLine();// 如果是下线命令if (message.equals("Bye")) {// 在客户端列表上删除该用户ServerThread temp = null;for (int i = clientList.size() - 1; i >= 0; i--) {temp = clientList.get(i);if (temp.getSocket().equals(socket)) {clientList.remove(i);}temp.stop();}// 断开连接释放资源 reader.close();writer.close();socket.close();return;} else {// 在文本区显示该消息jTextArea.append(message + "\n");// 将该消息广播给其他用户 broadcastMsg(message);}} catch (Exception e) {// TODO: handle exception }}}// 广播消息public void broadcastMsg(String msg) {for (int i = clientList.size() - 1; i >= 0; i--) {clientList.get(i).getWriter().println(msg);clientList.get(i).getWriter().flush();}}} }
1.4 客户端类
客户端类比较简单,只要创建一个线程,与服务器连接即可。
public class Client extends Thread {// 写该类的成员变量private Socket socket=null;private PrintWriter out=null;private BufferedReader in=null;private JTextArea area;// 写构造函数,完成初始化public Client(String ip,int nPort,JTextArea area){try {socket=new Socket(ip, nPort);} catch (UnknownHostException e) {System.err.println("初始化失败!");} catch (IOException e) {System.err.println("初始化失败!");}start(); // 启动线程,让线程开始工作this.area=area;this.area.setText("初始化成功,连接上服务器!\n");}// 发送消息public void sendMsg(String msg){if(socket.isConnected()==true){out.println("客户端:"+msg);out.flush();// 将消息推送给客户端 }}public void run(){while(true){try {// 获取客户端的输入流InputStream is = socket.getInputStream();InputStreamReader isr = new InputStreamReader(is);in = new BufferedReader(isr);// 获取客户端的输出流out = new PrintWriter(socket.getOutputStream(), true);while(true){// 不停的读取从服务器发来的信息String info = in.readLine();area.append(info + "\n");}} catch (Exception e) {System.err.println("发生数据流读取错误,程序退出!");System.exit(1);}finally{try {// 结束,扫尾工作 out.close();in.close();socket.close();} catch (Exception e2) {}}}} }
1.5 运行结果
运行结果如下所示: