我们在写一个聊天室的小项目时候,需要有客户端和服务器端;并且他们必须分开开发,不能有任何互相调用的语句出现!(并且运行的时候先运行服务端(只能一个),然后可以运行多个客户端)
一般我们先写好服务端:
package 聊天室服务端; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; public class MyServer { public static List<Cthread> list=new ArrayList<Cthread>(); public static void main(String[] args) { try { ServerSocket serversocket=new ServerSocket(9090);//创建一个服务端,端口号待会客户端连接要用 System.out.println("服务端已经打开!等待连接...."); while(true){//这里是一个无限循环,不停的等待着客户端来连接 Socket socket=serversocket.accept();//这是一个堵塞方法,没人连接就不会往下面运行 Cthread ct = new Cthread(socket);//每连上一个客户端,我就给他创建一个线程,专门去接收它的信息,以及广播给其他人 ct.start(); list.add(ct);//把线程都添加到一个列表,方便服务端管理 } } catch (Exception e) { e.printStackTrace(); } } }
服务端还需要接收消息,还有广播消息的功能,所以没当连上一个客户端的时候,我们就给新建一个线程来专门接收它的消息,以及广播给其他人(客户端不与其他客户端有联系,所有信息发送给服务端,然后由它广播)
package 聊天室服务端; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import java.net.SocketAddress; /** * ,专门去接收它的信息,以及广播给其他人 * @author yan * */ public class Cthread extends Thread{ Socket socket; private BufferedWriter bout; private SocketAddress addr; public Cthread(Socket socket){ this.socket=socket; } public void run(){ // 获得客户端地址 addr = socket.getRemoteSocketAddress(); System.out.println("连了一个客户端:" + addr); // 通过Socket对象获得IO流 try { OutputStream ops = socket.getOutputStream(); InputStream ips = socket.getInputStream(); // 包装成缓冲字符流 OutputStreamWriter osr = new OutputStreamWriter(ops); bout = new BufferedWriter(osr); InputStreamReader isr = new InputStreamReader(ips); BufferedReader br = new BufferedReader(isr); String msg = "欢迎光临!\r\n"; bout.write(msg); bout.flush(); while (true) { // 服务端读取客户端发来的消息 // 读取一行数据,读取到换行回车就不读取了 String str = br.readLine(); System.out.println( str); // 收到一个消息,就将消息广播给其它socket for (int i = 0; i < MyServer.list.size(); i++) { Cthread ct = MyServer.list.get(i); ct.sendMsg(str + "\r\n"); } } } catch (Exception e) { System.out.println("客户端已经断开连接!"); } } // 通过socket给客户端发送消息 public void sendMsg(String msg) throws IOException { this.bout.write(msg); this.bout.flush(); } }
服务端以及完成
----------------------------------------------------------------------------------------------
接下来便是客户端了:
Client类里面主要包含界面构建,事件的触发
连接服务端的语句:
socket = new Socket("127.0.0.1", 9090);//利用IP地址和端口号,来连接正在运行的服务器
package 聊天室客户端; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.net.Socket; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.JTextPane; public class Client extends JFrame implements ActionListener{ private Socket socket; private BufferedWriter bout; private BufferedReader br; private JTextPane pane; private JTextPane area; private static JTextField jt; private JButton btn1; private static JTextPane pane3;//用来显示在线人数,所以让其他对象都公用(目前没有做完这个功能) private boolean line=true; public static void main(String[] args) { Client c = new Client(); JOptionPane.showMessageDialog(null, "请先设置昵称!"); //如果不设置昵称,那么对话时就是显示的IP地址 } public Client() { this.setTitle("聊天室"); this.setBounds(200, 100, 800, 500); this.setDefaultCloseOperation(3); this.setLayout(null); // 显示接受的消息内容 pane = new JTextPane(); pane.setEditable(false); JScrollPane jsp = new JScrollPane(pane); jsp.setBounds(10, 10, 550, 300); jsp.setAutoscrolls(true); this.add(jsp); // 要发送的内容 area = new JTextPane(); JScrollPane jsp2 = new JScrollPane(area); jsp2.setBounds(10, 320, 440, 130); this.add(jsp2); JLabel label = new JLabel(); label.setText("请设置昵称:"); label.setBounds(460, 310, 100, 30); this.add(label); jt = new JTextField(); jt.setText(""); jt.setBounds(460, 340, 100, 30); this.add(jt); btn1 = new JButton("连接"); btn1.setBounds(460, 380, 100, 30); btn1.addActionListener(this); this.add(btn1); JButton btn2 = new JButton("发送"); btn2.setBounds(460, 420, 100, 30); btn2.addActionListener(this); this.add(btn2); // 显示在线人(还没有完成) JLabel label3 = new JLabel(); label3.setText("在线情况:"); label3.setBounds(580, 0, 100, 30); this.add(label3); pane3 = new JTextPane(); pane3.setEditable(false); JScrollPane jsp3 = new JScrollPane(pane3); jsp3.setBounds(580, 30,200, 420); jsp3.setAutoscrolls(true); this.add(jsp3); this.setResizable(false); this.setVisible(true); } public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); if (command.equals("连接")) { try { // 和服务器建立连接 socket = new Socket("127.0.0.1", 9090);//利用IP地址和端口号,来连接正在运行的服务器 OutputStream ops = socket.getOutputStream();//通过socket对象获得输出流 // 包装成缓冲字符流 OutputStreamWriter osr = new OutputStreamWriter(ops); bout = new BufferedWriter(osr); InputStream ips = socket.getInputStream(); InputStreamReader isr = new InputStreamReader(ips); br = new BufferedReader(isr); // 创建一个接受消息的线程,用来接受服务端发来的消息(把需要用到的参数传过去) ReceiveThread rt = new ReceiveThread(br, pane,jt,pane3); rt.start(); JOptionPane.showMessageDialog(null, "连接成功"); btn1.setText("已连接"); btn1.setEnabled(false);//设置点击连接后,连接按钮显示为不可以点击 // bout.write(jt.getText()+".929144493");//把名字发出去 } catch (Exception ef) { JOptionPane.showMessageDialog(null, "连接失败"); } } else if (command.equals("发送")) { // 获得输入框中输入的消息内容 String msg = area.getText(); if (msg == null || msg.equals("")) { JOptionPane.showMessageDialog(null, "必须输入消息内容!"); } else { try { if(!jt.getText().equals("")){ //获取名字和需要发送的信息,一起发送出去 bout.write(jt.getText()+" say:"+msg + "\r\n"); }else{ //没有名字的话,就获取IP地址和需要发送的信息,一起发送出去 bout.write(socket.getLocalAddress()+" 说:"+msg + "\r\n"); } bout.flush(); area.setText("");//清空发送框 } catch (Exception ef) { ef.printStackTrace(); } } } } }
当我们连接上服务端的时候,我们就新建一个线程类来接收服务端发来的消息(也就是服务端广播的消息),并且我们利用这个线程类来改变我们聊天界面的文字显示;
package 聊天室客户端; import java.io.BufferedReader; import javax.swing.JTextField; import javax.swing.JTextPane; /** * 客户端接收消息的线程 * * @author kowloon * */ public class ReceiveThread extends Thread { private BufferedReader br; private JTextPane pane;// 显示消息的文本框 private JTextField jt; private JTextPane pane3; public ReceiveThread(BufferedReader br, JTextPane pane,JTextField jt,JTextPane pane3) { this.br = br; this.pane = pane; this.jt=jt; this.pane3=pane3; } @Override public void run() { try { while (true) {//只要一创建,就一直在运行 // 读取到一个字符串 String msg = br.readLine() +"\r\n";//读取服务器广播过来的信息 // int i=msg.indexOf("."); // String panduan=msg.substring(i,i+10); // if(panduan.equals(".929144493")){ // //传名字 // String name=msg.substring(0,i); // pane3.setText(pane3.getText()+name+"\r\n"); // System.out.println("名字收到!"); // }else{//信息 String text = pane.getText();//获得我自己的聊天框里面的信息 pane.setText(text + msg);//把信息添加到聊天框里面,不需要换行符,因为读取数据的时候已经有了换行符 // System.out.println("信息收到"); // } } } catch (Exception ef) { ef.printStackTrace(); } } }
下面还有一些运行时候的图片,还有所有完整的项目压缩包!