前言
网络编程的基本模型是 Client/Server 模型, 也就是两个进程之间进行相互通信, 其中服务端提供位置信息 (绑定的 IP 地址和监听端口), 客户端通过连接操作向服务器监听的地址发起连接请求, 如果连接建立成功 , 双方就可以通过 Socket 进行通信.
在传统的 BIO 网络编程中, ServerSocket 负责绑定 IP 地址, 启动监听端口; Socket 负责发起连接操作. 连接成功之后, 双方通过输入和输出流进行同步阻塞式通信.
下面我们会一步一步的实现 BIO 的网络编程, 并且对代码进行不断的修改和优化.
BIO 网络编程
正如上文所言, 进行 BIO 网络编程需要 Server 和 Client, 客户端我们只编写一个版本, 保证能够向服务器发送请求, 而服务端我们会从最基本的只能够进行一次请求处理的版本开始, 不断的完善.
客户端
- package nio;
- import java.io.*;
- import java.net.InetSocketAddress;
- import java.net.Socket;
- public class BIOClient {
- public static void main(String[] args) throws Exception{
- for (int i=0;i<=99;i++){
- System.out.println(sendMsg("你好"+i));
- }
- }
- public static String sendMsg(String msg) throws Exception{
- // 创建 socket 只负责发送请求 不需要监听
- Socket socket = new Socket();
- // 连接服务器
- socket.connect(new InetSocketAddress("localhost",9999));
- // 写入数据给服务器
- OutputStream outputStream = socket.getOutputStream();
- // java 基础的 IO 操作
- PrintWriter printWriter = new PrintWriter(outputStream);
- printWriter.println(msg);
- printWriter.flush();
- // 关闭输出流
- socket.shutdownOutput();
- // 接受服务器的响应信息
- InputStream inputStream = socket.getInputStream();
- // java 基础的 IO 操作
- InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
- StringBuffer stringBuffer = new StringBuffer();
- String value = null;
- while ((value=bufferedReader.readLine())!=null){
- stringBuffer.append(value);
- }
- socket.close();
- return "客户端收到:"+ stringBuffer.toString();
- }
- }
服务端
第一版
服务器启动一次就关闭
优化方向: 保证能够连续的处理请求
- public static void test1() throws Exception{
- // 创建一个 ServerSocket
- ServerSocket ss = new ServerSocket();
- // 绑定服务器监听端口
- ss.bind(new InetSocketAddress(9999));
- // 代表服务器和客户端的会话 (InputStream = HttpServletRequest | OutputStream = HttpServletResponse)
- // socket 阻塞的 accept 会监听有没有请求
- System.out.println("我在 9999 监听");
- Socket socket = ss.accept();
- System.out.println("请求处理");
- // 获取请求数据 InputStream
- InputStream inputStream = socket.getInputStream();
- // java 基础的 IO 操作
- InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
- StringBuffer stringBuffer = new StringBuffer();
- String value = null;
- while ((value=bufferedReader.readLine())!=null){
- stringBuffer.append(value);
- }
- System.out.println("服务器收到:"+ stringBuffer.toString());
- // 获取响应 OutputStream
- OutputStream outputStream = socket.getOutputStream();
- // java 基础的 IO 操作
- PrintWriter printWriter = new PrintWriter(outputStream);
- printWriter.println("你好, 我是服务器");
- printWriter.flush();
- // 关闭资源
- socket.close();
- }
第二版
使用死循环, 保证可以一直处理请求.
缺点:
虽然可以处理多次请求, 但是所有的请求都是依次执行, 并不是实际意义上的并发处理, 也就是说没有实现高并发
串行执行如果第一个请求没有处理结束, 不可以处理第二个请求
升级方向: 多线程
- public static void test2() throws Exception{
- // 创建一个 ServerSocket
- ServerSocket ss = new ServerSocket();
- // 绑定服务器监听端口
- ss.bind(new InetSocketAddress(9999));
- // 代表服务器和客户端的会话 (InputStream = HttpServletRequest | OutputStream = HttpServletResponse)
- // socket 阻塞的 accept 会监听有没有请求
- while (true){
- System.out.println("我在 9999 监听");
- Socket socket = ss.accept();
- System.out.println("请求处理");
- // 获取请求数据 InputStream
- InputStream inputStream = socket.getInputStream();
- // java 基础的 IO 操作
- InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
- StringBuffer stringBuffer = new StringBuffer();
- String value = null;
- while ((value=bufferedReader.readLine())!=null){
- stringBuffer.append(value);
- }
- System.out.println("服务器收到:"+ stringBuffer.toString());
- // 获取响应 OutputStream
- OutputStream outputStream = socket.getOutputStream();
- // java 基础的 IO 操作
- PrintWriter printWriter = new PrintWriter(outputStream);
- printWriter.println("你好, 我是服务器");
- printWriter.flush();
- // 关闭资源
- socket.close();
- }
- }
版本三
多线程处理 一旦有请求过来就交给一个新的线程处理 缺点:
虽然实现了线程并发处理, 但是并没有对线程创建做限制, 如果每次处理 10 秒, 并发量 10 万, 服务器会阻塞成千上万个线程
直接 new 线程比较耗费系统的资源, 在高并发服务器中会使用多线程, 不可能无限制的 new 线程, 会耗尽服务器的资源
改进方向: 使用线程池
- public static void test3() throws Exception{
- // 创建一个 ServerSocket
- ServerSocket ss = new ServerSocket();
- // 绑定服务器监听端口
- ss.bind(new InetSocketAddress(9999));
- // 代表服务器和客户端的会话 (InputStream = HttpServletRequest | OutputStream = HttpServletResponse)
- // socket 阻塞的 accept 会监听有没有请求
- while (true){
- System.out.println("我在 9999 监听");
- Socket socket = ss.accept();
- // 多线程处理 一旦有请求过来就交给一个新的线程处理
- SocketProcessRunable socketProcessRunable = new SocketProcessRunable(socket);
- Thread thread = new Thread(socketProcessRunable);
- thread.start();
- }
- }
- class SocketProcessRunable implements Runnable{
- private Socket socket;
- public SocketProcessRunable(Socket socket){
- this.socket = socket;
- }
- @Override
- public void run() {
- try {
- Thread.sleep(1000);
- // 获取请求数据 InputStream
- InputStream inputStream = socket.getInputStream();
- // java 基础的 IO 操作
- InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
- StringBuffer stringBuffer = new StringBuffer();
- String value = null;
- while ((value=bufferedReader.readLine())!=null){
- stringBuffer.append(value);
- }
- System.out.println("服务器收到:"+ stringBuffer.toString());
- // 获取响应 OutputStream
- OutputStream outputStream = socket.getOutputStream();
- // java 基础的 IO 操作
- PrintWriter printWriter = new PrintWriter(outputStream);
- printWriter.println("你好, 我是服务器");
- printWriter.flush();
- }catch (Exception e){
- e.printStackTrace();
- }finally {
- try {
- socket.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
版本四
使用线程池管理线程
- public static void main(String[] args) throws Exception{
- // 创建线程池 Executors 创建线程的缺点
- ExecutorService executorService = Executors.newFixedThreadPool(10);
- // 创建一个 ServerSocket
- ServerSocket ss = new ServerSocket();
- // 绑定服务器监听端口
- ss.bind(new InetSocketAddress(9999));
- // 代表服务器和客户端的会话 (InputStream = HttpServletRequest | OutputStream = HttpServletResponse)
- // socket 阻塞的 accept 会监听有没有请求
- while (true){
- System.out.println("我在 9999 监听");
- Socket socket = ss.accept();
- // 多线程处理 一旦有请求过来就交给一个新的线程处理
- SocketProcessRunable socketProcessRunable = new SocketProcessRunable(socket);
- // 将任务交给线程池处理
- executorService.submit(socketProcessRunable);
- }
- }
- class SocketProcessRunable implements Runnable{
- private Socket socket;
- public SocketProcessRunable(Socket socket){
- this.socket = socket;
- }
- @Override
- public void run() {
- try {
- Thread.sleep(1000);
- // 获取请求数据 InputStream
- InputStream inputStream = socket.getInputStream();
- // java 基础的 IO 操作
- InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
- BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
- StringBuffer stringBuffer = new StringBuffer();
- String value = null;
- while ((value=bufferedReader.readLine())!=null){
- stringBuffer.append(value);
- }
- System.out.println("服务器收到:"+ stringBuffer.toString());
- // 获取响应 OutputStream
- OutputStream outputStream = socket.getOutputStream();
- // java 基础的 IO 操作
- PrintWriter printWriter = new PrintWriter(outputStream);
- printWriter.println("你好, 我是服务器");
- printWriter.flush();
- }catch (Exception e){
- e.printStackTrace();
- }finally {
- try {
- socket.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
我不能保证每一个地方都是对的, 但是可以保证每一句话, 每一行代码都是经过推敲和斟酌的. 希望每一篇文章背后都是自己追求纯粹技术人生的态度.
永远相信美好的事情即将发生.
来源: https://juejin.im/post/5af26d646fb9a07ab97975bf