java 实现群聊私聊功能(网络编程+io流) 封装终极版

首先明确一下目的:实现一个服务端加多个客户端可用,带有群聊和私聊功能的小项目(通过控制台输入);

服务端

服务端起到了转发的作用,一个client通过发送消息给服务端,服务端接受到消息之后判断是要群发还是私发(私发有格式),然后将消息发送给所有在线的客户端;

明确了功能咱们来分析下,服务端是用来群发的,群发给谁?所有在线的client,那么这些client是需要上线就存储,下线就移除的,所以肯定是需要容器的,并且这个容器还是能够支持多线程等功能,我们选择的是CopyOnwriteArrayList那么服务端的功能代码如下:

public class NewServe {
	static CopyOnWriteArrayList<Channel> array = new CopyOnWriteArrayList(); //一个静态的全局变量,方便使用,用来存放开启的客户端的信息;
	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(8888); //端口号是8888
		System.out.println("----------Server------------");
		while(true){
			Socket client = server.accept();
			Channel channel = new Channel(client);
			array.add(channel);
			new Thread(channel).start();
		}   //阻断式的接受就直接一个永真循环
	}
}
class Channel implements Runnable{
//channel封装了
	private String name = null;
	private boolean flag = true;
	private DataInputStream dis;
	private DataOutputStream dos;
	private Socket client;
	public Channel(Socket client) {
		this.client = client;
		try {
			dos = new DataOutputStream(client.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			dis = new DataInputStream(client.getInputStream());
			name = dis.readUTF();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	//接受消息,之所以定义就是封装一下,降低了run()方法里边的代码量;
	public String receive(){
		String msg = "";
		try {
			msg = dis.readUTF();
		} catch (IOException e) {
		//这里边捕获到异常之后做的事情,包括通知所有人还包括把这个关闭流等;
			sendAll("-----系统消息:"+name+"退出了群聊");
			reseve(dis);
			flag = false;
		}
		return msg;
	}
	//没用但是适合循序渐进的编程方式,先写出来这个send方法,之后。。。。自己发现了sendAll
	public void send(String msg){
		try {
			dos.writeUTF(msg);
			dos.flush();
		} catch (IOException e) {
			e.printStackTrace();
			reseve(dos);
			flag = false;
		}
	}
	//发送给所有人
	public void sendAll(String msg){
		if(msg.contains("@")) {
			String[] str1 = msg.split("@");
			for(Channel ch:NewServe.array){
				if(str1[0].equals(ch.name)) {
					ch.send("(来自私聊)"+name+":"+str1[1]);
				}//判断一下是不是用的是私聊格式的;
			}
		}else {
			for(Channel ch:NewServe.array){
				if(ch.client!=this.client){
					if(!ch.client.isClosed()) {
						ch.send(name+":"+msg);
					}else {
						NewServe.array.remove(ch); //知道有关着的客户端的时候立马把他从容器中移除;
					}
				}
			}
		}
	}
	//就是封装的一个能够关闭流的方法方便调用;
	public void reseve(Closeable... dis){
		for(Closeable d:dis){
			try {
				d.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	//因为是多线程,并且实现了Runnable接口所以必须重写这个方法的;
	@Override
	public void run() {
		while(flag){
			String msg = receive();
			sendAll(msg);
		}
	}
}

以上就是服务端的全部代码,总的就是,服务端接受到一个socket 就创建一个channel对象并传入socket,同时容器内加上这个channel对象;channel是一个实现了Runnable接口的类,里边有唯一的socket 作用就是如果接受到这个socket的发送信息就直接调用类里边的sendAll方法发送给所有人(通过判断发送给私人),每一个socket都有一个channel对象与之对应;

客户端

在写网络编程的时候我说过,如果想实现收发同步那必须是要用多线程的,所以毫无疑问,不仅是多线程,还有做到收一个线程,发一个线程,以下是封装后的代码:

发送端

public class Send1 implements Runnable{
	private String name = "";
	private DataOutputStream dos ;
	private boolean flag = true;
	private Socket client;
	static Scanner in = new Scanner(System.in);
	public Send1(Socket client){
		this.client = client;
		try {
			dos = new DataOutputStream(client.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	@Override
	public void run() {
		this.name = Thread.currentThread().getName();
		try {
			dos.writeUTF(name);
			dos.flush();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
		while(flag){
			String msg = in.next();
			try {
				dos.writeUTF(msg);
				dos.flush();
			} catch (IOException e) {
				e.printStackTrace();
				flag = false;
				reseve(dos);
			}
		}
	}
	public void reseve(Closeable... dis){
		for(Closeable d:dis){
			try {
				d.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

接受端

public class Receive1 implements Runnable{
	private DataInputStream dis ;
	private boolean flag = true;
	private Socket client;
	static Scanner in = new Scanner(System.in);
	public Receive1(Socket client){
		this.client = client;
		try {
			dis = new DataInputStream(client.getInputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	@Override
	public void run() {
		String msg = "";
		while(flag){
			try {
				msg = dis.readUTF();
				System.out.println(msg);
			} catch (IOException e) {
				e.printStackTrace();
				reseve(dis);
				flag = false;
			}
		}
	}
	public void reseve(Closeable... dis){
		for(Closeable d:dis){
			try {
				d.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

这两类都是实现了Runnable接口,并且两个类中封装的socket肯定是同一个(参考用客户端的时候怎么写);也就是稍微封装一下,两类的逻辑差不多,都是平平无奇的;
用客户端的时候长这样

public class Newclient {
	public static void main(String[] args) throws UnknownHostException, IOException {
		System.out.println("--------------Client-------------");
		Socket client = new Socket("localhost",8888);
		new Thread(new Send1(client),"李世翔").start();
		new Thread(new Receive1(client)).start();
	}
}

由于个人水平我也就能写到这个地方了,有好多地方能够改的,写出来仅供参考;

发布了4 篇原创文章 · 获赞 14 · 访问量 129

猜你喜欢

转载自blog.csdn.net/weixin_45127611/article/details/103948900