网络编程(2)—— 多客户端 现象的处理

在本人上一篇博文中,曾讲到了网络编程的基础知识,并且在最后,实现了一个服务器处理两个客户端的情况,那么,本人在这篇博文中来讲解一下 我们该如何处理多个客户端的情况

因为在上一篇博文中《网络编程(1)——基础知识讲解》中,本人讲解了有关 网络编程 的基本知识点,那么,在这篇博文中,本人就不再对于较为简单的代码段进行过多的讲解了。

那么,回归主题。

首先,本人来给出一个抽象类:

package com.mec.network.common;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public abstract class Communication implements Runnable {
	private Socket socket;
	private DataInputStream dis;
	private DataOutputStream dos;
	private volatile boolean goon;
	private String ip;

	public Communication(Socket socket) {	//带参构造中,我们主要是对各成员的赋初值
		this.socket = socket;
		this.ip = socket.getInetAddress().getHostAddress();	//获取客户端的拨号ip
		try {
			this.dis = new DataInputStream(socket.getInputStream());
			this.dos = new DataOutputStream(socket.getOutputStream());
			
			goon = true;
			new Thread(this).start();	//这里就用到了本人同专题之前的博文 《线程编程 小例》的知识,这一句是用来“启动”线程的
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public String getIp() {
		return ip;
	}

	public void send(String message) {	//这个方法的主要功能是“发送信息”
		try {
			dos.writeUTF(message);
		} catch (IOException e) {
			close();
		}
	}
	
	public abstract void dealNetMessage(String message);	//由于我们不知道掉用这个类的是服务器的类还是客户端的类,所以我们对于如何处理接收到的信息的操作还是未知的
	public abstract void peerAbnormalDrop();	//这个方法的功能是:处理对端异常掉线

	@Override
	public void run() {
		String message = null;
		while (goon) {
			// 侦听对端消息的发送
			try {
				message = dis.readUTF();
				dealNetMessage(message);
			} catch (IOException e) {
				if (goon == true) {
					// 处理异常掉线!
					peerAbnormalDrop();
				}
				close();	//这里调用close()的目的主要是:使得goon为false,从而结束循环
			}
		}
		close();	//这里调用close()的目的是:将dis、dos 和 socket都关闭掉,以保证调用这个类的服务器(或客户端)能够 正常下线
	}
	
	public void close() {
		goon = false;
		try {
			if (dis != null) {
				dis.close();
			}
		} catch (IOException e) {
		} finally {
			dis = null;
		}
		
		try {
			if (dos != null) {
				dos.close();
			}
		} catch (IOException e) {
		} finally {
			dos = null;
		}
		
		try {
			if (socket != null && !socket.isClosed()) {
				socket.close();
			}
		} catch (IOException e) {
		} finally {
			socket = null;
		}
	}
	
}

现在,本人再来给出两个接口来处理消息的传递:
首先是消息发送者的接口:

package com.mec.network.common;

public interface ISpeaker {
	void addListener(IListener listener);			// 增加客户端
	void removeListener(IListener listener);		// 删除客户端
}

下面则是消息接收者的接口:

package com.mec.network.common;

public interface IListener {
	void processMessage(String message);
}

那么,有了上一篇博文的经验,我们就再来编写一个处理配置的接口:

package com.mec.network.common;

public interface INetConfig {
	int DEFAULT_PORT = 54188;
}

本人现在来给出一个处理多客户端情况的服务器的类:

package com.mec.network.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import com.mec.network.common.Communication;
import com.mec.network.common.IListener;
import com.mec.network.common.INetConfig;
import com.mec.network.common.ISpeaker;

public class MulServer implements Runnable, ISpeaker {
	private int port;
	private ServerSocket server;
	private boolean goon;
	
	private List<IListener> listenerList;
	
	public MulServer() {
		this.listenerList = new ArrayList<>();
		this.port = INetConfig.DEFAULT_PORT;
	}
	
	public void startup() throws IOException {
		if (goon == true) {
			speakOut("服务器已启动!");
			return;
		}
		
		speakOut("启动服务器,请稍后……");
		server = new ServerSocket(port);
		speakOut("服务器启动成功!");
		goon = true;
		new Thread(this).start();
	}
	
	public void shutdown() {
		if (goon == false) {
			speakOut("服务器未启动!");
			return;
		}
		close();
		speakOut("服务器已宕机!");
	}
	
	public boolean isStartup() {
		return goon;
	}
	
	private void close() {
		goon = false;
		try {
			if (server != null && !server.isClosed()) {
				server.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			server = null;
		}
	}
	
	class InnerCommunication extends Communication {
		
		public InnerCommunication(Socket socket) {
			super(socket);
		}

		@Override
		public void dealNetMessage(String message) {
			System.out.println("来自客户端[" + getIp() + "]的消息:" + message);
			if (message.equalsIgnoreCase("byebye")) {
				speakOut("客户端[" + getIp() + "]下线!");
				close();
				return;
			}
			send("[" + message + "]");
		}

		@Override
		public void peerAbnormalDrop() {
			speakOut("客户端[" + getIp() + "]异常掉线!");
		}
	}

	@Override
	public void run() {
		speakOut("开始侦听客户端连接请求……");
		while (goon) {	//这里的设定是:当goon为true时,一直增添要侦听的客户端
			try {
				Socket socket = server.accept();
				String clientIp = socket.getInetAddress().getHostAddress();
				speakOut("接收到客户端[" + clientIp + "]连接请求!");	// 发送消息
				new InnerCommunication(socket);
			} catch (IOException e) {
				close();
			}
		}
		close();
	}
	
	private void speakOut(String message) {		// 给每一个“听众”(即:客户端)发送消息
		for (IListener listener : listenerList) {
			listener.processMessage(message);		// 实际上是调用每一个听众的必须实现的processMessage()
		}
	}

	@Override
	public void addListener(IListener listener) {	//这里是实现IListener这个接口所要完成的类
		if (listenerList.contains(listener)) {
			return;
		}
		listenerList.add(listener);
	}

	@Override
	public void removeListener(IListener listener) {	//这里是实现IListener这个接口所要完成的类
		if (!listenerList.contains(listener)) {
			return;
		}
		listenerList.remove(listener);
	}
	
}

现在,相应地,本人来给出以一个Test类:

package com.mec.network.server.test;

import java.io.IOException;
import java.util.Scanner;

import com.mec.network.common.IListener;
import com.mec.network.server.MulServer;

public class ServerTest implements IListener {

	public ServerTest() {
	}

	@Override
	public void processMessage(String message) {		// “阅读”消息
		System.out.println(message);
	}
	
	public static void main(String[] args) {
		MulServer server = new MulServer();
		server.addListener(new ServerTest());					// “订阅”:增加客户端
		Scanner in = new Scanner(System.in);
		String command = "";
		boolean finished = false;
		
		while (!finished) {
			command = in.next();
			if (command.equalsIgnoreCase("startup")) {	//本人设置:当在命令行输入字符串"startup"后,开始创建服务器
				try {
					server.startup();
				} catch (IOException e) {
					e.printStackTrace();
				}
			} else if (command.equalsIgnoreCase("shutdown")) {	//本人设置:当在命令行输入字符串"shutdown"后,开始关闭服务器
				server.shutdown();
			} else if (command.equalsIgnoreCase("exit")) {	//本人设置:当在命令行输入字符串"shutdown"后,退出程序
				if (!server.isStartup()) {
					finished = true;
				} else {
					System.out.println("服务器尚未宕机!");
				}
			}
		}
		
		in.close();
	}

}

现在,本人来给出客户端的类:

package com.mec.network.client;

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

import com.mec.network.common.Communication;
import com.mec.network.common.INetConfig;

public class MulClient {
	private String ip;
	private int port;
	private Socket socket;
	private Communication communication;
	
	public MulClient() {
		this.ip = "localhost";
		this.port = INetConfig.DEFAULT_PORT;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public void setPort(int port) {
		this.port = port;
	}
	
	public void send(String message) {
		if (communication == null) {
			return;
		}
		communication.send(message);
	}
	
	public void close() {
		if (communication == null) {
			return;
		}
		communication.close();
	}
	
	public void connectToServer() throws UnknownHostException, IOException {
		this.socket = new Socket(ip, port);
		communication = new Communication(this.socket) {
			@Override
			public void peerAbnormalDrop() {
				System.out.println("服务器异常宕机,服务停止!");
			}
			
			@Override
			public void dealNetMessage(String message) {
				System.out.println("来自服务器的消息:" + message);
			}
		};
	}
	
}

那么,接下来就是客户端的Test类:

package com.mec.network.client.test;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Scanner;

import com.mec.network.client.MulClient;

public class ClientTest {

	public static void main(String[] args) {
		MulClient client = new MulClient();
		
		try {
			client.connectToServer();
			
			Scanner in = new Scanner(System.in);
			String command = "";
			boolean finished = false;
			
			while (!finished) {
				command = in.next();
				client.send(command);
				if (command.equalsIgnoreCase("byebye")) {	//我们设置:当客户端输入字符串"byebye"时,关闭此客户端,并在服务器显示该客户端下线
					client.close();
					finished = true;
				}
			}
			
			in.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

那么,现在我们来看一下运行结果:
在这里插入图片描述可以看到:服务器可以正常地启动和关闭。
在这里插入图片描述可以看到:服务器可有连接多个客户端
在这里插入图片描述可以看到:服务器可以区分客户端的正常和异常下线。

那么,可能有同学问了:那我们的那两个接口有什么用呢?
答曰:可以使得我们的服务器与客户端应付多种需求。例如:窗口编程…
所以我们编程也是要放长一点眼光的啊!

那么,有关网络编程的多客户端 现象的处理 知识点,在这里就讲解完毕了。
若是对上述知识点或代码有任何疑惑、意见或者建议,请在下方评论区提出,本人将尽早予以答复,觉得有所帮助的同学请留下小赞赞,谢谢!!!

发布了118 篇原创文章 · 获赞 82 · 访问量 5225

猜你喜欢

转载自blog.csdn.net/weixin_45238600/article/details/103338610