前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >22. 网络编程(2)——TCP 协议

22. 网络编程(2)——TCP 协议

作者头像
小雨的分享社区
发布2022-10-26 15:48:16
2600
发布2022-10-26 15:48:16
举报
文章被收录于专栏:小雨的CSDN小雨的CSDN

网络编程需要依靠Socket API,在java标准库中有两种风格: 1.(UDP)DatagramSocket:面向数据报(发送接收数据,必须以一定的数据报为单位进行传输) 2.(TCP)ServerSocket:面向字节流

UDP和TCP就是传输层的两个最重要的协议

TCP

服务器逻辑:

1.初始化服务器 2.进入主循环 1)先去从内核中获取到一个TCP的连接 2)处理这个TCP的连接 a)读取请求并解析 b)根据请求计算响应 c)把响应写回给客户端

服务器实现:

代码语言:javascript
复制
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpEchoServer {
    //1.初始化服务器
    //2.进入主循环
    //  1)先去从内核中获取到一个TCP的连接
    //  2)处理这个TCP的连接
    //    a)读取请求并解析
    //    b)根据请求计算响应
    //    c)把响应写回给客户端

    private ServerSocket serverSocket = null;

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

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            //  1)先去从内核中获取到一个TCP的连接
            //TCP的连接管理是由操作系统内核来管理的(先描述,再组织【使用一个阻塞队列来组织若干个连接对象】)
            //当连接建立成功,内核已经把这个连接对象放到了阻塞队列中了,代码中调用到accept就是从阻塞队列中取出一个连接对象
            //在应用程序中就是Socket对象
            //如果服务器启动后,没有客户端建立连接,此时代码中的accept就会阻塞,直到有客户建立连接了才停止阻塞
            Socket clientSocket = serverSocket.accept();

            //  2)处理这个TCP的连接
            processConnection(clientSocket);
        }
    }

    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        //clientSocket.getInetAddress().toString():获得出IP
        //clientSocket.getPort()):获得端口号
        //通过 clientSocket 来和客户端交互,先做好准备工作,获取到clientSocket中流对象
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))) {
            //getInputStream();getOutputStream():字节流
            //InputStreamReader;OutputStreamWriter:把字节流转成字符流,
            //BufferedReader;BufferedWriter:套上缓冲区

            //此处是长连接版本:一次连接的过程中,需要处理多个请求和响应
            //短连接就是去掉while循环
            while (true) {
                //    a)读取请求并解析
                String request = bufferedReader.readLine();
                //此处暗含一个信息(协议):
                //客户端发的数据必须是一个按行发送的数据(每一条数据占一行)

                //    b)根据请求计算响应
                String response = process(request);

                //    c)把响应写回给客户端(客户端要按行来读)
                bufferedWriter.write(response+"\n");
                bufferedWriter.flush();

                System.out.printf("[%s:%d] req: %s; resp: %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 {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }


}

这里的服务器方法实现中使用到了长连接,那么对应的就是短连接 长连接:一个连接中,客户端和服务器之间交互N次,直到满足一定条件在断开 短连接:一个连接中,客户端和服务器之间交互一次,交互完毕就断开连接

长连接比短连接效率更高

客户端逻辑:

1.启动客户端(一定不要绑定端口号) 2.进入主循环 a)读取用户输入内容 b)构造一个请求发送给服务器 c)读取服务器的响应数据 d)把响应数据显示到界面上

客户端:

代码语言:javascript
复制
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 = new Socket(serverIp,serverPort);
    }

    public void start(){
        System.out.println("客户端启动");
        Scanner scanner = 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 = scanner.nextLine();
                if ("exit".equals(request)){
                    break;
                }

               //  b)构造一个请求发送给服务器
                bufferedWriter.write(request + "\n");//按行写
                bufferedWriter.flush();
                
                //  c)读取服务器的响应数据
                String response = bufferedReader.readLine();

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

运行结果:

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

存在的问题

以上的服务器和客户端交互的过程中,第一个客户端发送请求,就会进入while循环,只有当第一个客户端退出的时候,第二个客户端发送的请求才会被响应,其原因就是客户端大于一个的时候,就会在accept方法中阻塞,这时,为了提高效率,也就是说为了让多个客户端一起被服务器响应,就可以利用多线程的方式

代码如下:

代码语言:javascript
复制
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(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
            t.start();
        }
    }

    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) {
                //    a)读取请求并解析
                String request = bufferedReader.readLine();

                //    b)根据请求计算响应
                String response = process(request);

                //    c)把响应写回给客户端(客户端要按行来读)
                bufferedWriter.write(response+"\n");
                bufferedWriter.flush();

                System.out.printf("[%s:%d] req: %s; resp: %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 server = new TcpThreadEchoServer(9090);
        server.start();
    }

}

(只有start()方法变了,其他均与之前代码一样)

但是这也会存在一个问题,如果客户端太多了,那么创建的线程也太多了,服务器需要频繁的创建和销毁线程,这时就可以使用标准库中的线程池

代码语言:javascript
复制
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) {
                //    a)读取请求并解析
                String request = bufferedReader.readLine();

                //    b)根据请求计算响应
                String response = process(request);

                //    c)把响应写回给客户端(客户端要按行来读)
                bufferedWriter.write(response+"\n");
                bufferedWriter.flush();

                System.out.printf("[%s:%d] req: %s; resp: %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 server = new TcpThreadPoolEchoServer(9090);
        server.start();
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-03-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • TCP
  • 存在的问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档