Java学习笔记(十六):网络编程之服务器与多个客户端

一个服务器接入多个客户端

一个服务器接入多个客户端,其双向通信工作模式,必须拥有三个要素:
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”时,傻妞一号程序结束,服务器端接受消息,并输出客户端已正常下线;在这里插入图片描述
手动使傻妞二号异常掉线,服务器端输出客户端异常掉线;

在这里插入图片描述

手动使服务器端异常掉线,客户端输出服务器异常掉线;在这里插入图片描述
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值