Java网络编程——详解TCP协议的实现(缓冲区刷新、并发执行、使用线程池),本机的服务器和客户端连接及三次优化

TCP的API

在这里插入图片描述
ServerSocket
Socket

TCP回显服务器

TCP的连接管理是由操作系统内核管理
使用阻塞队列组织若干个连接对象
当连接建立成功,内核已经把这个连接对象放入到阻塞队列当中,代码中调用的accept就是从阻塞队列中取出一个连接对象(在应用程序中的化)
后续的数据读写都是针对clientSocket这个对象来进行展开的

如果服务器启动之后,没有客户端建立连接,此时代码中调用accept就会阻塞,阻塞到真的有客户端建立连接。

服务器的处理方式:
1.短连接: 一个连接中,客户端和服务器之间只交互一次.交互完毕,就断开连接.
2.长连接: 一个连接中,客户端和服务器之间交互N次.直到满足一定条件再断开连接.(效率更高,避免了反复建立连接和断开连接的过程)

1、初始化服务器
2、进入主循环
   a、从内核获得TCP连接
   b、处理TCP连接
     (1)读取请求并解析
     (2)根据请求计算响应
     (3)把响应写回给客户端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpEchoServer {
    //1、初始化服务器
    //2、进入主循环
    //a、从内核获得TCP连接
    //b、处理TCP连接
      //(1)读取请求并解析
      //(2)根据请求计算响应
      //(3)把响应写回给客户端
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        //类似UCP服务器绑定端口
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
            //1)先从内核中获取到一个TCP连接
            Socket clientSocket = serverSocket.accept();
            //2)处理连接
            processConnection(clientSocket);
        }
    }
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s : %d] 客户端上线\n", clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        //通过clientSocket 来和客户端交互,先做好准备工作,获取到clientSocket中的流对象
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
            //实现长连接 去掉while循环就是短链接
            //一次连接的处理过程中
            //当客户端断开连接退出循环,也就结束
            //当客户端断开连接的时候readLine 或者 write方法会触发异常
            while (true) {
                //1、读取请求并解析(readLine()对应客户端发送数据的格式,必须是按行发送)
                String request = bufferedReader.readLine();//客户端发的数据必须是一个按行发送的数据
                // (相当于应用层的自定义协议,客户端发送也必须按行发送)
                //2、根据请求计算响应
                String response = process(request);
                //3、把响应写回到客户端(客户端按行读取)
                bufferedWriter.write(response+"\n");


                System.out.printf("[%s : %d] ip :%s ; 端口: %s \n;",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {
            //e.printStackTrace();
            System.out.printf("[%s : %d] 客户端下线\n",clientSocket.getInetAddress().toString(),
                    clientSocket.getPort());
        }

    }
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(9090);
        tcpEchoServer.start();

    }
}

TCP服务器优化

TCP要保证可靠传输,所以牺牲了一定的性能。

优化一(缓冲区刷新)

上述代码中,客户端连接之后,服务器能感受到客户端,但是客户端发送数据,服务器没有反馈。
在这里插入图片描述
在这里插入图片描述
客户端发送数据给服务器之后,服务器就要做出相应的.
服务器没有任何提示,说明此时大概率的情况是:
1.客户端没有发送请求成功
2.服务器没有接受请求成功
没有打印日志,在readLine()堵塞

查看服务器线程
+
然后打开客户端的线程、
在这里插入图片描述
使用BuffferedWriter的write将数据写入了缓冲区,并没有真的写入到socket文件中,所以要手动刷新缓冲区 使用flush方法
在这里插入图片描述
服务器加flush
在这里插入图片描述
客户端加flush
在这里插入图片描述
测试
在这里插入图片描述
在这里插入图片描述

启动多个连接

在这里插入图片描述
在 idea中编译运行的客户端可以正常运行,但是在命令行中运行的客户端不能正常回响

在这里插入图片描述
只有当退出第一个在idea中的客户端,这样在shell中的客户端才正常上线。
在这里插入图片描述
说明现在的服务器同一时刻只能处理一个客户端的连接. (同时过来多个客户端,此时只有第一个能正确处理. 第一个客户端退出了, 第二个客户端
才能正确被处理)

原因在于accept方法调用速度太慢、频率太低,调用几次accept就处理几个客户端的连接

解决方案:
需要在代码中,同时调用accept和processConnection (让这两个操作,并发执行)
多线程:用一个线程专门负责调用accept,再用其他线程,每个线程专负责一个客户端的长连接循环
在这里插入图片描述
原来的代码, accept和processConnection是串行执行的,改进后accept和processConnection是并发执行的。此处代码中while就会反复快速的调用到accept.于是就能同时处理多个客户端的
连接了

在这里插入图片描述
在这里插入图片描述
注意
在这里插入图片描述
在这里插入图片描述

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpThreadEchoServer {
    private ServerSocket serverSocket = null;

    public TcpThreadEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            Socket clientSocket = serverSocket.accept();
            //针对这个连接,单独创建一个线程负责处理
            Thread t = new Thread(){
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            };
            t.start();
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s : %d] 客户端上线\n",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))){
            while (true){
                //1、读取请求并解析
                String request = bufferedReader.readLine();
                //2、根据请求计算响应
                String response = process(request);
                //3、把相应写回到客户端
                bufferedWriter.write(response + "\n");
                bufferedWriter.flush();

                System.out.printf("[%s : %d] ip:%s; 端口:%s\n",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }
        }catch (IOException e){
           // e.printStackTrace();
            System.out.printf("[%s : %d] 客户端下线\n",clientSocket.getInetAddress().toString(),
                    clientSocket.getPort());
        }

    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpThreadEchoServer tcpThreadEchoServer = new TcpThreadEchoServer(9090);
        tcpThreadEchoServer.start();
    }
}

线程池优化

避免频繁的创建和销毁线程,节省开销
在这里插入图片描述
在这里插入图片描述

最终的服务器优化代码


import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpThreadPoolEchoServer {
    private ServerSocket serverSocket = null;

    public TcpThreadPoolEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        //创建一个线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        while (true){
            Socket clientSocket = serverSocket.accept();
            //针对这个连接,单独创建一个线程负责处理
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });

        }
    }

    private void processConnection(Socket clientSocket)  {
        System.out.printf("[%s : %d] 客户端上线\n",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))){
            while (true){
                //1、读取请求并解析
                String request = bufferedReader.readLine();
                //2、根据请求计算响应
                String response = process(request);
                //3、把相应写回到客户端
                bufferedWriter.write(response + "\n");
                bufferedWriter.flush();

                System.out.printf("[%s : %d] ip:%s; 端口:%s\n",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }
        }catch (IOException e){
            // e.printStackTrace();
            System.out.printf("[%s : %d] 客户端下线\n",clientSocket.getInetAddress().toString(),
                    clientSocket.getPort());
        }

    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpThreadPoolEchoServer tcpThreadPoolEchoServer = new TcpThreadPoolEchoServer(9090);
        tcpThreadPoolEchoServer.start();
    }
}



TCP回显客户端

1、启动客户端(不绑定端口)和服务器建立连接
2、进入主循环
    a、读取用户输入内容
    b、构造一个请求发送给服务器
    c、读取服务器的响应数据
    d、把响应数据显示到界面
import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    //1、启动客户端(不绑定端口)和服务器建立连接
    //2、进入主循环
    //  a、读取用户输入内容
    //  b、构造一个请求发送给服务器
    //  c、读取服务器的响应数据
    //  d、把响应数据显示到界面
    private Socket socket = null;

    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        //从此处的实例化Socket过程,就是建立Ip连接
        socket = new Socket(serverIp,serverPort);
    }
    public void start() throws IOException {
        System.out.println("客户端启动");
        Scanner in = new Scanner(System.in);

        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))){
            while (true){
                //  a、读取用户输入内容
                System.out.println("->");
                String request = in.nextLine();
                if("exit".equals(request)){
                    break;
                }
                //  b、构造一个请求发送给服务器
                bufferedWriter.write(request + "\n");
                //\n是为了和服务器中的readLine相对应 读取和发送都是按行发送读取
                //按行读 按行写是一种简单的自定义协议

                //  c、读取服务器的响应数据
                String response = bufferedReader.readLine();//读取响应和服务器返回响应对应,只读一行忽略\n
                // 自定义协议请求和响应不一定要一致

                //  d、把响应数据显示到界面
                System.out.println(response);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
        //127.0.0.1表示环回ip,自己访问自己,刚刚所写的服务器和客户端都是在一个主机上,所以使用这个ip
        //如果不在同一个主机上,此处的ip就要写成要访问的服务器ip,9090就是上面写的服务器的端口号
        tcpEchoClient.start();
    }

}

交互流程

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值