【网络编程】TCP流套接字编程 | Socket类 | ServerSocket类 | 文件资源泄露 | TCP回显服务器 | 网络编程

文章目录

        • TCP流套接字编程
          • 1.ServerSocket类
          • 2.Socket类
          • 3.文件资源泄露
          • 4.**TCP回显服务器**


TCP流套接字编程

​ ServerSocket类和Socket类这两个类都是用来表示socket文件(抽象了网卡这样的硬件设备)。

TCP是面向字节流的,传输的基本单位是byte 字节。和UDP不同,UDP传输的单位是数据报。

1.ServerSocket类

给服务器使用的类,用这个类来绑定端口号

2.Socket类

既会给服务器用,又会给客户端用

因为TCP的有连接的,会保存对端的连接。不用像UDP那样每次发送都要手动在send方法中指定目标地址。

​ TCP的建立连接,由系统内核自动负责完成的。客户端,要发起“建立连接”的动作。服务器,要把建立好的连接从内核中拿到应用程序里。如果客户端和服务器建立连接,服务器的应用程序不需要任何操作,系统内核直接完成了连接建立的流程(三次握手),完成流程后,就会在内核的队列中排队(每个ServerSocket都会有这个队列)。应用程序要想和这个客户端进行通信,就需要通过按accept方法,把内核队列里已经建立好连接的对象,拿到应用程序中。

符合生产者消费者模型

            //通过accept方法,把内核中已经建立好的连接拿到应用程序中
            //建立连接的细节流程,都是内核自动完成的,应用程序只需要用现成的
            Socket clientSocket = serverSocket.accept();

返回的是一个Socket对象

ServerSocket专门用来接收连接, Socket类型的 clientSocket用来后续的客户端进行通信。

马路上招揽人头的销售 和 售楼部的员工

        clientSocket.getInputStream();
        clientSocket.getOutputStream();

InputStream和OutputStream就是字节流,TCP是传输内容同样是字节流。借助这两个对象,完成数据的“发送”和“接收”。

通过InputStream进行read操作,就是“接收”

通过OutputStream进行write操作,就是“发送”

3.文件资源泄露

​ 由于DatagramSocket和ServerSocket在程序中,只有一个对象,生命周期都是贯穿整个程序的。随时有请求过来,都会使用到。不涉及到一直频繁申请导致的泄露问题。

​ 但是clientSocket,每个循环中,每有一个新的客户端来建立连接,都会创建出新的clientSocket。并且这个Socket最多使用到该客户端断开连接。如果此时有很多客户端频繁建立连接,就会出现文件资源泄露的问题。

        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {

这里只是关闭了clientSocket自带的流对象,并没有关闭本身。需要手动进行关闭

        }finally {
            clientSocket.close();
            //进行clientSocket的关闭
            //processConnection方法就是在处理一个连接,这个方法执行完毕,连接就处理完了。
        }
4.TCP回显服务器

服务器

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) {
            //通过accept方法,把内核中已经建立好的连接拿到应用程序中
            //建立连接的细节流程,都是内核自动完成的,应用程序只需要用现成的
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }

    /**
     * 通过这个方法,来处理当前的连接
     *
     * @param clientSocket
     */
    public void processConnection(Socket clientSocket) throws IOException {
        //1.进入方法后,先打印日志,表示有客户端连接
        System.out.printf("[%s,%d] 客户端上线\n", clientSocket.getInetAddress(), clientSocket.getPort());
        //2.进行数据交互
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            //使用try(){}来自动关闭close

            //由于客户端发送来的数据,可能是“多条数据”,进行循环处理
            while (true){
                Scanner scanner = new Scanner(inputStream);
                if (!scanner.hasNext()){
                    //如果没有下一条数据,连接断开,循环结束
                    System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                String request = scanner.next();//以空白符为本次读取字节流结束的标记
                //1.读取请求并解析
                String response = process(request);
                //2.根据请求,计算响应

                //3.把响应写回到客户端:
                //可以把String转成字节数组,写进 OutputStream
                //也可以使用PrintWriter把OutputStream包裹一下,写进字符串
                PrintWriter printWriter = new PrintWriter(outputStream);
                //此处的println是写入到outputStream对应的流对象中,也就是写入到clientSocket里面,
                // 数据就通过网络发送出去了。发送给当前连接的另外一端。
                //因为之前连接本身就记录了对方的地址和端口,在写数据时直接写数据内容即可,不需要手动指定发给谁。
                printWriter.println(response);//写就是输出的体现形式
                //此处使用println带有\n,也是为了后续客户端可以使用Scanner.next来读取数据
                printWriter.flush();
                //同时,需要刷新缓冲区,确保数据从内存中写进网卡

                System.out.printf("[%s:%d] req = %s ,resp = %s\n",
                        clientSocket.getInetAddress(),clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            clientSocket.close();
            //进行clientSocket的关闭
            //processConnection方法就是在处理一个连接,这个方法执行完毕,连接就处理完了。
        }
    }
    public String process(String request){
        //回显服务器
        return request;
    }

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

}
服务器启动
[/127.0.0.1,63510] 客户端上线
[/127.0.0.1:63510] req = 你好 ,resp = 你好
[/127.0.0.1:63510] req = hello ,resp = hello
[/127.0.0.1:63510] 客户端下线
[/127.0.0.1,63523] 客户端上线
[/127.0.0.1:63523] req = 6666666 ,resp = 6666666

客户端

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp, int serverPort) throws IOException {
        //需要在创建Socket的同时,和服务器“建立连接”,告诉Socket,服务器在哪。
        //当new这个对象时,操作系统的内核就会完成三次握手的具体细节,完成建立连接的过程
        socket = new Socket(serverIp, serverPort);
    }

    public void start() {
        System.out.println("客户端启动");
        Scanner scanner = new Scanner(System.in);

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            PrintWriter writer = new PrintWriter(outputStream);
            Scanner scannerNetwork = new Scanner(inputStream);
            while (true) {
                //1.从控制台读取用户输入的内容
                System.out.println("->");
                String request = scanner.next();
                //2.把字符串作为请求,发送给服务器
                writer.println(request);
                //客户端发的时候有换行,和服务器的scanner.next匹配
                writer.flush();
                //3.从服务器读取响应
                String response = scannerNetwork.next();//和服务器的PrintWrite.println匹配
                //4.把响应打印到界面
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
        client.start();
    }
}
客户端启动
->
6666666
6666666
->

如果同时启动两个客户端,同时连接服务器。先启动的客户端正常运行,另一个后启动的客户端,无法与服务器进行交互

​ 在第一个客户端过来后,accept就返回得到了一个clientSocket,进入了processConnection方法。又进入了一个while循环,反复处理客户端发来的请求数据,如果客户端没发请求,服务器的代码就会阻塞在scanner.hasNext。此时第二个客户端也过来建立连接,连接建立成功后,连接对象就会在内核的队列里面,等待accept把连接取出来,在代码中处理。此时无法第一时间执行到第二次accept

第一个循环是循环获取连接,第二个循环是循环获取请求。第一个客户端就会使服务器处于processConnection方法内部, 此时卡在了方法中的循环,无法第二次执行accept方法。只有第一个客户端退出, 方法中的循环才能结束,从而第二次执行 accept

  • 要解决这个问题,就要在处理第一个客户端请求的过程中,让代码能够快速的第二次执行accept

​ 让两个循环能够“并发”执行,各执行各的,不会因为进入循环而影响另一个循环。所以,需要创建一个新的线程,由线程来执行processConnection方法。主线程就可以继续执行下次accept。新线程负责processConnection方法内部的循环。每有一个客户端,就要分配一个线程。

一个人是无法同时完成拉客 和 介绍楼盘的工作的

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
            //通过accept方法,把内核中已经建立好的连接拿到应用程序中
            //建立连接的细节流程,都是内核自动完成的,应用程序只需要用现成的
            Socket clientSocket = serverSocket.accept();
            //直接执行processConnection方法,会导致服务器不能处理客户端
            //创建线程调用。
            Thread thread = new Thread(()->{
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
            thread.start();
        }
    }

新的线程负责在processConnection里面来循环处理客户端的请求。

  • 如果有很多客户端,频繁的建立、断开连接。就会导致服务器频繁的创建销毁线程,造成大量开销。可以使用线程池来进行优化。
    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService service = Executors.newCachedThreadPool();
        while (true) {
            //通过accept方法,把内核中已经建立好的连接拿到应用程序中
            //建立连接的细节流程,都是内核自动完成的,应用程序只需要用现成的
            Socket clientSocket = serverSocket.accept();
            //直接执行processConnection方法,会导致服务器不能处理客户端
            //创建线程调用。
//            Thread thread = new Thread(()->{
//                try {
//                    processConnection(clientSocket);
//                } catch (IOException e) {
//                    throw new RuntimeException(e);
//                }
//            });
//            thread.start();
            //使用线程池进行优化
            service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    }
  • 但是如果出现巨量的线程。可以用协程来解决。除了携程,可以使用IO多路复用/IO多路转接的方法来处理(用一个线程,同时处理多个客户端的socket)->NIO

点击移步博客主页,欢迎光临~

偷cyk的图

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MySQL B+索引的工作原理及应用

引言 在数据库系统中,索引是优化查询、提高性能的关键技术之一。特别是在MySQL数据库中,B树索引作为最常用的索引类型,对数据库性能有着至关重要的影响。本文旨简单解析MySQL中B树索引的工作原理,帮助学生朋友们更好地理解和利用…

Kubernetes学习-核心概念篇(一) 初识Kubernetes

🏷️个人主页:牵着猫散步的鼠鼠 🏷️系列专栏:Kubernetes渐进式学习-专栏 🏷️个人学习笔记,若有缺误,欢迎评论区指正 目录 1. 前言 2. 什么是Kubernetes 3. 为什么需要Kubernetes 3.1. 应…

ArcGIS批量寻找图层要素中的空洞

空洞指的是图层中被要素包围所形成的没有被要素覆盖的地方,当图层要素数量非常庞大时,寻找这些空洞就不能一个一个的通过目测去寻找了,需要通过使用工具来实现这一目标。 一、【要素转线】工具 利用【要素转线】工具可以将空洞同图层要素处于…

HTML网页自动播放背景音乐和全屏背景图代码

HTML网页自动播放背景音乐的代码 背景音乐代码及分析代码的应用背景图代码及分析下期更新预报 背景音乐代码及分析 能使网站上自动循环的背景音乐代码如下&#xff1a; <audio src"music.mid" autostart"true" loop"true" hidden"true…

python使用opencv对图像的基本操作(2)

13.对多个像素点进行操作&#xff0c;使用数组切片方式访问 img[i,:] img[j,:] #将第j行的数值赋值给第i行 img[-2,:]或img[-2] #倒数第二行 img[:,-1] #最后一列 img[50:100,50:100] #50-100行&#xff0c;50-100列&#xff08;不包括第100行和第100列&#xff09; img[:100…

怎么用PHP语言实现远程控制电器

怎么用PHP语言实现远程控制电器呢&#xff1f; 本文描述了使用PHP语言调用HTTP接口&#xff0c;实现控制电器&#xff0c;通过控制电器的电源线路来实现电器控制。 可选用产品&#xff1a;可根据实际场景需求&#xff0c;选择对应的规格 序号设备名称厂商1智能WiFi通断器AC3统…

Ubuntu16.04搭建webrtc服务器

本人查阅无数资料,历时3周搭建成功 一、服务器组成 AppRTC 房间+Web服务器 https://github.com/webrtc/apprtcCollider 信令服务器,在AppRTC源码里CoTurn coturn打洞+中继服务器 Nginx 服务器,用于Web访问代理和Websocket代理。AppRTC 房间+Web服务器使用python+js语言 App…

Elcomsoft iOS Forensics Toolkit: iPhone/iPad/iPod 设备取证工具包

天津鸿萌科贸发展有限公司是 ElcomSoft 系列取证软件的授权代理商。 Elcomsoft iOS Forensics Toolkit 软件工具包适用于取证工作&#xff0c;对 iPhone、iPad 和 iPod Touch 设备执行完整文件系统和逻辑数据采集。对设备文件系统制作镜像&#xff0c;提取设备机密&#xff08…

【机器学习】集成学习:强化机器学习模型与创新能的利器

集成学习&#xff1a;强化机器学习模型预测性能的利器 一、集成学习的核心思想二、常用集成学习方法Bagging方法Boosting方法Stacking方法 三、集成学习代表模型与实现四、总结与展望 在大数据时代的浪潮下&#xff0c;机器学习模型的应用越来越广泛&#xff0c;而集成学习作为…

Centos7 yum报错 Could not resolve host: mirrorlist.centos.org

yum install报如下错误 应该是网络问题&#xff0c;检查是不是这个文件配置错了导致连不上网 /etc/sysconfig/network-scripts/ifcfg-ens33 注意里面的DNS配置 可以在服务器ping一下百度 ping wwww.baidu.com

QX2303L50F输入电压0.7V~5V输出电压5V非同步DCDC最大输出电流800mA

前言 外围较简单&#xff0c;价格较低&#xff0c;小电流输出时&#xff0c;最低启动电压0.8V 输出电压有多种&#xff0c;封装有多种 参考价格约0.2元 QX2303典型应用电路图 QX2303封装 QX2303丝印 1.概述 QX2303 系列产品是一种高效率、低纹波、工作频率高的 PFM 升压 DC-…

战胜DALL·E 3和 Midjourney的开源模型来了——playground-v2.5

这是首次超越闭源AI模型的开源时刻。Playground AI 前不久宣布Playground v2.5正式开源。Playground v2.5 是美学质量方面最先进的开源模型&#xff0c;特别关注增强的颜色和对比度、改进的多纵横比生成以及改进的以人为中心的精细细节。并且在美学质量方面树立了新标准&#x…

从单按键状态机思维扫描引申到4*4矩阵按键全键无冲扫描,一步一步教,超好理解,超好复现(STM32程序例子HAL库)

目前大部分代码存在的问题 ​ 单次只能对单个按键产生反应&#xff1b;多个按键按下就难以修改&#xff1b;并且代码耦合度较高&#xff0c;逻辑难以修改&#xff0c;对于添加长按&#xff0c;短按&#xff0c;双击的需求修改困难。 解决 16个按键按下无冲&#xff0c;并且代…

AIGC技术带来的安全与隐私问题探讨

如何看待AIGC技术&#xff1f; 简介&#xff1a;探讨AIGC技术的发展现状和未来趋势。提醒&#xff1a;在发布作品前&#xff0c;请把不需要的内容删掉。 方向一&#xff1a;技术应用 机遇和挑战 AIGC国内场景应用图谱 方向二&#xff1a;伦理与风险 垄断与隐私风险 AI民主化诉…

Linux--MyMiniTry--Vim

首先下载好vim,我们可以按以下的方式进行光标的移动&#xff08;也可以回车进行换行&#xff09; &#xff08;--> 进入教程&#xff09; &#xff08;初始的时候没有文本&#xff0c;你怎么按都没有用&#xff09; &#xff08;我们要先按 i &#xff0c;进行插入文本才…

前端单元测试的艺术:专业化策略与Vue项目高效实践指南

单元测试是软件开发中的基石&#xff0c;尤其对于前端领域&#xff0c;它为保证代码质量、提升开发效率、强化项目稳定性提供了不可或缺的支持。本文将深入剖析单元测试的核心理念&#xff0c;揭示其在前端开发中的独特价值&#xff0c;并提炼出一套专业且高效的实践策略&#…

全志ARM-官方库SDK安装和验证

进入界面&#xff0c;输入以下指令 git clone https://github.com/orangepi-xunlong/wiringOP //下载源码 cd wiringOP //进入文件夹 sudo ./build clean //清除编译信息 sudo ./build …

电容的理论基础

目录 1.电容的本质&#xff1a; 2.电容量的大小 2.1电容的单位 2.2电容的决定式 ​编辑3.电容的特点 5.电容器的类型 6.电容实际的电路模型 7.安装方法 ​编辑8.电容值 9.电容的耐压、封装 10.阻抗-频率特性 11.频率特性 12.等效串联电组ESR 13.电容器的温度特性…

[C++基础学习]----03-程序流程结构之选择结构详解

前言 本篇都是在自学C基础知识的基础上&#xff0c;加上本身理解所完成的&#xff0c;为了便于记录学习情况&#xff0c;使用更加容易理解的话术描述出来&#xff0c;方便使用。 在C程序中&#xff0c;选择结构&#xff08;也称为条件结构&#xff09;用于根据特定的条件执行不…

python 使用flask_httpauth和pyjwt实现登录权限控制

最近需要用到&#xff0c;学习了一下记录 首先安装依赖 pip install Flask-HTTPAuth pyjwt passlib Welcome to Flask-HTTPAuth’s documentation! — Flask-HTTPAuth documentation Welcome to PyJWT — PyJWT 2.8.0 documentation Passlib 1.7.4 documentation — Passl…