前言:这是本人在慕课网上学习 一站式学习Java网络编程 全面理解BIO/NIO/AIO 时所做的笔记,供本人复习之用,本文主要讲述BIO,并实现一个基于BIO的聊天室,不保证一定准确。
目录
第一章 一个简单的客户端、服务端通信的例子
懒得讲解了,直接上代码,应该都能看懂
1.1 服务端
package bio;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author ZhangChen
**/
public class Server {
public static void main(String[] args) {
final String quit = "quit";
final int port =8888;
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(port);
System.out.println("启动了服务器,监听端口"+port);
while(true){
//等待客户端连接
Socket socket = serverSocket.accept();
System.out.println("客户端["+socket.getPort()+"]已连接");
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//读取客户端发送的消息
//readline是读出行分隔符之前的所有信息
String msg = null;
while((msg=reader.readLine())!=null) {
System.out.println("客户端[" + socket.getPort() + "]: " + msg);
//回复客户发送的消息
writer.write("服务器: " + msg + "\n");
writer.flush();
if(msg.equals(quit)){
System.out.println("客户端["+socket.getPort()+"]已断开连接");
break;
}
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
1.2 客户端
package bio;
import java.io.*;
import java.net.Socket;
import java.nio.Buffer;
/**
* @author ZhangChen
**/
public class Client {
public static void main(String[] args) {
final String quit = "quit";
final String host = "127.0.0.1";
final int port = 8888;
Socket socket = null;
BufferedWriter writer =null;
try {
socket = new Socket(host,port);
//创建IO流
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//等待用户输入消息
while(true) {
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
String input = consoleReader.readLine();
//发送消息给服务器
writer.write(input + "\n");
writer.flush();
//读取服务器
String msg = reader.readLine();
System.out.println(msg);
if(input.equals(quit)){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(writer!=null){
try {
writer.close();
System.out.println("关闭socket");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
第二章 BIO编程模型
在上面的代码中,当服务器处理一个客户端请求时,就没办法再处理第二个请求了,我们可以在服务器端再创建一个线程,比如我们将它称为handler,它处理和客户端各种数据的交换,这就是BIO编程模型。
2.1 基于BIO的聊天室
功能:就是每个客户发送的消息能被转发给其他的用户。
客户端需要两个线程:一个读服务器的,一个向服务器写
服务端需要获取线程输出流的集合以把消息进行转发
2.2 聊天室服务端
服务端主要是当客户端有请求过来的时候,就开一个新线程来处理请求,主线程循环等待客户端连接,采用主机的端口号来映射连接。
服务器:
package bio.chat;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
/**
* @author ZhangChen
**/
public class ChatServer {
private int port = 8888;
private final String quit = "quit";
private ServerSocket serverSocket;
private Map<Integer, Writer> connectedClients ;
public ChatServer(){
connectedClients = new HashMap<>();
}
public synchronized void addClient(Socket socket) throws IOException {
if(socket!=null){
int clientPort = socket.getPort();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
connectedClients.put(clientPort,bufferedWriter);
System.out.println("客户端["+clientPort+"]已连接到服务器");
}
}
public synchronized void removeClient(Socket socket) throws IOException {
if(socket!=null){
int clientPort = socket.getPort();
if(connectedClients.containsKey(port)){
connectedClients.get(port).close();
}
connectedClients.remove(clientPort);
System.out.println("客户端["+clientPort+"]已连断开连接 ");
}
}
public synchronized void forwardMessage(Socket socket,String fwdMsg) throws IOException {
for(Integer id:connectedClients.keySet()){
if(!id.equals(socket.getPort())){
Writer writer = connectedClients.get(id);
writer.write(fwdMsg);
writer.flush();
}
}
}
public boolean readyToQuit(String msg){
return quit.equals(msg);
}
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(port);
System.out.println("启动服务器,监听端口: "+port+"...");
while (true){
//等待客户端连接
Socket socket = serverSocket.accept();
//创建ChatHandler线程
addClient(socket);
new Thread(new ChatHandler(socket,this)).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close();
}
}
public static void main(String[] args) {
ChatServer chatServer = new ChatServer();
chatServer.start();
}
}
处理线程:
package bio.chat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
/**
* @author ZhangChen
**/
public class ChatHandler implements Runnable{
private ChatServer server;
private Socket socket;
public ChatHandler(Socket socket,ChatServer chatServer){
this.server = chatServer;
this.socket = socket;
}
@Override
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = null;
while((msg=reader.readLine())!=null){
String fwdMsg = "客户端["+socket.getPort()+"]: "+msg+"\n";
System.out.print(fwdMsg);
if(server.readyToQuit(msg)){
break;
}
server.forwardMessage(socket,fwdMsg);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
server.removeClient(socket);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.3 聊天室客户端
客户端主要是两个线程,一个主线程监听服务器发来的消息,一个副线程处理用户的输入请求,将请求法向服务器。
主线程:
package bio.chat;
import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @author ZhangChen
**/
public class ChatClient {
final String quit = "quit";
final String host = "127.0.0.1";
final int port = 8888;
private Socket socket;
private BufferedWriter writer;
private BufferedReader reader;
public ChatClient(){
}
//发送消息给服务端
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 quit.equals(msg);
}
public void start (){
try {
//创建socket
socket = new Socket(host,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 void close(){
if(writer!=null){
try {
System.out.println("关闭socket");
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ChatClient chatClient = new ChatClient();
chatClient.start();
}
}
监听用户输入的线程:
package bio.chat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author ZhangChen
**/
public class UserInputHandler implements Runnable{
private ChatClient chatClient;
public UserInputHandler(ChatClient chatClient){
this.chatClient = chatClient;
}
@Override
public void run() {
//等待用户输入消息
BufferedReader reader =null;
try {
reader = new BufferedReader(new InputStreamReader(System.in));
String msg = null;
while(true){
msg = reader.readLine();
chatClient.send(msg);
if(chatClient.readyToQuit(msg)){
chatClient.close();
break;
}
}
}catch (IOException e){
e.printStackTrace();
} finally {
if(reader!=null){
try {
reader.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
}
第三章 使用伪异步IO改进多人聊天室
如果每来一个连接请求则开一个线程,那么有成千上百个请求的时候就会出问题,所以可以用线程池来解决这个问题。
public class ChatServer {
private int port = 8888;
private final String quit = "quit";
private ServerSocket serverSocket;
private Map<Integer, Writer> connectedClients ;
private ExecutorService executorService;
public ChatServer(){
connectedClients = new HashMap<>();
executorService = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MILLISECONDS,,new LinkedBlockingQueue<Runnable>(),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
}
public synchronized void addClient(Socket socket) throws IOException {
if(socket!=null){
int clientPort = socket.getPort();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
connectedClients.put(clientPort,bufferedWriter);
System.out.println("客户端["+clientPort+"]已连接到服务器");
}
}
public synchronized void removeClient(Socket socket) throws IOException {
if(socket!=null){
int clientPort = socket.getPort();
if(connectedClients.containsKey(port)){
connectedClients.get(port).close();
}
connectedClients.remove(clientPort);
System.out.println("客户端["+clientPort+"]已连断开连接 ");
}
}
public synchronized void forwardMessage(Socket socket,String fwdMsg) throws IOException {
for(Integer id:connectedClients.keySet()){
if(!id.equals(socket.getPort())){
Writer writer = connectedClients.get(id);
writer.write(fwdMsg);
writer.flush();
}
}
}
public boolean readyToQuit(String msg){
return quit.equals(msg);
}
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(port);
System.out.println("启动服务器,监听端口: "+port+"...");
while (true){
//等待客户端连接
Socket socket = serverSocket.accept();
//创建ChatHandler线程
addClient(socket);
executorService.execute(new ChatHandler(socket,this));
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close();
}
}
public static void main(String[] args) {
ChatServer chatServer = new ChatServer();
chatServer.start();
}
}