红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Java编程笔记
  4. 正文

Java编程笔记25:TCP

2022年10月31日 186点热度 0人点赞 0条评论

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));
        }
    }
}

服务端主要是创建一个用于监听的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();
        }
​
    }
}

打包

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

<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
​
    <groupId>org.example</groupId>
    <artifactId>server</artifactId>
    <version>1.0-SNAPSHOT</version>
​
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.0.2</version>
                <configuration>
                    <archive>
                        <addMavenDescriptor>false</addMavenDescriptor>
                        <manifest>
                            <useUniqueVersions>true</useUniqueVersions>
                            <addClasspath>false</addClasspath>
                            <mainClass>cn.icexmoon.java.note.ch25.Main</mainClass>
                        </manifest>
                        <manifestEntries>
                            <Class-Path>./</Class-Path>
                        </manifestEntries>
                    </archive>
                    <!-- 过滤掉不希望包含在jar中的文件  -->
                    <excludes>
                        <exclude>*.xml</exclude>
                    </excludes>
                    <!-- 这里不做举例了 -->
                    <includes>
                        <include>**/*.class</include>
                        <include>**/*.properties</include>
                    </includes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

客户端的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();
                    }
                }
            });
        }
    }
    // ...
}

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

  • Java多线程相关内容可以阅读Java学习笔记21:并发(1) - 红茶的个人站点 (icexmoon.cn)。

  • 注意不要将Socket client = ss.accept();这行代码放在子线程中执行,那样会导致for循环无限创建子线程并对端口进行监听,进而导致内存爆掉(相当刺激)。而正常逻辑应该是主线程监听,一旦有客户端连接进来,就开一个子线程进行具体服务。

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

  • 这个应用实际上依然有所不足,比如客户端必须是发送>接收>再发送>再接收这样的,实际上客户端的发送和接收应当分两个线程来分别处理,回显不应当被发送所阻塞,有兴趣的童鞋可以自行修改完善。

  • 本来打算把TCP和UDP结合写一章的,但UDP不打算写了,它并不像TCP那样应用广泛。感兴趣的可以阅读UDP编程 - 廖雪峰的官方网站 (liaoxuefeng.com)。

谢谢阅读。

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

参考资料

  • maven-jar-plugin的使用及详解懒鸟一枚的博客-CSDN博客maven-jar-plugin

  • TCP编程 - 廖雪峰的官方网站 (liaoxuefeng.com)

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: java tcp
最后更新:2022年10月31日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇
下一篇 >

文章评论

取消回复

*

code

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号