一个服务器接入多个客户端
一个服务器接入多个客户端,其双向通信工作模式,必须拥有三个要素:
Socket类的对象,即通信端口封装类。由它才能创建下面的通信信道,并在结束通信时,关闭网络连接。
DataInputStream类的对象:接收来自”对端“信息的输入通信信道。
DataOutputStream类的对象:对”对端“发送信息的输出信道。
任何需要直接通信的双方,只要拥有这三个元素,就能进行直接通信。
从通信层面上看,无论是服务器端还是客户端,功能需求基本上都是相同的,可以”抽象“成一个单独的类。
在程序执行message = dis.readUTF(); 时,是没有办法录入文字的。
因为,只要服务器没有向这个客户端发送信息,这条语句就一直在等待。无法实现边听边说的功能。
所以,要用线程来处理侦听来自对端的功能。
package stu.caryue.multi.core;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
/**
* @author Crayue
* @version 2019年12月3日 下午8:45:23
*/
public abstract class Communication implements Runnable{
private Socket socket;
private DataInputStream dis;
private DataOutputStream dos;
private String ip;
protected volatile boolean goon;
public Communication(Socket socket) {
this.socket=socket;
this.ip = socket.getInetAddress().getHostAddress();
try {
dis = new DataInputStream(socket.getInputStream());//接收数据
dos = new DataOutputStream(socket.getOutputStream());//发送数据
goon = true;
new Thread(this).start();//启动侦听对端线程
} catch (IOException e) {
e.printStackTrace();
}
}
public void send(String message) {
try {
dos.writeUTF(message);
} catch (IOException e) {
//向对端发信息失败,意味着对端已经关闭通信
close();
}
}
public String getIp() {
return ip;
}
@Override
public void run() {
String message = null;
while (goon) {
try {
message = dis.readUTF();
dealNetMessage(message);
} catch (IOException e) {
if (goon == true ) {
peerAbnormalDrop();
}
close();
}
}
close();
}
public abstract void dealNetMessage(String message);//处理消息
public abstract void peerAbnormalDrop();//处理对端异常掉线
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;
}
}
}
服务器如何接入多个客户端呢?
在程序执行Socket socket = server.accept();时,只有当有客户端请求并连接,函数才会返回。
换句话说,在没有新的客户端接入之前,程序会卡在这一条语句。
应该用线程实现侦听客户端连接请求的工作。
这样就可以,不断执行 Socket socket = server.accept();
每连接一个客户端,就初始化一个Communication类,并开始进行网络通信。
package stu.caryue.multi.server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import stu.caryue.multi.core.Communication;
/**
* @author Crayue
* @version 2019年12月3日 下午8:44:10
*/
public class Multiserver implements Runnable {
private ServerSocket server;
private int port;
private volatile boolean goon;
public Multiserver() {
this.port = 54188;
}
public Multiserver setPort(int port) {
this.port = port;
return this;
}
public void startup() throws IOException {
if (goon == true) {
System.out.println("服务器已启动");
return;
}
System.out.println("正在启动服务器,请稍后...");
server = new ServerSocket(port);
System.out.println("服务器启动成功咯~");
goon = true;
new Thread(this).start();//启动监听线程
}
public void shutdown() {
if (goon == false) {
System.out.println("服务器未启动");
return;
}
close();
System.out.println("服务器已宕机");
}
public boolean isStartup() {
return goon;
}
private void close() { //关闭server
goon = false;
try {
if (server != null && !server.isClosed()) {
server.close();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
server = null;
}
}
@Override
public void run() {
while (goon) {//使用无限循环模式,一直增添要侦听的客户端
try {
System.out.println("开始侦听客户端连接请求……");
Socket socket = server.accept();//建立远程连接, 只有当有客户端请求并连接,函数才会返回
String clientIp = socket.getInetAddress().getHostAddress();
System.out.println("侦听到一个客户端[" + clientIp + "]的连接请求,并已连接!");
new Communication(socket) {
@Override
public void peerAbnormalDrop() {
System.out.println("客户端" + getIp() + "异常掉线");
}
@Override
public void dealNetMessage(String message) {
System.out.println("接收到来自客户端" + getIp() + "的消息" + message);
if (message.equalsIgnoreCase("byebye")) {
System.out.println("客户端[" + getIp() + "]下线");
close();
} else {
send("[" + message + "]");
}
}
};
} catch (IOException e) {
close();
}
}
close();
}
}
一对多客户端
package stu.caryue.multi.client;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
import stu.caryue.multi.core.Communication;
/**
* @author Crayue
* @version 2019年12月3日 下午8:44:33
*/
public class MultiClient {
private String ip;
private int port;
private Socket socket;
private Communication communication;
public MultiClient() {
this.ip = "localhost";
this.port = 54188;
}
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 {
socket = new Socket(ip, port);
communication=new Communication(socket) {
@Override
public void peerAbnormalDrop() {
System.out.println("服务器异常掉线");
}
@Override
public void dealNetMessage(String message) {
System.out.println("接收到来自服务器的消息" + message);
}
};
}
}
服务器端的测试类
当从键盘键入“startup”时,开始创建服务器
当从键盘键入“shutdown”时,关闭服务器
当从键盘键入“exit”,并且保证已经关闭服务器后,正常退出程序
package stu.caryue.multi.test;
import java.io.IOException;
import java.util.Scanner;
import stu.caryue.multi.server.Multiserver;
/**
* @author Crayue
* @version 2019年12月3日 下午8:45:45
*/
public class ServerTest {
public static void main(String[] args) {
Multiserver server = new Multiserver();
Scanner in = new Scanner(System.in);
String command = "";
boolean finished = false;
while (!finished) {
command = in.next();
if (command.equalsIgnoreCase("startup")) { //开始创建服务器
try {
server.startup();
} catch (IOException e) {
e.printStackTrace();
}
} else if (command.equalsIgnoreCase("shutdown")) {//关闭服务器
server.shutdown();
} else if (command.equalsIgnoreCase("exit")) { //退出程序
if (!server.isStartup()) {
finished = true;
} else {
System.out.println("服务器尚未宕机!");
}
}
}
in.close();
}
}
客户端的测试类
当从键盘键入“byebye”时,关闭此客户端,并在服务器显示该客户端下线;
package stu.caryue.multi.test;
import java.io.IOException;
import java.net.UnknownHostException;
import java.util.Scanner;
import stu.caryue.multi.client.MultiClient;
/**
* @author Crayue
* @version 2019年12月3日 下午8:46:04
*/
public class ClientTest {
public static void main(String[] args) {
MultiClient client = new MultiClient();
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")) {//关闭此客户端,并在服务器显示该客户端下线
client.close();
finished = true;
}
}
in.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行截图
可以看出,服务器可接入多个客户端,且互不影响;
当傻妞一号键入“byebye”时,傻妞一号程序结束,服务器端接受消息,并输出客户端已正常下线;
手动使傻妞二号异常掉线,服务器端输出客户端异常掉线;
手动使服务器端异常掉线,客户端输出服务器异常掉线;