Socket套接字(TCP流)篇

发布于:2024-04-27 ⋅ 阅读:(20) ⋅ 点赞:(0)

ServerSocket

ServerSocket是创建TCP服务端Socket的API.

ServerSocket构造方法:

方法签名 方法说明
ServerSocket(int port) 创建一个服务端套接字Socket,并绑定到指定端口

ServerSocket方法:

方法签名 方法说明
Socket accept() 开始监听指定端口(创建时绑定的端口),当有客户端连接时,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接 ,否则阻塞等待
void close() 关闭套接字

Socket

Socket是客户端Socket,或者是服务端中收到客户端建立连接的请求后,返回的服务端Socket.

Socket的构造方法:

方法签名 方法说明
Socket(String host, int port) 创建一个客户端套接字Socket, 并与对应主机,对应ip建立连接

Socket方法:

方法签名 方法说明
InetAddress getInetAddress() 返回套接字连接的地址
InputStream getInputStream() 返回套接字的输入流
OutputStream getOutputStream() 返回套接字的输出流

案例演示

TCP回显服务器

public class TcpEchoServer {
    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. 处理客户端的连接
            // 把内核中的连接获取到应用程序中了,连接相当于一个个任务,放在队列中
            // 类似于生产者消费者模型
            // 如果没有客户端连接成功,就会进入阻塞.
            // 从队列中出序号最前面的连接, 与三次握手没有关系
            Socket clientSocket = serverSocket.accept();
            Thread thread = new Thread(() -> {
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            thread.start();
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        // 读取请求 计算响应  返回性相应
        // Socket 对象内部包含了两个字节流对象, 可以把这俩字节流对象获取到, 完成后续的读写工作
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            // 一次连接中,可能会涉及多次请求和响应
            while (true) {
                // 1. 读取请求并解析, 为了读取方便,直接使用scanner
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()) {
                    System.out.printf("[%s:%d] 客户端下线\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                    break;
                }
                // 客户端发送的数据,得是文本数据
                // next()读取数据,一直读到空白符结束(换行,回车,空格,制表符,垂直制表符)
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回给客户端,把outputStream用PrintWriter包裹一下,方便发送数据
                PrintWriter writer = new PrintWriter(outputStream);
                // 使用 PrintWriter的println方法,把响应返回给客户端
                // 在结尾加上'\n', 客户端就可以用scanner.next()访问了
                writer.println(response);
                // 刷新缓冲区
                writer.flush();
                // 日志, 打印当前的请求信息
                System.out.printf("[%s:%d] req: %s, resp: %s\n",clientSocket.getInetAddress().toString(), clientSocket.getPort(),
                        request, response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // 不关闭会内存泄漏
            // 在finally中加入close方法,确保socket被关闭
            clientSocket.close();
        }
    }

    public String process(String request) {
        // 回显服务器
        return request;
    }

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

TCP客户端

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        // new操作后,就完成了tcp连接的建立
        // 建立了三次握手
        socket = new Socket(serverIp, serverPort);
    }

    public void start() throws IOException {
        //
        System.out.println("客户端启动");
        Scanner scannerConsole = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
            while (true) {
                System.out.println("-> ");
                // 1. 从控制台输入字符串
                String request = scannerConsole.next();
                // 2. 把请求发送给服务器
                PrintWriter writer = new PrintWriter(outputStream);
                // 使用 println 带上换行. 后续服务器读取请求, 就可以使用 scanner.next 来获取了
                writer.println(request);
                // 确保数据发出去了
                writer.flush();
                // 3. 从服务器读取响应
                Scanner scannerNetWork = new Scanner(inputStream);
                String response = scannerNetWork.next();
                // 4. 把响应打印出来
                System.out.println(response);
            }
        }
    }

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

Scanner对象和PrintWriter没有进行close,是否有文件资源泄漏呢>
答案是不会的.
流对象持有的资源分为两部分:

  1. 内存(对象销毁,内存就回收了)
  2. 文件描述符
    while循环结束,内存被销毁.Scanner和PrintWriter没有文件描述符,有的是InputStream和OutputStream,准确说是Socket对象,把Socket对象关闭就可以了.
实际上客户端往往会发送大量的请求,我们可以使用线程池的方式来实现高并发.
但是即使使用了线程池,避免了频繁创建销毁线程.
毕竟是每个客户端对应一个线程,如果客户端很多,就需要创建大量线程,
对于服务器是开销很大的.
我们可以引入"IO多路复用"的方式解决多并发,利用"节流",是消耗的硬件资源更少了,减少
了线程的数量.

小结

本博客总结了Socket套接字(TCP流)的相关知识,有收获的小伙伴多多支持.


网站公告

今日签到

点亮在社区的每一天
去签到