package bio;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
// 客户端启动必备
Socket socket = null;
// 实例化与服务端通信的输入输出流
ObjectInputStream input = null;
ObjectOutputStream output = null;
// 服务器的通信地址
InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 8888);
try {
socket = new Socket();
// 连接服务器
socket.connect(addr);
System.out.println("Connect Server success !");
output = new ObjectOutputStream(socket.getOutputStream());
input = new ObjectInputStream(socket.getInputStream());
System.out.println("Ready send message");
// 向服务器输出请求
output.writeUTF("Cover");
output.flush();
// 接收服务器的输出
System.out.println(input.readUTF());
} finally {
if (socket != null) {
socket.close();
}
if (output != null) {
output.close();
}
if (input != null) {
input.close();
}
}
}
}
package bio;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerSingle {
public static void main(String[] args) throws IOException {
// 服务端启动必备
ServerSocket serverSocket = new ServerSocket();
// 表示服务端在哪个端口上监听
serverSocket.bind(new InetSocketAddress(8888));
System.out.println("Start Server....");
int connectCount = 0;
try {
while(true) {
System.out.println("一轮循环");
Socket socket = serverSocket.accept();
System.out.println("accept client socket .... total = " + (++connectCount));
try (
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
) {
// 接收客户端的输出,也就是服务端的输入
String userName = inputStream.readUTF();
System.out.println("Accept Client message:" + userName);
// 服务器的输出,也就是客户端的输入
outputStream.writeUTF("Hello" + userName);
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
socket.close();
}
}
} finally {
serverSocket.close();
}
}
}
单线程版的Server,只能一个一个地处理客户端的请求,如果正在处理的客户端发生了阻塞,比如说没有发送数据给服务器端,那么后续的客户端将会受限阻塞的客户端,无法处理下一个请求
有人可能会说一个线程处理一个客户端请求,这样一个线程发生阻塞时,不影响其他的客户端处理,于是乎又在上面代码进行改良
package bio;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
//服务端启动必备
ServerSocket serverSocket = new ServerSocket();
// 表示服务器在哪个端口上监听
serverSocket.bind(new InetSocketAddress(8888));
System.out.println("Start Server ....");
try {
while (true) {
new Thread(new ServerTask(serverSocket.accept())).start();
}
} finally {
serverSocket.close();
}
}
private static class ServerTask implements Runnable {
private Socket socket = null;
public ServerTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// 实例化与客户端通信的输入输出流
try (
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())
) {
//接收客户端的输出,也就是服务器的输入
String userName = inputStream.readUTF();
System.out.println("Accept client message:" + userName);
// 服务器的输出,也就是客户端的输入
outputStream.writeUTF("Hello," + userName);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
一个客户端请求一个线程处理,解决了上面当一个客户端发生阻塞时,其他客户端请求无法处理的问题,但是又有新的问题来了
一个客户端请求就需要创建一个线程来处理,对于操作系统来说,线程是一种昂贵的资源,线程的创建和销毁都需要CPU的调度,这会给操作系统带来一定的压力,即便机器配置很高,但会把服务器的资源吃的很满,这个时候我们可以用线程池来做,避免线程的频繁创建与销毁,我们再来改良一下
package bio;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ServerPool {
private static final Integer CPU_NUM = Runtime.getRuntime().availableProcessors();
private static final Integer CORE_SIZE = CPU_NUM;
private static final Integer MAX_SIZE = CPU_NUM * 2;
private static ThreadPoolExecutor pool = new ThreadPoolExecutor(CORE_SIZE, MAX_SIZE, 0L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), (Runnable r) -> {
Thread t = new Thread(r);
t.setName("bio");
return t;
}, new ThreadPoolExecutor.AbortPolicy());
public static void main(String[] args) throws IOException {
// 服务端启动必备
ServerSocket serverSocket = new ServerSocket();
// 表示服务端在哪个端口上监听
serverSocket.bind(new InetSocketAddress(8888));
System.out.println("Start Server....");
try {
while (true) {
pool.execute(new ServerTask(serverSocket.accept()));
}
} finally {
serverSocket.close();
}
}
private static class ServerTask implements Runnable {
private Socket socket = null;
public ServerTask(Socket socket ) {
this.socket = socket;
}
@Override
public void run() {
// 实例化与客户端通信的输入输出流
try (
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
) {
// 接受客户端的输出,也就是服务端的输入
String userName = inputStream.readUTF();
System.out.println("Accept Client Message:" + userName);
// 服务器的输出,也就是客户端的输入
outputStream.writeUTF("Hello, " + userName);
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
经过一番优化,其实线程池版的Server已经可以抗住一定规模的客户端请求了,如果你的用户规模不是很高,或者说机器数量还可以的话,上面的实现还算可以,但是仍然存在问题,假如当前客户端请求没有发送数据,服务端启动的线程就需要等待,直到数据发送过来,而且也不支持数据灵活读取,不能读取一段数据,接收一段数据,因为BIO是面向流的,只能等这个流被完整读取,才可以进行处理,哪怕服务端要先读取一段,也是不行的,如果还是扛不住,就得需要进一步的优化,比方可以考虑NIO方式,NIO是可以实现单机百万并发连接的
感兴趣的朋友,可以关注我下期的NIO