JavaEE|网络编程之套接字 TCP

news2024/12/22 17:21:39

文章目录

    • 一、ServerSocket API
      • 构造方法
      • 常用方法
    • 二、Socket API
      • 构造方法
      • 常用方法
      • 注意事项
    • 三、TCP中的长短连接
    • E1:一发一收(短连接)
    • E2:请求响应(短连接)
    • E3:多线程下的TCP回响服务器

说明:这部分说实话有点懵,理解上有点吃力,这里暂时先放到这,有新的认识再进行回来修改。

一、ServerSocket API

它是创建TCP服务端Socket的api

构造方法

方法签名说明
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

给服务器绑定端口。

常用方法

方法签名说明
Socket accept()开始监听创建时绑定的端口,有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void close()关闭此套接字

二、Socket API

即会给客户端和服务器使用。

构造方法

方法签名说明
Socket(String host, int port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的 进程建立连接

常用方法

方法签名说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

TCP中,socket对象,对于服务器而言,是靠accpet返回的;对于客户端而言,是靠代码内部构造的。

注意事项

  • TCP 中的ClientSocket的socket对象需要释放,而前边Server对象和UDP的都没释放,为什么这里需要呢?原因有二

    1.这里的socket声明周期比较短,UDP里边的和TCP服务器里的是要跟随整个程序的。

    2.这里的socket对象可能比较多,可能会把文件描述符表占满。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ln3qbRpA-1676879262621)(F:\typora插图\image-20230220145615579.png)]

outputStream相当于一个文件描述符(一个socket文件),通过这个对象就可以往这个文件描述符中写数据。

OutStream自身方法不方便写字符串,把流进行转换一下,用一个PW对象来表示(对应的文件还是通过一个)。

不过使用PW(打印流)写,往一个地方写,只不过的写的更方便了。

println是写,往控制台上,往网卡上……

三、TCP中的长短连接

客户端socket对象构造,会触发tcp建立连接

短连接:每次接收到数据并返回响应后,都关闭连接,即是短连接。也就是说,短连接只能一次收发数据。

长连接:不关闭连接,一直保持连接状态,双方不停的收发数据,即是长连接。也就是说,长连接可以多次收发数据。

两者区别如下:
建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。

主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。

两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。

E1:一发一收(短连接)

服务器端代码、客户端代码

public class Code04_TCPEchoServer {
    private ServerSocket serverSocket=null;//这种可以返回Socket类型的对象

    public Code04_TCPEchoServer(int port) throws IOException {
        serverSocket=new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("启动服务器成功!");
        while(true){
            Socket clientSocket=serverSocket.accept();
            //效果是接收连接
            //前提是客户端来建立连接:若有则连接,若无,则阻塞等待
            //建立连接
            processConnection(clientSocket);
        }
    }
    //使用这个方法来处理一个连接
    //这个连接对应到一个客户端,但是这里可能会设计到多次交互
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //基于上述socket进行通信
        try(InputStream inputStream=clientSocket.getInputStream();
            //由于要处理多个请求,所以也是使用循环来进行
            OutputStream outputStream=clientSocket.getOutputStream()){
           while(true){
               //1.读取请求
               Scanner scanner=new Scanner(inputStream);
               if(!scanner.hasNext()){
//                   System.out.println("当前连接已关闭!");
                   System.out.printf("[%s:%d] 客户端下线!",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
               }
               System.out.println();
               String request=scanner.next();//next读到换行符/其他空白符结束,但是不包含
               //2.根据请求构造响应
                String response=process(request);
               //3.返回响应结果
               PrintWriter printWriter = new PrintWriter(outputStream);
               printWriter.println(response);
               // 此处加上 flush 保证数据确实发送出去了.
               printWriter.flush();
               //4.打印中间结果
               System.out.printf(response);
           }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try{
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

    public static void main(String[] args) throws IOException {
        Code04_TCPEchoServer tcpEchoServer=new Code04_TCPEchoServer(1200);
        tcpEchoServer.start();
    }
}
public class Code05_TCPEchoClient {
    private Socket socket = null;//这是用来接收服务器的socket对象

    public Code05_TCPEchoClient(String serverIp, int serverPort) throws IOException {
        // Socket 构造方法, 能够识别 点分十进制格式的 IP 地址. 比 DatagramPacket 更方便.
        // new 这个对象的同时, 就会进行 TCP 连接操作.
        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()) {
            while (true) {
                // 1. 先从键盘上读取用户输入的内容
                System.out.print("> ");
                String request = scanner.next();
                if (request.equals("exit")) {
                    System.out.println("bye");
                    break;
                }
                // 2. 把读到的内容构造成请求, 发送给服务器.
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                // 此处加上 flush 保证数据确实发送出去了.
                printWriter.flush();
                // 3. 读取服务器的响应
                Scanner respScanner = new Scanner(inputStream);
                String response = respScanner.next();
                // 4. 把响应内容显示到界面上
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5BGi1OmX-1676879262623)(F:\typora插图\image-20230220151140134.png)]

这里不用println写是不行的,原因如下:

TCP协议是面向字节流的协议。接收方一次读多少个字节需要我们在数据传输中进行明确约定。

这里的next和println是相互制约的。next在等请求中的结束符。

enter是换行符,但是这里按下enter是把next里内容送上去,并没有把换行符读到。

E2:请求响应(短连接)

与UDP类似,这里是在服务器处,加上了相关的业务逻辑。

E3:多线程下的TCP回响服务器

public class Code04_TCPEchoServer {
    private ServerSocket serverSocket=null;//这种可以返回Socket类型的对象

    public Code04_TCPEchoServer(int port) throws IOException {
        serverSocket=new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("启动服务器成功!");
        ExecutorService threadPool= Executors.newCachedThreadPool();
        while(true){
            Socket clientSocket=serverSocket.accept();
            //效果是接收连接
            //前提是客户端来建立连接:若有则连接,若无,则阻塞等待
            //建立连接
            threadPool.submit(()->{
                processConnection(clientSocket);
            });
        }
    }
    //使用这个方法来处理一个连接
    //这个连接对应到一个客户端,但是这里可能会设计到多次交互
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线!",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //基于上述socket进行通信
        try(InputStream inputStream=clientSocket.getInputStream();
            //由于要处理多个请求,所以也是使用循环来进行
            OutputStream outputStream=clientSocket.getOutputStream()){
           while(true){
               //1.读取请求
               Scanner scanner=new Scanner(inputStream);
               if(!scanner.hasNext()){
//                   System.out.println("当前连接已关闭!");
                   System.out.printf("[%s:%d] 客户端下线!",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
               }
               System.out.println();
               String request=scanner.next();//next读到换行符/其他空白符结束,但是不包含
               //2.根据请求构造响应
                String response=process(request);
               //3.返回响应结果
               PrintWriter printWriter = new PrintWriter(outputStream);
               printWriter.println(response);
               // 此处加上 flush 保证数据确实发送出去了.
               printWriter.flush();
               //4.打印中间结果
               System.out.printf(response);
           }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try{
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

    public static void main(String[] args) throws IOException {
        Code04_TCPEchoServer tcpEchoServer=new Code04_TCPEchoServer(1200);
        tcpEchoServer.start();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YpC6p15H-1676879262624)(F:\typora插图\image-20230220154240639.png)]

1.修改允许多个客户端其中之后服务器只显示一个客户端上线

2.客户端1发送消息可以,客户端2发送消息没有响应【占线】

一旦客户端1一下线,客户端2立即上线。

为什么?这里需要我们结合服务器的启动代码分析

这里是多线程的问题。(其实还是有点不太懂,但具体是哪里说不上来)

当有客户端连上服务器后,代码就执行到了processConnection这个方法里的while循环,这时另一个客户端再次尝试发送请求,由于此时调到这里循环不结束,processConnection方法就结束不了,进一步也就无法再次accept了

解决办法:使用多线程。

主线程。专门负责accpet,每次收到一个连接,创建新线程,由这个新线程负责这个新的客户端

这里因为有可能频繁的申请释放线程,所以这里我们采用的是线程池。

一般的版本:

Thread t=new Thread(()->{
    processConnection(clientSocket);
});
t.start();

说明:虽然线程池的加入会一定程度上解决多个客户端的需要同时启动的效率问题。但是线程的创建与销毁始终还是比较耗时间的。一旦客户端的数量激增接近阈值,还是存在的问题的。
对此,操作系统提供了io多路复用的机制缓解。
【基于BIO(同步阻塞IO)的长连接会一直占用系统资源。对于并发要求很高的服务端系统来说,这样的消耗是不能承受的。

实际应用时,服务端一般是基于NIO(即同步非阻塞IO)来实现长连接,性能可以极大的提升。】

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

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

相关文章

SrpingBoot拦截器

一、拦截器原理 根据当前请求,进入到 HandlerExecutionChain(可以处理请求的 handler 以及 handler 的所有拦截器)根据顺序执行所有拦截器的 preHandle() 方法如果当前拦截器的 preHandler() 方法返回 true,则执行下一个拦截器的 preHandler() 方法如果当…

Java常用框架(一)

思维导图 常见知识点 一、SpringBoot 1.简单介绍一下Spring及其优缺点 1.1 概念 重量级企业开发框架EJB的替代品,通过依赖注入、面向切面编程,使用简单Java对象POJO为企业Java开发提供了相对简单的方法。 1.2 优缺点 1.2.1 优点 组件代码轻量级 …

高并发架构 第一章大型网站数据演化——核心解释与说明。大型网站技术架构——核心原理与案例分析

大型网站架构烟花发展历程1.1.1初始阶段的网站构架1.1.2应用服务和数据服务分离1.1.3使用缓存改善网络性能1.1.4使用应用服务器集群改善网站的并发处理能力1.1.5数据库读写分离1.1.6使用反向代理和cdn加速网站相应1.1.1初始阶段的网站构架 大型网站都是由小型网站一步步发展而…

音视频基础之音频常见名词

采样频率 每秒钟采样的点的个数。常用的采样频率有: 22000(22kHz): 无线广播。 44100(44.1kHz):CD音质。 48000(48kHz): 数字电视,DVD。 96000&am…

【C++提高编程】C++全栈体系(二十二)

C提高编程 第三章 STL - 常用容器 五、stack容器 1. stack 基本概念 概念:stack是一种先进后出(First In Last Out,FILO)的数据结构,它只有一个出口 栈中只有顶端的元素才可以被外界使用,因此栈不允许有遍历行为 栈中进入数据称为 — 入…

startForegroundService与startService 使用浅析

一. 了解服务(Service)的概念 service是安卓开发中一个很重要组件,意为“服务”。与我们常见的activity不同,“服务”是默默的在背后进行工作的,通常,它用于在后台为我们执行一些耗时,或者需要…

【机器学习】Adaboost

1.什么是Adaboost AdaBoost(adapt boost),自适应推进算法,属于Boosting方法的学习机制。是一种通过改变训练样本权重来学习多个弱分类器并进行线性结合的过程。它的自适应在于:被前一个基本分类器误分类的样本的权值会…

二叉树最大深度、最小深度、以及n叉树的最大深度

1.N 叉树的最大深度 给定一个 N 叉树,找到其最大深度。 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。 N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。 示例 1: 输入&#xff…

多因子模型(MFM)

多因子模型(Muiti-Factor M: MFM)因子投资基础CAPM (资本资产定价模型)APT套利定价理论截面数据 & 时间序列数据 & 面板数据定价误差 α\alphaαalpha 出现的原因线性多因子模型Fama-French三因子模型三因子的计算公式利用alpha大小进行购买股票…

centos误删python2后怎么重新安装

此教程为离线安装 一. 先查询系统版本 cat /proc/version Linux version 3.10.0-1127.el7.x86_64 (mockbuildkbuilder.bsys.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-39) (GCC) ) #1 SMP Tue Mar 31 23:36:51 UTC 2020 二. 安装python2.7.5(已知原python版…

【C++修炼之路】19.AVL树

每一个不曾起舞的日子都是对生命的辜负 AVL树前言:一.AVL树的概念二.AVL树的结构2.1 AVL树节点的定义2.2 AVL树的结构2.3 AVL树的插入2.4 AVL树的验证2.5 AVL树的删除(了解)三.AVL树的旋转(重要)3.1 左单旋3.2 右单旋3.3 左右双旋3.4 右左双旋…

2023年 ChatGPT 研究报告

第一章 行业概况 ChatGPT是由OpenAI 团队研发创造,OpenAI是由创业家埃隆马斯克、美国创业孵化器Y Combinator总裁阿尔特曼、全球在线支付平台PayPal联合创始人彼得蒂尔等人于2015年在旧金山创立的一家非盈利的AI研究公司,拥有多位硅谷重量级人物的资金支…

最简单得方法解决TCP分包粘包问题

如何用最简单的方法解决TCP传输中的分包粘包问题? 首先需要说明一点,分包粘包等等一系列的问题并不是协议本身存在的问题,而是程序员在写代码的时候,没有搞清楚数据的边界导致的。 看个简单的例子,TCP客户端不断的向服…

SAP 解析固定资产的减值功能

SAP固定资产的减值功能 若固定资产出现减值迹象,也就是固定资产的可收回金额小于账面价值时,就要计提固定资产减值准备。 分录: 借:资产减值损失(损益科目) 贷:固定资产减值准备(资…

骨传导耳机是不是智商税?骨传导耳机真的不伤耳吗?

很多人对骨传导耳机是具有一定的了解,但是对骨传导耳机还是有一定的刻板印象,那么骨传导耳机到底是不是智商税呢?主要还是要从骨传导耳机传声原理上讨论。 骨传导耳机是属于固体传声的一种方式,通过骨骼传递声音,在使用…

一种基于强化学习的自动变道机动方法

文章目录摘要前言相关的工作方法论动作空间奖励函数设计Q学习仿真结果结论摘要 变道是一项至关重要的车辆操作,需要与周围车辆协调。建立在基于规则的模型上的自动换道功能可能在预定义的操作条件下表现良好,但在遇到意外情况时可能容易失败。在我们的研…

谈一谈正向代理和反向代理?

谈一谈正向代理和反向代理?什么是代理服务器(Proxy Serve)?为什么使用代理服务器?什么是正向代理什么是反向代理正向代理和反向代理的区别正向代理的应用反向代理的应用什么是代理服务器(Proxy Serve&#…

android kotlin 协程(四) 协程间的通信

android kotlin 协程(四) 协程间的通信 学完本篇你将会了解到: channelproduceactorselect 先来通过上一篇的简单案例回顾一下挂起于恢复: fun main() {val waitTime measureTimeMillis {runBlocking<Unit> {println("main start") // 1 // …

学会这些Jmeter插件,才能设计出复杂性能测试场景

为什么要使用jmeter线程组插件呢&#xff1f; jmeter自带的线程组插件模拟的压测场景非常有限&#xff0c;当需要模拟复杂压测场景的时候&#xff0c;推荐大家使用jmeter线程组插件。 如何下载jmeter线程组插件呢&#xff1f; 早期版本的jmeter可以针对我们需要的扩展功能&a…

软考案例分析题精选

试题一&#xff1a;阅读下列说明&#xff0c;回答问题1至问题4&#xff0c;将解答填入答题纸的对应栏内。某公司中标了一个软件开发项目&#xff0c;项目经理根据以往的经验估算了开发过程中各项任务需要的工期及预算成本&#xff0c;如下表所示&#xff1a;任务紧前任务工期PV…