本篇文章,我主要讲解BIO的多人聊天室
BIO编程模型
服务器端设计:首先有一个accept做一个接收的工作,用主线程来接收。(创建服务端和客户端的连接)在服务器中创建线程负责与客户端进行读写服务,也就是handler线程。为了让服务器可以接收多个客户端发送过来的请求,并且和多个客户端进行交流,我们的主线程为每一个客户端创建一个与之对应的handler线程,一对一的关系。因为这是一个多人聊天室,所以,我们需要存储目前在线的用户集合
客户端设计:可以和服务器建立连接进行数据交换,接收客户文本输入(阻塞)是一个等待的过程,客户端还是需要两条线程的,一条使用户自己输入,一条是转发其他用户的消息
在服务器方面
ChatServer
//监听客户端发起的连接要求,有就接收
public class ChatServer {
private int DEFAULT_PORT=8888;
private final String QUIT="quit";
//线程池
private ExecutorService executorService;
private ServerSocket serverSocket;
//向其他客户端写消息
private Map<Integer, Writer> connectedClients;
public ChatServer(){
connectedClients=new HashMap<>();
executorService= Executors.newFixedThreadPool(10);
}
//添加用户
public synchronized void addClient(Socket socket) throws IOException {
if(socket!=null)
{
int port=socket.getPort();
BufferedWriter writer=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
connectedClients.put(port,writer);
System.out.println("这个客户端["+port+"]已连接到服务器");
}
}
//移除用户
public synchronized void removeClient(Socket socket) throws IOException {
if(socket!=null)
{
int port=socket.getPort();
if(connectedClients.containsKey(port)){
connectedClients.get(port).close();
}
connectedClients.remove(port);
System.out.println("这个客户端["+port+"]已断开连接");
}
}
//转发消息
public synchronized void forwardMessage(Socket socket,String msg) throws IOException {
for(Integer id:connectedClients.keySet())
{
if(!id.equals(socket.getPort()))
{
Writer writer=connectedClients.get(id);
writer.write(msg);
writer.flush();
}
}
}
//查看是否退出
public boolean readToQuit(String msg)
{
return msg.equals(QUIT);
}
//统一关闭方法
public synchronized void close(){
if(serverSocket!=null)
{
try {
serverSocket.close();
System.out.println("关闭serverSocket");
} catch (IOException e) {
e.printStackTrace();
}
}
}
//开始
public void start(){
try {
//绑定监听端口
serverSocket=new ServerSocket(DEFAULT_PORT);
System.out.println("服务器已经启动,监听端口"+DEFAULT_PORT);
while (true)
{
//等待客户端连接
//accept的调用是阻塞式的,一旦有连接就会返回连接的socket
Socket socket=serverSocket.accept();
//创建charHandler线程
//new Thread(new ChatHandler(this,socket)).start();
executorService.execute(new ChatHandler(this,socket));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
close();
}
}
public static void main(String[] args) {
ChatServer server=new ChatServer();
server.start();
}
}
ChatHandler
public class ChatHandler implements Runnable{
private ChatServer server;
private Socket socket;
public ChatHandler(ChatServer server,Socket socket){
this.server=server;
this.socket=socket;
}
@Override
public void run() {
try {
//存储新上线客户
server.addClient(socket);
//读取用户发送来的消息
BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg=null;
while ((msg=reader.readLine())!=null)
{
String fwgMsg="客户端["+socket.getPort()+"]"+msg+"\n";
System.out.println(fwgMsg);
//转发消息给聊天室在线的用户
server.forwardMessage(socket,fwgMsg);
//检查用户发送的消息是不是准备退出
if(server.readToQuit(msg))
{
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
server.removeClient(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
在客户端方面
ChatClient
public class ChatClient {
private final String DEFAULT_SERVER_HOST="127.0.0.1";
private final int DEFAULT_SERVER_PORT=8888;
private final String QUIT="quit";
private Socket socket;
private BufferedReader reader;
private BufferedWriter writer;
//发送消息给服务器
public void send(String msg) throws IOException {
if(!socket.isOutputShutdown())
{
writer.write(msg+"\n");
writer.flush();
}
}
//从服务器接收消息
public String receive() throws IOException {
String msg=null;
if(!socket.isInputShutdown())
{
msg=reader.readLine();
}
return msg;
}
//检查用户是否准备退出
public boolean readyToQuit(String msg)
{
return msg.equals(QUIT);
}
public void close()
{
if(writer!=null)
{
try {
System.out.println("关闭socket");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void start()
{
try {
//创建socket对象
socket=new Socket(DEFAULT_SERVER_HOST,DEFAULT_SERVER_PORT);
//创建发送和接收消息的io流
reader=new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//处理用户输入
new Thread(new UserInputHandler(this)).start();
//读取服务器转发的消息
String msg=null;
while((msg=receive())!=null){
System.out.println(msg);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
close();
}
}
public static void main(String[] args) {
ChatClient client=new ChatClient();
client.start();
}
}
UserInputHandler
public class UserInputHandler implements Runnable{
private ChatClient chatClient;
public UserInputHandler(ChatClient chatClient){
this.chatClient=chatClient;
}
@Override
public void run() {
//等待用户输入消息
BufferedReader consoleReader=new BufferedReader(new InputStreamReader(System.in));
try {
while (true) {
String input = consoleReader.readLine();
//向服务器发送消息
chatClient.send(input);
//检查用户是否准备退出
if(chatClient.readyToQuit(input))
{
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}