简单实现基于UDP与TCP的回显服务器(二)

简介: 简单实现基于UDP与TCP的回显服务器

TCP 版的回显服务器


需要用到的 api


1.ServerSocket API


ServerSocket 是创建TCP服务端 Socket 的 API (给服务器用的)


ServerSocket 一定要绑定具体端口号. (服务器得绑定的端口号才能提供服务)


主要用到的构造方法 :


ServerSocket(int port)


创建一个服务端流套接字Socket,并绑定到指定端口.


主要用到的方法 :


Socket accept()


开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端 Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待.


void close()


关闭此套接字 (大多情况下, ServerSocket 的生命周期都会跟随整个程序, 所以不调 close 也问题不大)


accept 就是接受的意思.

客户端主动发起连接, 服务端被动接受连接.

其实 tcp 的连接是在内核就完成了的, 这里的 accept 是应用层层面的接受, 并返回一个 Socket 对象, 通过这个对象就可以与客户端进行交互了.


2.Socket API


Socket 即会给服务端用, 也会给客户端用.

不管是客户端还是服务端Socket,都是双方建立连接以后,保存对端信息,用来与对方收发数据的。


Socket 和 DatagramSocket 类似, 都是构造的时候指定一个具体的端口, 让服务器绑定该端口.


主要用到的构造方法 :


Socket(String host, int port)


两个参数表示 ip 和 端口号, TCP是有连接的, 在客户端 new Socket 时, 就会尝试和指定 ip 端口的目标建立连接.


主要用到的方法 :


InetAddress getInetAddress()


返回套接字所连接的地址


InputStream getInputStream()


返回此套接字的输入流


OutputStream getOutputStream()


返回此套接字的输出流


void close()


关闭此套接字


TCP 是面向字节流的, 所以我们可以通过上述字节流对象进行数据传输.

从 InputStream 读数据, 就相当于从网卡接收.

往 OutputStream 写数据, 就相当于从网卡发送.


服务器


public class TcpEchoServer {
    // serverSocket 就是外场拉客的小哥哥
    // serverSocket 只有一个.
    private ServerSocket serverSocket = null;
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        // 服务器不止是对一个客户端进行服务
        // 加上 while 循环, 是让服务器随时可以获取到新的客户端的请求连接
        while(true) {
            // clientSocket 就是内场服务的小姐姐.
            // clientSocket 会给每个客户端都分配一个.(可以通过它来获取客户端的信息)
            Socket clientSocket = serverSocket.accept();
            // 我们对多客户端的交互应该是并行的
            // 所以创建线程来对每个客户端进行响应
            Thread t = new Thread(() -> {
                try {
                    // 该方法就是对客户端进行交互了
                    processConnection(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            t.start();
        }
    }
    private void processConnection(Socket clientSocket) throws IOException {
        // 提醒客户端已上线.
        System.out.printf("[%s : %d] 客户端已上线!\n",
                clientSocket.getInetAddress(),clientSocket.getPort());
        // 将输入,输出流放入 try() 中, 可以不用考虑资源释放.
        // 通过 clientSocket 获得输入, 输出流.
        try(InputStream inputStream = clientSocket.getInputStream();
                OutputStream outputStream = clientSocket.getOutputStream()) {
            // 因为 tcp 是面向字节流的, 所以为了方便读取, 给输入流套一个外壳
            // Scanner 是面向字符流.
            Scanner scanner = new Scanner(inputStream);
            // 同样的, 为了方便写入, 给输出流套一个壳
            // PrintWriter 是面向字符流
            PrintWriter printWriter = new PrintWriter(outputStream);
            // 因为服务器与客户端交互不是一次就完了, 所以得加个循环
            while(true) {
                // 判断是否还有数据在输入流中,如果没有了就认为客户端下线了,退出循环
                if(!scanner.hasNext()) {
                    System.out.printf("[%s : %d] 客户端已下线!\n",
                            clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                // 通过 scanner 读取网卡(客户端的请求)
                String request = scanner.next();
                // 通过请求计算响应
                String response = process(request);
                // 将响应写入缓存区, 当缓存区满了 就会写入网卡
                // 因为 Scanner 读取是不会读空格和回车的
                // 所以写入的时候要加换行, 以便客户端区分请求和响应
                printWriter.println(response);
                // 因为写入缓存区时, 缓存区并不一定会刷新
                // 所以我们要手动刷新, 将缓存中的数据刷入网卡
                printWriter.flush();
                System.out.printf("[%s %d] req: %s resp: %s\n", clientSocket.getInetAddress(),clientSocket.getPort(),
                                    request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 因为 clientSocket 在客户端下线后就没用了,所以要释放
            // serverSocket 之所以不要释放, 是因为它的生命周期伴随整个程序
            // 程序结束, 它自然被释放了
            clientSocket.close();
        }
    }
    //计算响应
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(8080);
        tcpEchoServer.start();
    }
}

客户端


public class TcpEchoClient {
    private Socket socket = null;
    public TcpEchoClient(String serverIP, int port) throws IOException {
        // 这个操作相当于让客户端和服务器建立 tcp 连接.
        // 这里的连接连上了, 服务器的 accept 就会返回.
        socket = new Socket(serverIP,port);
    }
    public void start() {
        // 这个 scanner 是用来读取用户输入内容的
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
                OutputStream outputStream = socket.getOutputStream()) {
            PrintWriter printWriter = new PrintWriter(outputStream);
            // 这里的 scannerFromSocket 是用来读取网卡(服务器响应)
            Scanner scannerFromSocket = new Scanner(inputStream);
            // 通过循环来对服务器进行多次请求
            while(true) {
                // 提示用户输入
                System.out.printf("-> ");
                // 读取键盘输入
                String request = scanner.next();
                // 将键盘输入写入缓存, 当缓存满了就自动刷新 并写入网卡
                // 注意带回车, 不然服务器读不到空白符,会一直读
                printWriter.println(request);
                // 手动刷新缓存, 将数据写入网卡
                printWriter.flush();
                // 读取服务器写入网卡的响应
                String response =  scannerFromSocket.next();
                System.out.printf("req: %s resp: %s\n", request,response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) throws IOException {
        TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1", 8080);
        tcpEchoClient.start();
    }
}


服务端 :

254931b45dc74d81a2f424eb2244613b.png



客户端 :

3be9bf79ebd0433294a28a411cf91dac.png


当然了, 我们还可以多启动几个客户端, 与服务器进行交互.


服务端 :

c255409b15e042c88d7dbe42a52877ac.png


对服务器进行改进(使用线程池)


这里主要是修改 start 方法里的线程创建, 就单独拿出来修改 :


public void start() throws IOException {
        while(true) {
            ExecutorService executorService = Executors.newCachedThreadPool();
            Socket clientSocket = serverSocket.accept();
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

效果还是一样的.


TCP 版本的字典客户端和字典服务器


其实和 UDP 版的基本一样, 只需要对服务端进行修改 :


public class TcpDictServer extends TcpEchoServer{
    private HashMap<String,String> dict = new HashMap<>();
    public TcpDictServer(int port) throws IOException {
        super(port);
        dict.put("pen","笔");
        dict.put("dog","狗");
        dict.put("cat","猫");
    }
    public String process(String request) {
        return dict.getOrDefault(request,"未找到该单词");
    }
    public static void main(String[] args) throws IOException {
        TcpDictServer tcpDictServer = new TcpDictServer(8080);
        tcpDictServer.start();
    }
}

0304c7f4d29847638a5c976086d1001f.png

d336443f2e1d45c092247310b825a3a5.png


相关文章
|
10月前
|
网络协议 安全 网络安全
什么是TCP/UDP/HTTP?它们如何影响你的内网穿透体验?
数据的传输离不开各种协议,它们就像现实世界中的交通规则,规定了数据如何打包、寻址、传输和接收。对于使用内网穿透的用户来说,理解TCP、UDP和HTTP这些基础协议的特点,能帮助你更好地理解其性能表现,并选择最适合的配置方案。
|
网络协议 安全 网络安全
详细阐述 TCP、UDP、ICMPv4 和 ICMPv6 协议-以及防火墙端口原理优雅草卓伊凡
详细阐述 TCP、UDP、ICMPv4 和 ICMPv6 协议-以及防火墙端口原理优雅草卓伊凡
754 2
|
网络协议 Java 开发工具
全平台开源即时通讯IM框架MobileIMSDK:7端+TCP/UDP/WebSocket协议,鸿蒙NEXT端已发布,5.7K Stars
全平台开源即时通讯IM框架MobileIMSDK:7端+TCP/UDP/WebSocket协议,鸿蒙NEXT端已发布,5.7K Stars
815 1
|
网络协议 Java API
【JavaEE】——Udp翻译器的实现(回显服务器)
网络编程,DatagramSocket 和 DatagramPacket类,回显服务器,服务器实现,客户端实现,
|
监控 网络协议 网络性能优化
不再困惑!一文搞懂TCP与UDP的所有区别
本文介绍网络基础中TCP与UDP的区别及其应用场景。TCP是面向连接、可靠传输的协议,适用于HTTP、FTP等需要保证数据完整性的场景;UDP是无连接、不可靠但速度快的协议,适合DNS、RIP等对实时性要求高的应用。文章通过对比两者在连接方式、可靠性、速度、流量控制和数据包大小等方面的差异,帮助读者理解其各自特点与适用场景。
|
存储 网络协议 安全
用于 syslog 收集的协议:TCP、UDP、RELP
系统日志是从Linux/Unix设备及网络设备生成的日志,可通过syslog服务器集中管理。日志传输支持UDP、TCP和RELP协议。UDP无连接且不可靠,不推荐使用;TCP可靠,常用于rsyslog和syslog-ng;RELP提供可靠传输和反向确认。集中管理日志有助于故障排除和安全审计,EventLog Analyzer等工具可自动收集、解析和分析日志。
1401 2
|
9月前
|
弹性计算 运维 安全
阿里云轻量应用服务器与云服务器ECS啥区别?新手帮助教程
阿里云轻量应用服务器适合个人开发者搭建博客、测试环境等低流量场景,操作简单、成本低;ECS适用于企业级高负载业务,功能强大、灵活可扩展。二者在性能、网络、镜像及运维管理上差异显著,用户应根据实际需求选择。
800 10
|
9月前
|
弹性计算 ice
阿里云4核8g服务器多少钱一年?1个月和1小时价格,省钱购买方法分享
阿里云4核8G服务器价格因实例类型而异,经济型e实例约159元/月,计算型c9i约371元/月,按小时计费最低0.45元。实际购买享折扣,1年最高可省至1578元,附主流ECS实例及CPU型号参考。
854 8
|
9月前
|
运维 安全 Ubuntu
阿里云渠道商:服务器操作系统怎么选?
阿里云提供丰富操作系统镜像,涵盖Windows与主流Linux发行版。选型需综合技术兼容性、运维成本、安全稳定等因素。推荐Alibaba Cloud Linux、Ubuntu等用于Web与容器场景,Windows Server支撑.NET应用。建议优先选用LTS版本并进行测试验证,通过标准化镜像管理提升部署效率与一致性。
|
9月前
|
存储 监控 安全
阿里云渠道商:云服务器价格有什么变动?
阿里云带宽与存储费用呈基础资源降价、增值服务差异化趋势。企业应结合业务特点,通过阶梯计价、智能分层、弹性带宽等策略优化成本,借助云监控与预算预警机制,实现高效、可控的云资源管理。