基于Socket,java简单群聊功能

java简单群聊功能

​ 使用了Socket,swing,线程。

​ 原本是只准备写两人间的通信,是后来才想改成这样的,所以代码有点乱。

服务器类(Server)

有个set集合,当服务器群发消息时就要遍历它。

public class Server_ZZ {
	public static void main(String[] args) throws IOException {
		ServerSocket ss87 = new ServerSocket(10087);
        //用来存储连接的Socket
		HashSet<Socket> li=new HashSet<>();	
		//服务器界面
		Swing_Server sw=new Swing_Server();
		
		//不停的接受Socket,将其放入一个集合里面
		while(true){
			Socket s87 = ss87.accept();
			li.add(s87);
			sw.addSocket(s87, li);
		}
	}
}

服务器界面(Swing_Server)

发送消息的功能是在这个类中完成的,通过触发button的点击事件,来发送消息。用线程来接收消息

发送消息的流程为:

​ 通过Socket得到OutputStream os;

​ os.write()//写入你要发送的消息,要转换成byte[]型;

接受消息:

​ 因为消息是一直要接收的,如果不用线程则进行不了其他操作。RecepteThread类

public class Swing_Server extends JFrame {
	HashSet<Socket> li;
	Socket s;
	OutputStream os;
	String ip;//自己的ip地址
	String cip;//客户端的IP地址
	JTextArea ja1;
	
	
	public void addSocket(Socket soc,HashSet<Socket> li) throws IOException{
		s=soc;//当前集合通信
		this.li=li;//socket通信集合
		
		os= s.getOutputStream();//socket中的输出流		
		ip=InetAddress.getLocalHost().toString();//拿到自己的ip地址
		cip=s.getInetAddress().getHostAddress();//拿到对方的ip地址
		System.out.println(ip+"自己的ip地址");
		System.out.println(cip+"客户端的ip地址");
		
		setTitle("本机主机名/ip地址:"+ip);// 窗体标题
		
		// 当读到消息就把消息放入文本域,如果不用线程就会在这里一直执行
		RecepteThread rt = new ServerRecepteThread(s, this.ja1,this.li);
		new Thread(rt).start();
	}	
	public Swing_Server() throws IOException {
		setDefaultCloseOperation(EXIT_ON_CLOSE);// 关闭窗口时关闭程序
		//setTitle("本机主机名/ip地址:"+ip);// 窗体标题
		setResizable(false);// 禁止改变窗体大小

		setBounds(500, 100, 0, 0);// 窗体坐标
		setSize(700, 550);// 窗体大小
		setVisible(true);// 窗体可见
		setBackground(Color.white);

		Container c = getContentPane();// 拿到窗体的容器
		c.setLayout(new FlowLayout(FlowLayout.LEFT));

		// 消息文本域
		ja1 = new JTextArea();
		ja1.setEditable(false);// 不可编辑
		ja1.setSize(300, 500);// 文本框大小

		ja1.setFont(new Font("楷体", Font.PLAIN, 18));// 字体
		ja1.setLineWrap(true);// 自动换行
		ja1.setBackground(Color.WHITE);
		ja1.setRows(17);
		ja1.setColumns(67);
		
		// 发送消息文本域
		JTextArea ja2 = new JTextArea();
		ja2.setSize(100, 500);// 文本框大小
		ja2.setFont(new Font("楷体", Font.PLAIN, 16));
		ja2.setRows(7);// 行数
		ja2.setColumns(75);// 列数

		// 发送按钮
		JButton jb = new JButton();
		jb.setText("发送");
		
		//监听事件 当按下发送键时 发送消息
		jb.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				msg(ja1,ip,ja2.getText());// 在自己的屏幕上显示自己发的消息
				//写入流
				try {
					// 服务器自己发送的消息全部发一遍
					for (Socket so : li) {
						os = so.getOutputStream();
                        //将ip地址和消息内容用“&”隔开,这意味发送的消息里不能&
						String ipmsg=ip+"&"+ja2.getText();
						os.write(ipmsg.getBytes());
					}
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				ja2.setText("");// 清空文本
			}
		});
		// 将组件加入窗体
		c.add(ja1);
		c.add(ja2);
		c.add(jb);
		// 刷新
		c.validate();
	}
	// 在自己的屏幕上显示自己发的消息
	public static void msg(JTextArea j,String ip,String msg) {
		String str = "主机名/ip地址:" +ip+ "\n" + msg + "\n";
		j.append(str);
	}
}

服务器接收消息线程类(ServerRecepteThread)

这个类是继承的RecepteThread,RecepteThread就是我的客户端接收消息线程类,我重写了run方法。我这种写法是不对的,我开头也讲了,临时改的,就偷了点懒,这里应该用接口可能会更好点。

public class ServerRecepteThread extends RecepteThread implements Runnable {
	OutputStream os;
	HashSet<Socket> li;

	ServerRecepteThread(Socket s, JTextArea ja1, HashSet<Socket> li) throws IOException {
		super(s, ja1);
		this.li = li;
		this.os = s.getOutputStream();
	}

	@Override
	public void run() {
		try {
			int len;
			byte[] bys = new byte[1024];
			bi = s.getInputStream();
			String read;
			while (true) {
				// 接受消息
				len = bi.read(bys);
				read = new String(bys, 0, len);
				// 写入消息文本域ja1
				ja1.append("ip地址:" + cip + "\n" + read + "\n");

				// 服务器接收到客户端消息,就除该客户端都发送一遍
				for (Socket so : li) {
					if (!so.equals(s)) {
						os = so.getOutputStream();
						String reads = new String(cip + "&" + read);
						os.write(reads.getBytes());
					}
				}

				// 如果接受到“gg” 就结束
				if (read.equals("gg")) {
					s.close();
					break;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

(客户端)接收消息线程类(RecepteThread)

public class RecepteThread implements Runnable {
	Socket s;
	JTextArea ja1;
	String cip;
	InputStream bi;

	 socket 显示文本框
	RecepteThread(Socket s, JTextArea ja1) {
		this.s = s;
		this.ja1 = ja1;
		// 对方的ip地址
		this.cip = s.getInetAddress().getHostAddress();
	}

	@Override
	public void run() {
		try {
			int len;
			byte[] bys = new byte[1024];
			bi = s.getInputStream();
			String read;
			while (true) {
				// 接受消息
				len = bi.read(bys);
				read = new String(bys, 0, len);
				// 用&分割
				String[] reads = read.split("&");
				// 写入消息文本域ja1
				ja1.append("ip地址:" + reads[0] + "\n" + reads[1] + "\n");

				// 如果接受到“gg” 就结束
				if (read.equals("gg")) {
					s.close();
					break;
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

客户端类(Client)

public class Client {

	public static void main(String[] args) throws UnknownHostException, IOException {
		Socket s87 = new Socket(InetAddress.getByName(/*服务器的IP地址 字符串*/), 10087); 
		System.out.println("Link start");
		new Swing_Client(s87);
	}
}

客户端界面(Swing_Client)

这个类其实应该和服务端界面类,抽象后继承同一个父类,因为这两者有很多相同的代码,仅接收消息和发送消息的操作不同,界面设计是一样的。

public class Swing_Client extends JFrame {
	
	public Swing_Client(Socket soc) throws IOException {
		Socket s=soc;//当前集合通信
		
		OutputStream os= s.getOutputStream();//socket中的输出流
		
		String ip=InetAddress.getLocalHost().toString();//拿到自己的ip地址
		String cip=s.getInetAddress().getHostAddress();//拿到对方的ip地址
		System.out.println(ip+"自己的ip地址");
		System.out.println(cip+"对方的ip地址");
		
		setDefaultCloseOperation(EXIT_ON_CLOSE);// 关闭窗口时关闭程序
		setTitle("本机主机名/ip地址:"+ip);// 窗体标题
		setResizable(false);// 禁止改变窗体大小

		setBounds(500, 100, 0, 0);// 窗体坐标
		setSize(700, 550);// 窗体大小
		setVisible(true);// 窗体可见
		setBackground(Color.white);

		Container c = getContentPane();// 拿到窗体的容器
		c.setLayout(new FlowLayout(FlowLayout.LEFT));

		// 消息文本域
		JTextArea ja1 = new JTextArea();
		ja1.setEditable(false);// 不可编辑
		ja1.setSize(300, 500);// 文本框大小

		ja1.setFont(new Font("楷体", Font.PLAIN, 18));// 字体
		ja1.setLineWrap(true);// 自动换行
		ja1.setBackground(Color.WHITE);
		ja1.setRows(17);
		ja1.setColumns(67);
		
		//当读到消息就把消息放入文本域,如果不用线程就会在这里一直执行
		RecepteThread rt=new RecepteThread(s,ja1);
		new Thread(rt).start();
		
		// 发送消息文本域
		JTextArea ja2 = new JTextArea();
		ja2.setSize(100, 500);// 文本框大小
		ja2.setFont(new Font("楷体", Font.PLAIN, 16));
		ja2.setRows(7);// 行数
		ja2.setColumns(75);// 列数

		// 发送按钮
		JButton jb = new JButton();
		jb.setText("发送");
		
		//监听事件 当按下发送键时 write入流
		jb.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				msg(ja1,ip,ja2.getText());// 发送消息
				//写入流
				try {
					os.write(ja2.getText().getBytes());
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				
				ja2.setText("");// 清空文本
			}
		});

		// 将组件加入窗体
		c.add(ja1);
		c.add(ja2);
		c.add(jb);

		// 刷新
		c.validate();
	}
	
	public static void msg(JTextArea j,String ip,String msg) {
		String str = "主机名/ip地址:" +ip+ "\n" + msg + "\n";
		j.append(str);
	}
}

效果图:

基于Socket,java简单群聊功能_第1张图片

带有主机名就是自己发的消息。服务器和客户端页面一样。要先开服务器,再开客户端。

总结:

代码有很多需要优化的地方,比如我上面提到的代码的复用,不能发送&,以及关闭窗口不能同时关闭线程问题,所以通过关闭窗口来下线再上线会报错。还有可以增加的功能:比如自己发的消息右对齐,别人的左对齐;右边空出来的可以做成 上线人列表,可以发起单独聊天等。下次在改! 下次!下次!咕咕咕

你可能感兴趣的:(随手写的小应用)