• Java编程笔记25:TCP


    Java编程笔记25:TCP

    5c9c3b3b392ac581.jpg

    图源:Java Switch语句(用法详解)-java教程-PHP中文网

    TCP和UDP通信可以说是网络应用的起点,原理方面的内容不在本文讨论范围内,这里直接展示如何用Java创建一个基于TCP的CS结构的网络应用。

    Server

    package cn.icexmoon.java.note.ch25;
    // ...
    public class Main {
        public static void main(String[] args) throws IOException {
            ServerSocket ss = new ServerSocket(6666);
            System.out.println("server is starting...");
            while (true) {
                Socket client = ss.accept();
                System.out.println("get connection from " + client.getRemoteSocketAddress());
                dealRequest(client);
            }
        }
    
        private static void dealRequest(Socket clientSocket) throws IOException {
            String addr = clientSocket.getRemoteSocketAddress().toString();
            try {
                InputStream is = clientSocket.getInputStream();
                OutputStream os = clientSocket.getOutputStream();
                InputStreamReader isr = new InputStreamReader(is);
                BufferedReader br = new BufferedReader(isr);
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
                try {
                    do {
                        String msg = br.readLine();
                        System.out.println(String.format("[%s]received message: %s", addr, msg));
                        if (msg == null || msg.trim().isEmpty()) {
                            continue;
                        }
                        bw.write(new StringBuilder(msg).reverse().toString() + "\n");
                        bw.flush();
                        if (msg.toLowerCase().equals("bye")) {
                            break;
                        }
                    }
                    while (true);
                } finally {
                    System.out.println("stream is closed.");
                    br.close();
                    bw.close();
                }
            } catch (IOException e) {
                System.out.println(String.format("[%s]connect is closed by IOException.", addr));
                e.printStackTrace();
            } finally {
                clientSocket.close();
                System.out.println(String.format("[]connect is closed normally.", addr));
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    服务端主要是创建一个用于监听的Socket,然后用Socket.accept方法监听请求,在收到请求后进行处理。

    示例是一个回文程序,在收到字符串后将其倒序回显。

    需要注意的是,这里是通过换行符作为每条消息间隔进行读取和回显,而BufferedReader.readLine方法读取的字符串会自动去掉结尾的换行符,所以在发送时需要手动在结尾添加一个换行符。否则就会出现这边发出了消息,另一边迟迟不接收的情况。

    Client

    package cn.icexmoon.java.note.ch25.client;
    // ...
    public class Main {
        public static void main(String[] args) throws IOException {
            Socket socket = new Socket("localhost", 6666);
            sendMsg(socket);
        }
    
        private static void sendMsg(Socket socket) throws IOException {
            try {
                InputStream is = socket.getInputStream();
                OutputStream os = socket.getOutputStream();
                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
                BufferedReader stdReader = new BufferedReader(new InputStreamReader(System.in));
                System.out.println("server is connected.");
                try {
                    do {
                        System.out.println("please enter some messages:");
                        String msg = stdReader.readLine();
                        bw.write(msg + "\n");
                        bw.flush();
                        System.out.println(String.format("msg is send to server."));
                        System.out.println("ready to get msg back from server...");
                        String msgBack = br.readLine();
                        System.out.println("get back msg: " + msgBack);
                        if (msgBack.toLowerCase().equals("eyb")) {
                            break;
                        }
                    }
                    while (true);
                } finally {
                    br.close();
                    bw.close();
                    System.out.println("stream is closed.");
                }
            } catch (IOException e) {
                e.printStackTrace();
                socket.close();
                System.out.println("connect is closed caused IOException.");
            } finally {
                socket.close();
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    打包

    需要分别打包后运行客户端和服务端,这里我选择使用mvn进行打包,具体的pom.xml文件可以参考:

    
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0modelVersion>
    
        <groupId>org.examplegroupId>
        <artifactId>serverartifactId>
        <version>1.0-SNAPSHOTversion>
    
        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
        properties>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.pluginsgroupId>
                    <artifactId>maven-jar-pluginartifactId>
                    <version>3.0.2version>
                    <configuration>
                        <archive>
                            <addMavenDescriptor>falseaddMavenDescriptor>
                            <manifest>
                                <useUniqueVersions>trueuseUniqueVersions>
                                <addClasspath>falseaddClasspath>
                                <mainClass>cn.icexmoon.java.note.ch25.MainmainClass>
                            manifest>
                            <manifestEntries>
                                <Class-Path>./Class-Path>
                            manifestEntries>
                        archive>
                        
                        <excludes>
                            <exclude>*.xmlexclude>
                        excludes>
                        
                        <includes>
                            <include>**/*.classinclude>
                            <include>**/*.propertiesinclude>
                        includes>
                    configuration>
                plugin>
            plugins>
        build>
    project>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    客户端的POM文件与之类似,只要修改入口文件类名即可。

    现在只要先运行服务端jar包,再运行客户端jar包就能看到效果,这里不再详述。

    多线程

    上边的应用实际上是一个单线程结构,服务端每次只能接受一个客户端连接,并作出响应。

    下面将这个程序改写为多线程:

    package cn.icexmoon.java.note.ch25;
    // ...
    public class Main {
        public static void main(String[] args) throws IOException {
            ServerSocket ss = new ServerSocket(6666);
            System.out.println("server is starting...");
            ExecutorService es = Executors.newCachedThreadPool();
            while (true) {
                Socket client = ss.accept();
                final Socket finalClient = client;
                es.execute(new Runnable() {
    
                    @Override
                    public void run() {
                        try {
                            System.out.println("get connection from " + finalClient.getRemoteSocketAddress());
                            dealRequest(finalClient);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
        }
    	// ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    这里用了一个线程池来执行多线程,并且将创建连接后的响应业务代码放在Runable中。

    • Java多线程相关内容可以阅读Java学习笔记21:并发(1) - 红茶的个人站点 (icexmoon.cn)
    • 注意不要将Socket client = ss.accept();这行代码放在子线程中执行,那样会导致for循环无限创建子线程并对端口进行监听,进而导致内存爆掉(相当刺激)。而正常逻辑应该是主线程监听,一旦有客户端连接进来,就开一个子线程进行具体服务。

    现在应用可以服务多个客户端了。

    • 这个应用实际上依然有所不足,比如客户端必须是发送>接收>再发送>再接收这样的,实际上客户端的发送和接收应当分两个线程来分别处理,回显不应当被发送所阻塞,有兴趣的童鞋可以自行修改完善。
    • 本来打算把TCP和UDP结合写一章的,但UDP不打算写了,它并不像TCP那样应用广泛。感兴趣的可以阅读UDP编程 - 廖雪峰的官方网站 (liaoxuefeng.com)

    谢谢阅读。

    最终的完整示例代码可以从java-notebook/ch25 (github.com)获取。

    参考资料

  • 相关阅读:
    【科学文献计量】pybibx论文原文精读与对照翻译
    服务网格和性能优化:介绍如何通过服务网格提高微服务架构的性能和可扩展性
    执法装备管理系统DW-S304的概念与特点
    SpringBoot 工程打包并运行
    Postgresql更改字段默认值、设置字段默认值、删除字段默认值
    kubernetes部署jenkins
    48.Java Lambda表达式
    算法分析与设计——要求根据给定的正整数n计算第n个斐波那契数。
    C语言实验三 选择结构程序设计
    iMazing 3中文版功能介绍免费下载安装教程
  • 原文地址:https://blog.csdn.net/hy6533/article/details/127620149