【Java网络编程】 三

news2024/11/16 5:36:27

本文主要介绍了TCP版本的回显服务器的编写。

一.TCP版本回显服务器

1.服务器

服务器的实现流程

1.接收请求并解析

2.根据请求计算出响应(业务流程)

3.把响应返回给客户端

代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Tcp版本的回显服务器
 *
 * 服务器
 */

public class TcpEchoServer {
    private ServerSocket serverSocket=null;
    //使用线程池:此处不应该创建固定线程数目的线程池
    private ExecutorService service= Executors.newCachedThreadPool();


    public TcpEchoServer(int port) throws IOException {
        serverSocket=new ServerSocket(port);
    }

    //这个操作会绑定端口
    public void start() throws IOException {
        System.out.println("服务器启动");
        while(true){
            //从内核中的连接获取到应用程序中
            /**
             *
             * accept是把内核中已经建立好的连接,给拿到应用程序中,但是这里的返回值并非是
             * 一个connection对象,而只是一个socket对象,这个socket对象就像一个耳麦
             * 可以说话,也可以听到对方的声音
             */
            Socket clientSocket=serverSocket.accept();


            //单个线程,不方便完成这里的一边拉客,一边介绍;就需要多线程
            //多线程负责拉客
            //每次有一个新的客户端,都创建一个新的线程去服务

//            Thread t=new Thread(()->{
//                try {
//                    processConnection(clientSocket);
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//
//            });
//            t.start();


            //使用线程池也可以解决
            service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });


        }
    }

    //通过这个方法来处理一个连接的逻辑
    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;
                }
                /**
                 *这里暗含了一个约定,客户端发过来的请求
                 * 得是文本数据,同时还要包含空白符
                 */
                String request=scanner.next();
                //next一直读到空白符结束(换行,回车,空格,制表符,等)

                //2.根据请求计算响应
                String response=process(request);

                //3.把响应写给客户端
                /**
                用printWriter把outputstream包裹一下,方便进行收发数据
                 */
                PrintWriter writer=new PrintWriter(outputStream);

                /**
                 * 使用printWriter的println方法,把响应写给客户端,结尾\n,
                 * 是为了方便客户端读取响应,使用scanner.next读取
                 */
                writer.println(response);

                /**
                 * 还需要加一个刷新缓冲区操作
                 * io操作比较有开销,相比于访问内存,进行io次数越多,程序的速度就越慢
                 *
                 * 作为一块内存作为缓冲区,写数据的时候,先写到缓冲区里
                 * 存一波数据,统一进行io
                 * printwriter内置了缓冲区
                 * 手动刷新,确保这里的数据是真的通过网卡发出去了,而不是残留在缓冲区里
                 *
                 * 加上flush是更稳妥的做法。
                 */
                writer.flush();


                //打印日志
                System.out.printf("[%s:%d] rep:%s , resp:%s \n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                        request,request);


            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            /**
             * socek有很多,每来一个连接,就会有一个连接
             */
            //在finally中加上close操作,确保当前socket及时关闭。
            clientSocket.close();
        }

    }

    public String process(String request){
        return request;
    }

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


}

说明

1.循环之后,服务器要做的事情不是读取客户端的请求,而是先处理客户端的连接,因为TCP是面向连接的。

2.一个服务器中,要对应很对客户端,服务器内核中有很多客户端连接。虽然内核中连接很多,但是应用程序还是要一个一个的处理。

我们可以把内核中的连接看成 待办事项, 待办事项在队列中,应用程序需要一个一个完成这些任务

要完成任务,就要先取任务 ; 因此在处理请求之前,要先通过accept()从内核中获得请求


我们可以把TCP连接的生成和获得连接的过程看作一个生产者消费者模型。

socket中会包含一个管理连接的队列,这个队列是每个socket都有一份,相互之间不会混淆。


3.当服务器执行到accept时,此时如果客户端还没来,accept就会阻塞,直到有客户端连接成功为止。

accept是把内核中已经建立好的连接,拿到应用程序中,返回值是一个socket对象,这个对象就像一个耳麦,既可以说话,也可以听到对反的声音。

也就是通过socket对象就可以和对方进行网络通信


此时这个回显服务器中,涉及到两种socket

1.ServerSocket

相当于是在店外揽客的服务员,揽到客人之后,交给店内的服务员

2.clientSocket

店内负责招待的服务员

4.

scanner和printwriter没有close,并不会导致文件资源暴露

流对象中持有的资源的两个部分

1)内存(对象销毁,内存回收)

2)   文件描述符  scanner和printwriter持有的是inputstream和outpustream的引用

5.服务器怎么感知到客户端下线的

hasNext()在客户端没有发请求的时候,也会阻塞,一直阻塞到客户端发了请求,或者是客户端退出,它就返回了

2.客户端

基本实现流程:

1.从控制台读取用户的输入

2.把输入的内容构造成请求发送给服务器

3.从服务器读取响应

4.把响应显示到控制台上

代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * Tcp版本的服务器
 *
 * 客户端
 */

public class TcpEchoClient {
    private Socket socket=null;

    //要和服务器通信,就需要先知道,服务器所在的位置
    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        //这个new操作就完成了tcp连接的建立
        socket = new Socket(serverIp, serverPort);

    }

    private void start() {
        System.out.println("客户端启动");
        Scanner scannerConsole=new Scanner(System.in);
        try(InputStream inputStream=socket.getInputStream();
            OutputStream outputStream=socket.getOutputStream()){
            while(true){
                //1.从控制台输入字符串
                System.out.print("->");
                String request=scannerConsole.next();

                //2.把请求发送给服务器
                PrintWriter printWriter=new PrintWriter(outputStream);
                printWriter.println(request);

                /**
                 * 不要忘记flush
                 * 确保数据真的发送出去了
                 */
                printWriter.flush();

                //3.从服务器读取响应
                Scanner scannerNetwork=new Scanner(inputStream);
                String response=scannerNetwork.next();

                //4.把响应打印出来
                System.out.println(response);


            }
        } catch (IOException e) {
            e.printStackTrace();
        }

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


}

二.问题和解决方法

1.服务器问题

1.关闭当前的socket!!放在finally当中

客户端会有很多,而每个客户端都有一个socket,如果不关闭会消耗大量的资源。

2.(重点!上面的代码是修改后的!)

两个以上(包含)客户端发来的请求,服务器无法正确地处理。

这是因为当第一个客户端来了,accept会返回,进入processConnection

在处理这个客户端请求过程中,即使第二个客户端来了,也无法第二次调用accept

解决办法:改进成多线程

主线程:负责accept,和客户端建立连接

然后创建新的线程,让新的线程去处理客户端的各种请求

更好的办法:使用线程池!

这样可以避免频繁创建和销毁线程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1131152.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算法训练 第四周

一、二分查找 本题给我们提供了一个有n个元素的升序整形数组nums和一个目标值target,要求我们找到target在nums数组中的位置,并返回下标,如果不存在目标值则返回-1。nums中的所有元素不重复,n将在[1,10000]之间&#x…

高效视频剪辑:批量调整视频尺寸的技巧与步骤

对于许多新手和初学者来说,视频剪辑可能是一项令人望而生畏的任务。但是,有了正确的工具,比如固乔剪辑助手,即使你是个新手,也能轻松、高效地完成视频剪辑工作。下面就是关于如何使用固乔剪辑助手来批量调整视频尺寸的…

Python第三方库 - Flash(python web框架)

1 Flask 1.1 认识Flask Web Application Framework( Web 应用程序框架)或简单的 Web Framework( Web 框架)表示一个库和模块的集合,使 Web 应用程序开发人员能够编写应用程序,而不必担心协议,线…

部署springboot打包不打包配置文件,配置文件为外部配置文件使用 (真实场景)

场景 springboot项目打包的时候,想要将配置文件不要打包到jar包中,作为外部使用,不同环境,配置文件的配置值肯定不一样,真实场景。 比如 开发环境数据库配置为 127.0.0.1:3306 , 测试环境 122.0.2.2&am…

火山引擎 LAS Spark 升级:揭秘 Bucket 优化技术

更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 文章介绍了 Bucket 优化技术及其在实际业务中的应用,包括 Spark Bucket 的基本原理,重点阐述了火山引擎湖仓一体分析服务 LAS(下…

记录--vue3 + mark.js | 实现文字标注功能

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 页面效果 具体实现 新增 1、监听鼠标抬起事件,通过window.getSelection()方法获取鼠标用户选择的文本范围或光标的当前位置。2、通过 选中的文字长度是否大于0或window.getSelection().isC…

drf-过滤、排序、异常处理、自封装Response

过滤 过滤就是根据路由url?后的信息过滤出符合?后条件的数据而非全部,比如…/?nameweer就是只查name是weer的数据,其余不返回。 1、安装:pip3 install django-filter2、注册:在settings.py中的app中注册django-filt…

MYSQL(事务+锁+MVCC+SQL执行流程)理解(2)

一)MYSQL中的锁(知识补充) 可以通过In_use字段来进行判断是否针对于表进行加了锁 1)对于undo log日志来说:新增类型的,在事务提交之后就可以清除掉了,修改类型的,事务提交之后不能立即清除掉这些日志会用于mvcc只有当没有事务用到该版本信息时…

列表推导式、集合推导式、字典推导式、生成器

列表推导式 可以与三目运算符搭配使用 dict1 {name: "by", "age": 20} dict2 {name: "ss", "age": 25} dict3 {name: "sa", "age": 24} dict4 {name: "xs", "age": 27} list1 [dict1, …

Python 算法高级篇:分治算法的原理与应用

Python 算法高级篇:分治算法的原理与应用 1. 什么是分治算法?2. 分治算法的应用2.1 归并排序2.2 快速排序2.3 最大子数组问题2.4 汉诺塔问题 3. 代码示例3.1 分治算法求幂 4. 总结 分治算法是一种重要的算法设计技巧,它将一个大问题分解为多个…

(R900567512)4WE6D6X/OFEG24N9K4液压电磁换向阀

(R900567512)4WE6D6X/OFEG24N9K4液压电磁换向阀特点: ▶▶ 具有三位四通,二位四通或二位三通的方向设计 ▶▶ 高功率线圈 ▶▶ 油口安装面符合 DIN 24340 形式 A ▶▶ 油口安装面符合 ISO 4401-03-02-0-05和 NFPA T3.5.1 R2-2002 D03 ▶▶ 带…

虹科 | 解决方案 | 汽车示波器 索赔管理方案

索赔管理 Pico汽车示波器应用于主机厂/供应商与服务店/4S店的协作,实现产品索赔工作的高效管理;同时收集的故障波形数据,便于日后的产品优化和改进 故障记录 在索赔申请过程中,Pico汽车示波器的数据记录功能可以用于捕捉故障时的…

搭建zlmediakit和wvp_pro

zlmediakit使用zlmediakit/zlmediakit:master镜像 wvp_pro使用648540858/wvp_pro,可参照https://github.com/648540858/wvp-GB28181-pro wvp_pro官方https://doc.wvp-pro.cn/#/ 刚开始我找了个docker镜像运行,后来播放页面一直加载,最后就用了…

软件设计包括了四个既独立又相互联系的活动

软件设计包括了四个既独立又相互联系的活动

广告联盟是什么?app开发者如何选择广告联盟?

在广告变现过程中,广告联盟是必不可少的因素,广告联盟是一种在线广告服务模式,它将广告主和网站主(或博主、应用开发者等)联系在一起,通过广告联盟平台的中介服务,将广告主的广告投放到网站主的…

sigmoid和softmax函数的区别;神经网路常用的损失函数以及对应的应用场景;softmax的作用

一、Sigmoid函数和Softmax函数是常用的激活函数,它们在神经网络中经常用于实现非线性映射。 Sigmoid函数:Sigmoid函数常用于二分类问题,它将输入的取值范围映射到一个介于0和1之间的输出。Sigmoid函数的公式是 f(x) 1 / (1 exp(-x))&#x…

Python的web自动化学习(一)Selenium库的工作原理

Python的web自动化学习(一)Selenium库的工作原理 首发2023-07-31 11:01云中随心而记 后续会根据持续学习来更新,大家一起学习 引言: Selenium是一个流行的自动化测试工具,用于模拟和控制浏览器行为,常用于…

美创科技列为IDC中国数据安全市场代表厂商

近日,国际权威IT咨询机构IDC发布《中国数据安全市场发展趋势,2023》报告,报告针对中国数据安全市场的发展现状进行调研,明确了最终用户数据安全建设的痛点、难点,阐述了市场中各技术服务提供商的服务方案和优势。 美创…

24 个常见的 Docker 疑难杂症处理技巧!!!记得点赞收藏

1. Docker 迁移存储目录 默认情况系统会将 Docker 容器存放在 /var/lib/docker 目录下 [问题起因] 今天通过监控系统,发现公司其中一台服务器的磁盘快满了,随即上去看了下,发现 /var/lib/docker 这个目录特别大。由上述原因,我们都…

【openGauss】一主一备实现主备节点切换实验(switchover、failover)

openGauss在运行过程中,数据库管理员可能需要手工对数据库节点做主备切换。例如发现数据库节点主备failover后需要恢复原有的主备角色,或怀疑硬件故障需要手动进行主备切换。 级联备机不能直接转换为主机,只能先通过switchover或者failover成…