【Socket】之TCP数据报套接字

1. 介绍下API

1.1 ServerSocket API

这是创建TCP服务端Socket的API。

构造方法 方法说明
ServerSocket(int port) 创建一个服务端流套接字Socket,并绑定到指定端口
普通方法 方法说明
ServerSocket.accept() 开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close() 关闭此套接字

1.2 Socket API

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,用来接收返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据
的。

构造方法 方法说明
Socket(String host, int port) 创建一个客户端套接字Socket,并于对应的ip的主机上,对应端口进程建立连接
普通方法 方法说明
InputStream getInputStream() 返回此套接字的输入流
OutputStream getOutputStream() 返回此套接字的输出流

1.3 TCP中长短连接

TCP发送数据时,需要先建立连接,什么时候关闭连接就决定是短连接还是长连接:

短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。

长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。

对于长短连接区别:

  • 建立连接、关闭连接耗时:短连接每次请求响应都需要建立连接,关闭连接;而长连接只需要建立第一次连接,之后的请求、响应可以直接传输。相对来说建立连接,关闭连接也是耗时的,长连接效率更高。
  • 主动发送请求不同:短连接一般是客户端主动向服务器端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
  • 两者场景不同,短连接适用于客户端请求频率不高的场景,如浏览网页;长连接适用于客户端与服务器通信频繁的场景,如聊天室,实时打游戏等。

2. 代码案例

2.1 服务器端(TcpEchoServer)

public class TcpEchoServer {
    private ServerSocket serverSocket = null;   // 外场销售

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

    public void start() throws IOException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        System.out.println("服务器启动!");
        while (true) {
            Socket clientSocket = serverSocket.accept();   // 内场销售好多个
            // 如果直接调用,该方法会导致这个循环的二次执行,导致accept不及时
            // 创建新的线程,用新线程调用 processConnection
            // 每次来一个新的客户端都搞一个新的的线程即可!
//            Thread t = new Thread(()->{
//                try {
//                    processConnection(clientSocket);
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            });
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    // 通过这个方法来处理一个连接
    // 读取请求
    // 根据请求计算相应
    // 把响应返回给客户端
    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线!n",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        // try() 这种写法, ()中允许写多个流对象。 使用 ; 来分割
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream); // 字符流
            PrintWriter printWriter = new PrintWriter(outputStream);
            while (true) {
                // 1,读取请求
                if (!scanner.hasNext()){
                    // 读取的流到结尾(对端关闭了)
                    System.out.printf("[%s:%d] 客户端下线!n",clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;
                }
                // 直接使用 scanner 读取一段字符串
                String request = scanner.next();
                // 2. 根据请求计算相应
                String response = process(request);
                // 3. 把相应返回客户端
                printWriter.println(response);
                printWriter.flush();
                System.out.printf("[%s:%d] req: %s; resq: %sn", clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            clientSocket.close();
        }
    }

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

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

2.2 客户端(TcpEchoClient)

public class TcpEchoClient {

    private Socket socket = null;

    public TcpEchoClient(String serverIp, int port) throws IOException {
        socket = new Socket(serverIp, port);
    }

    public void start() {
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            Scanner scannerFromSocket = new Scanner(inputStream);
            while (true) {
                // 1.从键盘上读取用户输入的内容
                System.out.print("->");
                String request = scanner.next();
                // 2.把读取的内容构造成请求,发送给服务器
                // 注意这里发送 是带有换行
                printWriter.println(request);
                printWriter.flush();  // 此处需要手动刷新缓冲区
                // 3.从服务器读取相应内容
                String response = scannerFromSocket.next();
                // 4.把相应结果显示到控制台上
                System.out.printf("req: %s, resq: %sn", request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
        tcpEchoClient.start();
    }
}

2.3 流程图

下面用一个流程图,来讲解代码是如何配合工作的
在这里插入图片描述

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码

)">
< <上一篇
下一篇>>