【网络编程】(TCP流套接字编程 ServerSocket API Socket API 手写TCP版本的回显服务器 TCP中的长短连接)

news2024/10/7 10:24:54

文章目录

  • 网络编程
    • TCP流套接字编程
      • ServerSocket API
      • Socket API
      • TCP中的长短连接
      • 手写TCP版本的回显服务器


网络编程

TCP流套接字编程

TCP提供的API主要是两个类:ServerSocket 和 Socket .
TCP不需要一个类来表示"TCP数据报"因为TCP不是以数据报为单位进行传输的.是以字节的方式,流式传输

ServerSocket API

ServerSocket 是专门给服务器使用的Socket对象.

ServerSocket 构造方法:

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

ServerSocket 方法:

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

在这里插入图片描述

voidclose() 关闭此套接字

Socket API

Socket是既会给客户端使用,也会给服务器使用.

Socket 构造方法:在服务器这边是有accept返回的.在客户端这边,在代码里构造的时候制定一个IP和端口号.(此处的IP和端口是服务器IP和端口)有了这个信息就能和服务器进行连接了.

Socket(String host, intport)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接.
在这里插入图片描述

Socket 方法:

进一步通过Socket对象获取内部的流对象,借助流对象来进行发送/接收.

InetAddress getInetAddress() 返回套接字所连接的地址
InputStream getInputStream() 返回此套接字的输入流
OutputStream getOutputStream() 返回此套接字的输出流

TCP中的长短连接

在TCP有连接的场景下,针对连接这个概念,有两种典型的表现形式.

  1. 短连接:客户端每次给服务器发消息,先建立连接,发送请求;下次再发送,则重新建立连接.

  2. 长连接:客户端建立连接后,连接先不断开,然后再次发送请求,读取响应;再发送请求,读取响应;若干轮之后,客户端确实短时间之内不再需要使用这个连接了,此时再断开.

两者区别:

  1. 建立连接、关闭连接的耗时:短连接每次请求、响应都需要建立连接,关闭连接;而长连接只需要第一次建立连接,之后的请求、响应都可以直接传输。相对来说建立连接,关闭连接也是要耗时的,长连接效率更高。
  2. 主动发送请求不同:短连接一般是客户端主动向服务端发送请求;而长连接可以是客户端主动发送请求,也可以是服务端主动发。
  3. 两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于客户端与服务端通信频繁的场景,如聊天室,实时游戏等。

拓展了解:
在这里插入图片描述

手写TCP版本的回显服务器

在这里插入图片描述

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) {
            //使用这个clientSocket和具体的客户端进行交流
            Socket clinentSocket = serverSocket.accept();
            processConnection(clinentSocket);
        }
    }
    //使用这个方法处理一个连接 一个连接对应一个客户端
    //可能涉及到多次交互
    private void processConnection(Socket clinentSocket) {
        //获得客户端IP和端口
        System.out.printf("[%s:%d] 客户端上线!\n",clinentSocket.getInetAddress().toString(),clinentSocket.getPort());
        // 基于上述socket对象和客户端进行通信
        try(InputStream inputStream = clinentSocket.getInputStream();
            OutputStream outputStream = clinentSocket.getOutputStream()){
            //由于要处理多个请求和响应,也是使用循环
            while (true){
                //1. 读取请求
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    //没有下一个数据 说明读完了 (客户端关闭了连接)
                    System.out.printf("[%s:%d] 客户端下线!\n",clinentSocket.getInetAddress().toString(),clinentSocket.getPort());
                    break;
                }
                //此处使用next是一直读取到换行符/空格/其他空白符结束
                //但是结果不包含上述空白符
                String request = scanner.next();
                //2. 根据请求响应
                String response = process(request);
                //3. 返回响应结果
                //outputStream没有write String这样的功能 可以把String里的字节数组拿出来 进行写入
                //也可以用字符流来转换一下
                PrintWriter printWriter = new PrintWriter(outputStream);
                //此处使用println来写入 让结果中带有一个\n 换行 方便对端来接收解析
                printWriter.println(response);
                //flush用来刷新缓冲区 保证当前写入的数据 确实是发送出去了
                printWriter.flush();
                System.out.printf("[%s:%d] req: %s; resp: %s \n",clinentSocket.getInetAddress().toString(),clinentSocket.getPort(),
                        request,response);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                clinentSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}

TCP客户端

public class TcpEchoClient {
    private Socket socket = null;

    public 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.println("> ");
                String request = scanner.next();
                if(request.equals("exit")){
                    System.out.println("goodbye");
                    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 {
        TcpEchoClient tcpEchoClient = new TcpEchoClient("127.0.0.1",9090);
        tcpEchoClient.start();
    }
}

问题:

当前代码里使用的是println来发送数据,println会在发送的数据后面自动带上\n换行.如果不适用println,而是使用print(不带\n换行) 上面的代码是否能正确运行?

不能正确运行,没有\n是不行的.TCP协议是面向字节流的协议(字节流特性:一次读多少个字节都行).接收方无法知道我们一次要多多少字节,这就需要我们在数据传输中进行明确的约定.此处代码中,隐式约定了使用\n来作为当前代码的请求/响应分割约定.
在这里插入图片描述
但是我们发现上面的代码还是存在一些问题的:
在这里插入图片描述
为解决上面的问题,我们使用多线程,主线程负责进行accept.每次接收到一个连接,创建新线程 ,由这个新的线程负责处理这个新的客户端.每个线程是独立的执行流.每个独立的执行流是各自执行各自的逻辑,彼此之间是并发关系.不会出现一边阻塞而影响到另一边执行的情况.

如果服务器,客户端特别多,很多客户端频繁建立连接就需要频繁创建/销毁线程了, 此时单纯的多线程的处理方法也不行了,所以我们就得用线程池来进行处理.

在这里插入图片描述

如果客户端非常多而且客户端连接都迟迟不断开,就会导致机器上有很多线程.如果一个服务器有几千个客户端就得是几千个线程.这个事情对机器来说,是一个很大的负担.这个时候为了解决单机支持更大量客户端的问题即C10M问题.就想办法让一个线程,处理多个客户端连接.为了解决这个问题操作系统底层提出了IO多路复用,IO多路转接.就是利用充分等待时间,做别的事情.我们给这个线程安排个集合,这个集合就放了一堆连接.这个线程就负责监听这个集合,哪个连接有数据来了,线程就来处理哪个连接,虽然连接有很多,总还是有先有后的.操作系统提供了一些原生API ,select,poll,epoll.在Java中,提供了一组NIO这样类,就封装了上述多路复用的API.

改进后: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("启动服务器!");
        //此处使用CachedThreadPool ,使用FixedThreadPool不太合适(线程数不太应该是固定的 )
        ExecutorService threadPool = Executors.newCachedThreadPool();
        while (true) {
            //使用这个clientSocket和具体的客户端进行交流
            Socket clinentSocket = serverSocket.accept();
            //此处使用多线程处理
            /*Thread t = new Thread(()->{
                processConnection(clinentSocket);
            });*/
            //使用线程池
            threadPool.submit(()->{
                processConnection(clinentSocket);
            });
        }
    }
    //使用这个方法处理一个连接 一个连接对应一个客户端
    //可能涉及到多次交互
    private void processConnection(Socket clinentSocket) {
        //获得客户端IP和端口
        System.out.printf("[%s:%d] 客户端上线!\n",clinentSocket.getInetAddress().toString(),clinentSocket.getPort());
        // 基于上述socket对象和客户端进行通信
        try(InputStream inputStream = clinentSocket.getInputStream();
            OutputStream outputStream = clinentSocket.getOutputStream()){
            //由于要处理多个请求和响应,也是使用循环
            while (true){
                //1. 读取请求
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    //没有下一个数据 说明读完了 (客户端关闭了连接)
                    System.out.printf("[%s:%d] 客户端下线!\n",clinentSocket.getInetAddress().toString(),clinentSocket.getPort());
                    break;
                }
                //此处使用next是一直读取到换行符/空格/其他空白符结束
                //但是结果不包含上述空白符
                String request = scanner.next();
                //2. 根据请求响应
                String response = process(request);
                //3. 返回响应结果
                //outputStream没有write String这样的功能 可以把String里的字节数组拿出来 进行写入
                //也可以用字符流来转换一下
                PrintWriter printWriter = new PrintWriter(outputStream);
                //此处使用println来写入 让结果中带有一个\n 换行 方便对端来接收解析
                printWriter.println(response);
                //flush用来刷新缓冲区 保证当前写入的数据 确实是发送出去了
                printWriter.flush();
                System.out.printf("[%s:%d] req: %s; resp: %s \n",clinentSocket.getInetAddress().toString(),clinentSocket.getPort(),
                        request,response);
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                clinentSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public String process(String request) {
        return request;
    }
    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}

在这里插入图片描述

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

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

相关文章

电机故障诊断(python程序,模型为CNN结合LSTM)

代码运行环境要求:TensorFlow版本>2.4.0,python版本>3.6.0 运行效果视频:电机故障诊断(python代码)_哔哩哔哩_bilibili 1.电机常见的故障类型有以下几种: 轴承故障:轴承是电机运转时最容…

用于永磁同步电机驱动器的自适应SDRE非线性无传感器速度控制(MatlabSimulink实现)

目录 💥1 概述 📚2 运行结果 🎉3 参考文献 🌈4 Matlab代码&Simulink仿真实现 💥1 概述 本文方法基于状态依赖的里卡蒂方程(SDRE)控制技术及其梯度型神经网络的实时计算方法,允许…

Gorm中Belong to 自引用

//评论 type Comment struct {gorm.ModelContent string //内容ParentID uint //评论的父级评论idParentComment *Comment gorm:"foreignkey:ParentID;references:ID" //重写外键与重写引用 gorm的belongTo关系 }func One2one() {GLOAB_DB.AutoMigrate(&Commen…

Latex error——Unknown graphics extension: .eps解决办法

1. 问题描述 使用PDFTeXify进行编译出现错误 2. 错误原因 使用includegraphics插入图片为eps格式,此种方法只能用latex默认编译器使用,如果使用pdflaTex则不能插入.eps格式图片。 3. 解决办法 在开头添加 \usepackage{epstopdf}

专项练习-04编程语言-03JAVA-01

1. 以下有关构造方法的说法,正确的是:() A 一个类的构造方法可以有多个 B 构造方法在类定义时被调用 C 构造方法只能由对象中的其他方法调用 D 构造方法可以和类同名,也可以和类名不同 正确答案:A 官方解析…

使用Anaconda3创建pytorch虚拟环境

一、Conda配置Pytorch环境 1.conda安装Pytorch环境 打开Anaconda Prompt,输入命令行: conda create -n pytorch python3.6 ​ 输入y,再回车。 稍等,便完成了Pytorch的环境安装。我们可以利用以下命令激活pytorch环境。 conda…

比XShell更好用,更现代的终端工具-Tabby

目录 简介:Tabby 是一名老外在 Github 开源的终端连接的工具, 安装教程: SSH连接功能: SFTP文件传输功能: 设置: 简介:Tabby 是一名老外在 Github 开源的终端连接的工具, Tabby 的…

进制和进制转换

什么是进制 进制的全称是进位计数制,是人为定义的带进位的计数方法,对于任何一种X进制,就表示每一位置上的数运算时都是逢 X进一位。 十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,以此类推…

数据出境要依法“过安检”!什么是数据出境?

为了规范数据出境活动,保护个人信息权益,维护国家安全和社会公共利益,促进数据跨境安全、自由流动。从2022年9月1日起,《数据出境安全评估办法》施行。什么是数据出境,什么情况下需要进行数据出境安全评估呢&#xff1…

windows下安装composer

安装Php 教程 下载composer 官网 中文网站 exe下载地址 下载好exe 双击运行 找到php.ini注释一行代码 测试 composer -v说明安装成功 修改源 执行以下命令即可修改 composer config -g repo.packagist composer https://packagist.phpcomposer.com # 查看配置…

基于springboot+mybatis +mysql+jsp图书管理系统

基于springbootmybatis mysqljsp图书管理系统 一、系统介绍二、功能展示1.用户登陆2.用户注册3.图书借阅(学生)4.我的借阅(学生)5.图书管理(管理员)6.用户管理(管理员)7.借阅信息(管理员&#x…

VScode中python的相对路径与绝对路径 FileNotFoundError: [Errno 2] No such file or directory

VScode中,python里的相对路径是相对于当前工作目录来定位的,而当前的工作目录在VScode中下方的终端窗口会有提示: 说明此时的工作目录并非当前python文件所在的目录,而是C:\Users\xxxxx(你的用户名)。因此,使用VScode…

【Apollo学习笔记】—— Cyber RT之创建组件

文章目录 前言0. 前置知识0.1 什么是 Component0.2 Component 的类型0.3 Component 的创建及工作流程0.4 Component 如何被加载0.5 Component 的优点 1. 初始化组件的目录结构2. 实现组件类2.1 头文件2.2 源文件2.3 创建 BUILD 文件 3. 设置配置文件3.1 配置 DAG 文件3.2 配置 …

每克卖1.67亿美元——内嵌富勒烯

英国牛津大学的科学家宣称,一个叫做Designer Carbon Materials的实验室正在生产内嵌富勒烯,而且还以3.2万美元的天价出售了200微克。 内嵌富勒烯于1985年被首次发现,它是由碳原子组成的球形结构。因其形状而被昵称为“巴基球”。60个碳原子组…

Java8实战-总结7

Java8实战-总结7 通过行为参数化传递代码真实的例子用Runnable执行代码块GUI事件处理 小结 Lambda表达式Lambda管中窥豹 通过行为参数化传递代码 真实的例子 用Runnable执行代码块 线程就像是轻量级的进程:它们自己执行一个代码块。但是,怎么才能告诉…

QT实现连接MySQL数据库以及查询等操作

QT实现连接数据库以及查询等操作 目录 QT实现连接数据库以及查询等操作实现效果建立数据库代码实现文件结构连接数据库增删改查 所用类及其函数解释源代码后面的话 实现效果 功能包含数据库的增删改查和界面的显示,因为没有用.ui文件所以控件的位置都是手动设置的&a…

【AI应用如何决策?如何研发出安全可信的AI应用?《Python可解释AI(XAI)实战》告诉你】

怎样才能有效地向A业务和利益相关者解释Al的决策?你需要仔细规划、设计和可视化。要解决的问题、模型以及变量之间的关系通常是微妙、出乎意料和复杂的。 《Python可解释AI(XAI)实战》通过几个精心设计的项目让你在实践中掌握众多XAI工具和方法&#x…

无涯教程-jQuery - triggerHandler( event, data )方法函数

triggerHandler(event,[data])方法触发元素(用于特定事件类型)上所有绑定的事件处理程序,而无需执行浏览器的默认操作,冒泡或实时事件。 triggerHandler( event, [data] ) - 语法 selector.triggerHandler( event, [data] ) 这是此方法使用…

【Matlab】基于粒子群优化算法优化BP神经网络的数据回归预测(Excel可直接替换数据)

【Matlab】基于粒子群优化算法优化 BP 神经网络的数据回归预测(Excel可直接替换数据) 1.模型原理2.数学公式3.文件结构4.Excel数据5.分块代码5.1 fun.m5.2 main.m 6.完整代码6.1 fun.m6.2 main.m 7.运行结果 1.模型原理 基于粒子群优化算法(…

2、基于redis实现分布式锁

目录 2.1. 基本实现2.2. 防死锁2.3. 防误删2.4. redis中的lua脚本2.4.1 redis 并不能保证2.4.2 lua介绍 2.5. 使用lua保证删除原子性 2.1. 基本实现 借助于redis中的命令setnx(key, value),key不存在就新增,存在就什么都不做。同时有多个客户端发送setn…