Socket套接字(TCP流)篇

Socket套接字

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回显服务器

java 复制代码
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客户端

java 复制代码
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对象关闭就可以了.
java 复制代码
实际上客户端往往会发送大量的请求,我们可以使用线程池的方式来实现高并发.
但是即使使用了线程池,避免了频繁创建销毁线程.
毕竟是每个客户端对应一个线程,如果客户端很多,就需要创建大量线程,
对于服务器是开销很大的.
我们可以引入"IO多路复用"的方式解决多并发,利用"节流",是消耗的硬件资源更少了,减少
了线程的数量.

小结

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

相关推荐
SamDeepThinking8 小时前
裁掉那个差程序员后,给你看团队里高手的代码:这个习惯,希望你有
java·后端·程序员
朕瞧着你甚好9 小时前
技术雷达 & Java 集成评估报告 — Apache Tika 3.3.1
java·ai编程
MacroZheng10 小时前
短短几天,暴涨2.8万Star!又一款编程神器开源!
java·人工智能·后端
SamDeepThinking10 小时前
函数式编程:用BiFunction消除多类型分支的代码重复
java·后端·面试
Flittly1 天前
【AgentScope Java新手村系列】(16)从RAG到多路检索
java·spring boot·spring
小兔崽子去哪了1 天前
Java 生成二维码解决方案
java·后端
人活一口气1 天前
从JVM调优到MCP协议:Java全栈技术体系深度总结与企业级架构实践
java·spring boot
NE_STOP1 天前
Vibe Coding -- 完整项目案例实操
java
荣码1 天前
GraphRAG:普通RAG只能回答"点"的问题,我踩了4个坑才搞懂
java·python
SimonKing1 天前
Google第三方授权登录
java·后端·程序员