网络编程二

news2024/11/25 4:26:19

前言

在上一篇关于网络协议的博客中,我们简单概括了网络套接字中的UDP协议,本篇博客我们将继续学习分享关于网络套接字中另一个协议,TCP网络协议


一、UDP和TCP协议区别是什么?

二者之间的区别如下

🔗UDP的主要特点

无连接:使用UDP的通信双方不需要直接保存对方的信息,直接投递。例如发短信,电信诈骗分子发短信给我,但是我压根不认识这些坏人,但是可以直接发短信给我

面向数据报:UDP是以一个数据报作为传输单位,不可能只传输半个数据报。

不可靠传输:不可靠传输不是意味着不安全,而是表示只要双方其中一个发送了数据即可,不关心有没有收到。

全双工:一条路径,双向通信。

🔗TCP的主要特点

有连接:跟UDP不同,使用TCP的双方必须要建立联系。我打电话约朋友出来打球,大家拿起电话通话建立了联系,才知道约的是几点打球。

面向字节流:以字节作为传输的基本单位,读写都是非常灵活的。

可靠传输:发送数据不能保证100%传输,但是需要尽可能的传输过去。

全双工:一条路径,双向通信。

🧷注:此处说的连接不是指拿绳子把通信双方绑在一起,举个例子。一对新人去民政局领证,结婚证一式两份,盖完章,此时新娘和新郎官就建立了联系。

既然有全双工,就会有半双工📥

二、TCP套接字网络编程

1.API

TCP网络编程也是基于Socket套接字开发,双发如果需要通信必须要建立连接,在网络编程中收发是以字节为单位的流式传输。

对于TCP网络编程,Java提供了2个类进行传输,分别是针对服务器和客户端。ServerSocket API,是创建TCP服务器Socket的API,Socket API是基于客户端的API。

ServerSocket构造方法

方法签名                        方法签名
ServerSocket(int port)

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

这个Socket 和 UDP中的DatagramSocket 类似,都是构造的时候指定一个具体放入端口

让服务器绑定该端口,但是此处的ServerSocket一定是绑定具体的端口。

ServerSocket关键方法

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

accept的意思是接收,服务器是被动的一方,客户端则是主动的一方,客户端主动向服务器发起连接请求,服务器需要同意一下,accept和上述字面意思是不一样的,TCP连接的接收是在内核里面已经完成了,此处的accept是应用层层面的接受。


Socket API

Socket是客户端Socket,也是服务端中收到客户端建立连接(accept方法)的请求之后,返回服务端Socket。

Socket构造方法

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

 

String host 和 int port 分别代表服务器的IP和端口;

TCP是有连接的,客户端在new Socket的时候,就会尝试和指定IP端口的目标建立连接了。

Socket方法

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

InputStream 这里读取数据,就相当于从网卡接收

OutputStream 这里写数据,就相当于从网卡发送

Socket就相当于是一个遥控器🕹️ ,在上一篇网络编程一的博客中我们有详细的说明。


2.长短连接

我们介绍了关于TCP网络编程开发中所用到的API,知道了如何使用这些方法,在发送数据的实收需要建立联系,何时关闭就是由长短连接来决定。

短连接:每一次收到数据后并返回响应,直接关闭连接,短连接只能一次性收发数据。

长连接:一直保持连接状态,双方不停的收发数据,可以多次收发数据。

二者之间也是由明显的区别:

  • 使用场景不同,短连接适用于客户端请求次数不高的业务,例如打开浏览器网页,可以多次打开并且响应快。长连接则适用于长时间的请求和响应,例如游戏和网上聊天,是实时的互动并且属于高频率使用。
  • 发送请求不同:短连接一般是客户端主动向服务端发送请求,长连接则是客户端和服务端都可以互相发送。
  • 耗时不同:短连接每次收发数据都需要建立并关闭连接,而长连接只需要建立一次连接,后续的请求和响应都可以直接传输,从建立连接的角度出发,长连接每次建立连接只需要一次,耗时短且执行效率很高

3.TCP回显服服务器程序

3.1 TCP服务器代码

设计一个TCP服务器端的步骤如下所述

1.创建ServerSocket服务器端实例对象,并在服务器上指定端口号

2.启动服务器,使用accept方法与客户端建立连接,如果客户端没有建立连接,accept会阻塞。

3.接收客户端的请求

4.处理客户端发送的请求,计算响应

5.将响应结果返回给客户端


代码视图📃

 Part1

public class TcpEchoServer{

    private SerberSocker serverSocket = null;//创建一个服务器专用的socket,用于和客户端建立连接
    public TcpEchoServer(int port) throws IOException {//设置端口号
        serverSocket = new ServerSocket(port);//把端口号传递给服务器
    }
}

Part2

public void start() throws IOException {
    System.out.println("服务器启动");
    while(true){// 死循环,和UDP没有区别,服务器需要随时接收请求
         Socket clientsocket = serverSocket.accept();//和客户端建立连接;clientSocket
         processConnection(clientsocket);//处理连接的方法,稍后解释
    }
}

处理连接的方法📥

Part3

private void processConnection(Socket clientSocket) throws IOException{  //长连接处理

        System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress().toString),

        clientSocket.getPort());
        //基于clientSocket对象和客户端进行通信

        try(InputStream inputStream = clinetSocket.getInputStream();//写
            OutputStream outputStream = clienttSocket.getOutputStream()){//读

            Scanner scanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
   

        //考虑到客户端会一直发送请求,所以用while循环
        while(true){
            //1.读取请求
           if(!scanner.hasNext())
               System.out.printf("[%s:%d] 客户端下线\n",
                  clientSocket.getInetAddress().toString(),
                  clientSocket.getPort());
            break;
            }
            //直接使用scanner 读取一段字符串,next 是一直读取到换行符号/空格/其他空白字符
            //但是最终返回结果是不包含空白符的
            String request = scanner.next();
           
            //2.根据请求响应计算
            String response = process(request);

            //3.把响应写回客户端
            printWriter.println(response);
            printWriter.flush();
            System.out.printf("[%s:%d] req:%s; resp: %s\n",
                clientSocket.getInetAddress().toString(),
                clientSocket.getPort(), request, rsponse);
                }
             } catch (IOException e){
                    e.printStackTrace();
          } finally{
                    clinetSocket.close();
        }
}
      private String process (String requset){
        return request;
     //回显程序,写的是啥,读取的就是啥
   }
      public static void main(String[] args) throws IOException {
        TcpRchoServer tcpEchoServer = new TcpEchoServer(9090);
        tcpEchoServer.start();
    }

}

以上的代码中部分内容较为繁琐,我们逐个说明

问题1:已经有了一个serverSocket,为什么还需要一个clientSocket,需要这么多的Socket吗?

答:举个例子,以卖房和买房作为理解场景。我们假设serverSocket是卖房子的销售小哥,负责招揽顾客去售楼大厅看房,顾客到了售楼大厅看房子,就会为这些顾客安排一位负责讲解楼房的小姐,此时售楼小姐就是clientSocket,销售小哥转身又去拉顾客来看房子。周而复始,只要卖房子的销售小哥(serverSocket)拉进来一名顾客,都会为顾客分配一名售楼小姐(clientSocket)讲解。

通过accept将服务端和客户端建立连接以后,accept所返回的socket,我们就将他命名为clientSocket。

所以理解起来如下

serverSocket 就是在外面招揽客人的售楼小哥

clientSocket  就是在大厅内部的服务的小姐

serverSocket只有一个,但是clientSocket 会给每个客户端都分配一个


问题2:处理一个客户端连接的过程中,是发来一个请求就结束了,还是会发送来多个请求,如何处理多个响应?

答:我们在处理多个响应的过程中,用到try的写法

在 () 中允许写多个对象,使用进行分割,这样无论是短连接还是长连接我们都可以获取到。


问题3:既然有了读和写,如果是一个长连接,那么读到哪里才算是一个完成的请求呢?

答:首先我们需要理解两个事项,第一,每个请求都是一个字符串(文本数据)。其二,请求和请求之间使用 \n 来进行分割,TCP是面向字节流的,像水流一样的东西,水龙头一旦打开就会放水,接了多少水算是结束呢?只要看到  \n 就会把水龙头关闭。

为了方便读取输入流(inputStream)和输出流(outputStream),我们用到了Scanner 和 PrintWriter,不用行不行呢?完全可以,但是代价就是一个字节一个字节去扣,我们通过Scanner 和 PrintWriter将字节流包装成了字符流,上述约定了请求是字符串,此时也可以通过字符流处理,本体还是inputStream 和 outputStream,Scanner 和 PrintWriter只是套壳而已。


问题4:hashNext()的作用

当进入while()循环后,此时客户端会一直向服务端发送请求,通过hashNext()方法判断输入(文件、字符串等一系列的输入流)是否还有下一个输入项,如果有返回true,反之false。hashNext()会等待客户端的输入,也会阻塞等待输入源的输入,当客户端关闭了连接以后吗,输入源也就结束了,没有下一个数据了,已经读完了,此时hashNext()就为false了。

紧接着scanner读取一段字符串,next()会一直读取到空白符结束,空格,换行,制表符,翻页符都算是空白符,但是返回的结果中是不会有空格或者空白符的。


问题5:为什么clientSocket需要被关闭,而serverSocket则不需要关闭?

答:serverSocket是一直在用。clientSocket只是在循环当中,每次客户端连接,accept都会返回一个Socket,有十万个客户端就会有十万个Socket,不用不回收,就会造成文件资源泄露,线程使用的文件是有上限的。

3.2 TCP客户端代码

设计一个TCP客户端代码步骤如下

1.从键盘上读取用户输入的内容

2.把读取到的内容构造成请求,发送给服务器

3.服务器读取响应内容

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


代码视图📃

Part1

public class TcpEchoClient{
    private Socket socket = null;
    public TcpEchoClient(String serverIp, int port) throws IOException {
        //此时通过服务器端口绑定的端口,我们已经将客户端和服务端绑定在一起了
        //这里的连接连上了,服务器的accept就会返回一个socket
        socket = new Socket(serverIp, port);
    } 
}

Part2

public void start() throws IOException {
    Scanner scanner = new Scanner(System.in);
    try(InputStream inputStream = socket.getInputStream();
        OutputStream outputStram = socket.getOutputStream()){
        PrintWriter printWriter = new PrintWriter(outputStream);
        Scanner scannerFromSocket = new Scanner(inputStream);
        while (true){
            //1.从键盘上读取用户输入的内容
            System.out.println("->");
            String request = scanner.next();

            //2.把读取的内容构造成请求,发送给服务器,发送过来的数据是带有换行的
            //此时只能通过next去读取,发送的数据没有换行,next会一直等,等到有换行的出来
              printWriter.println(request);
              printWriter.flush();//flush代表将刷新
            
           //3.服务器读取响应内容
              String response = scannerFromSocket.next();
              
           //4.把响应结果显示到控制台上
              System.out.printf("req:%s;resp: %s\n",request,response);

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

}
    

 Part3

public static void main(String[] args)throws IOException {
    TcpEchoClient client = new TcpEchoClient("127.0.0.1",port:9090);//服务器端设置的端口号也是9090
    client.start();//启动客户端
    
}

3.3运行结果

1.启动服务器

2.启动客户端

3.服务器返回响应

三、TCP后续问题

1.不加flush(),客户端启动后会一直无反应

将flush()方法注释之后,我们看下运行结果📥

先正常启动服务器

再启动客户端

这里光标一直停在这里,并没有返回一个一模一样的响应,这是什么原因导致的?

要理解这个问题我们需要知道,数据都是放在缓冲区中的。缓冲区(buffer)是内存空间的一部分,也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或者输出的数据,这部分预留的空间就被称为缓冲区。内存的读写速度很快,硬盘的读写速度很慢,为了提高IO效率。(读写硬盘和读写网卡都可以视为IO操作)引入缓冲区减少IO次数,就可以适当的提高整体的效率。假设要写十次网卡,就需要先把要写的数据放一个到内存构成的缓冲区(buffer),再统一把这个缓冲区中的数据写入网卡。

没加flush之前,只是把数据写入到内存的缓冲区中,等到缓冲区满了,才会写真正的网卡,flush相当于手动刷新缓冲区,可以让数据立即写入网卡。


2.服务器无法连接多个客户端

当我开第二个客户端的时候,服务器是没有任何回应的。

我们在服务器代码中提到了 processConnection 方法,启动服务器后,这个while循环,accept把内核建立的连接引入到程序中,只要有一个客户端,就需要accept一次,有多个客户端就需要accept多次,只有与当前通信的客户端传输完成后才会退出,也就是说这里的循环不退出,Socket对象就没有办法释放去建立其他连接,无法连接多个客户端。

这里我们可以采用多线程/线程池的方式来解决问题,让主线程负责accept和客户端建立连接,每接收一个连接,创建新的线程,由新的线程来负责处理这个新的客户请求。

修改后的代码🔨

public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
        Socket clientsocket = serverSocket.accept();
            Thread t =new Thread(()->{
                try{
                    processConnection(clientsocket);
                }catch (IOException e) {
                    e.printStackTrace();
                }
             });
            t.start();
        }
    }

创建新的线程,用新的线程来调用 processConnection,每次有新的客户端就搞一个新的线程即可。

此时就服务器就可以调用多个客户端了,主线程只做2件事,accept获取请求和创建线程,当线程创建好了,就会立即下一次调用accept。但是有个弊端,创建线程会多次频繁销毁会浪费内存空间,使用线程池的方案相比较创建新的线程在效率和浪费资源的上面来说是更加好的方案。

修改代码🔨

 public void start() throws IOException {
         ExecutorService executorService = Executors.newCachedThreadPool();
         System.out.println("服务器启动");
         while (true) {
         Socket clientsocket = serverSocket.accept();
         executorService.submit(new Runnable() {
                public void run() {
                    try{
                        processConnection(clientsocket);

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

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

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

相关文章

Mysql查询使用group_concat函数后,如果查询无结果,仍会返回一条空数据

1、在查询中使用了group_concat 函数,简单例子如下: select GROUP_CONCAT(recordid) from s_au_user where username 121212此sql查询一个username 为121212的数据,当然肯定是查询不到的,理论上应该返回0条结果,但是…

Python操作Word

Python操作Word 一、Word简介二、向Word写入内容2.1 导入模块2.2 创建doc文档对象2.3 添加段落2.4 添加列表2.5 添加图片2.6 保存文件 三、读取Word内容四、批量生成Word文件 一、Word简介 ​ 在日常工作中,有很多简单重复的劳动其实完全可以交给Python程序&#x…

Camtasia2024下载安装使用教程汇总

Camtasia Studio2024提供了强大的屏幕录像(Camtasia Recorder)、视频剪辑和编辑(Camtasi Studio)、视频菜单制作(Camtasia MenuMaker)等功能,界面简洁明晰、操作方便快捷。使用Camtasia Studio官方用户可以方便地进行屏幕操作的录制和配音、视频的剪辑和过场动画、添…

晚期食管癌肿瘤治疗线程分类

文章目录 1、肿瘤治疗的线数1.1 基础概念1.2 线程定义1.3 如何计算治疗线数 2 食管癌治疗指南2.1 食管癌诊疗指南2.1 CSCO 本文前半部分主要来源于参考文件1,其余部分来源于官方指南。无原创内容,全部为摘要。 1、肿瘤治疗的线数 1.1 基础概念 抗肿瘤药…

【Hive】——DML

1 Load(加载数据) 1.1 概述 1.2 语法 LOAD DATA [LOCAL] INPATH filepath [OVERWRITE] INTO TABLE tablename [PARTITION (partcol1val1, partcol2val2 ...)]LOAD DATA [LOCAL] INPATH filepath [OVERWRITE] INTO TABLE tablename [PARTITION (partcol…

ABAP与HANA集成 2:ABAP调用HANA存储过程或SQL语句

作者 idan lian 如需转载备注出处 需求 虽然是做BW模块,但是最近项目上种种,都需要给ABAP人员或者前台用户提供能供他们使用的表,就稍微研究了下ABAP和HANA的集成问题,因为我们BW更擅长的还是HANA,而且HANA的运行效…

什么是 DDoS ?如何识别DDoS?怎么应对DDOS攻击

什么是DDOS攻击 DDoS攻击(Distributed Denial of Service Attack)即分布式拒绝服务攻击,是一种利用分布式网络来发起大量的请求,占用目标服务器或网络资源的攻击行为。这种攻击方式可以瘫痪目标系统,导致其无法正常提供…

TensorFlow 2 和 Keras 之间的区别总结

1、什么是TensorFlow 2 TensorFlow 2是谷歌开源的一款深度学习框架,于2019年发布,并且在同年10月1日发布了TensorFlow 2.0.0正式稳定版。这款框架被很多企业与创业公司广泛用于自动化工作任务和开发新系统。 TensorFlow 2在分布式训练支持、可扩展的生…

MATLAB - 使用 MPC Designer 线性化 Simulink 模型

系列文章目录 前言 本主题介绍如何使用 MPC Designer 对 Simulink 模型进行线性化。为此,请从包含 MPC 控制器块的 Simulink 模型打开该应用程序。本例中使用 CSTR_ClosedLoop 模型。 open_system(CSTR_ClosedLoop) 在模型窗口中,双击 MPC 控制器模块。…

了解 SBOM (软件物料清单)

近年来,开源软件在开发中的采用激增,目前已占已构建软件的高达 90%。它在全球公司中的受欢迎程度源于成本节约和产品上市时间的加快。然而,在集成开源软件组件时,有一个关键的方面需要考虑。 Synopsys 报告84% 的商业和专有代码库…

未来十年,人工智能就业方向及前景如何?

人工智能(AI)是一个快速发展的领域,对于未来的就业方向和前景有着巨大的影响。以下是一些可能的发展趋势和就业前景: 1、增长趋势:人工智能正在全球范围内经历巨大的增长,预计在未来十年内将继续保持这一趋…

rn报错 run react-native-start,rn真机报错bug解决

错误信息 这个问题是因为手机和电脑没有链接起来需要实行通讯 ADB ADB (Android Debug Bridge) ,译作 安卓调试桥 ,一个能让你 与Android设备进行通信 的 命令行工具 。 你可以通过它,在命令行输入命令控制Android设备,或者查看…

【map】【单调栈 】LeetCode768: 最多能完成排序的块 II

作者推荐 【贪心算法】【中位贪心】.执行操作使频率分数最大 涉及知识点 单调栈 排序 map 区间合并 题目 给你一个整数数组 arr 。 将 arr 分割成若干 块 ,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。 返回…

【Spring】11 EnvironmentAware 接口

文章目录 1. 简介2. 作用3. 使用3.1 创建并实现接口3.2 配置 Bean 信息3.3 创建启动类3.4 启动 4. 应用场景总结 Spring 框架为开发者提供了丰富的扩展点,其中之一就是 Bean 生命周期中的回调接口。本文将着重介绍一个与环境(Environment)相关…

uniapp 预览图片

preImg(index){let urls []this.images.map((item,i) > {if(indexi){urls.unshift(item.file_path)}else{urls.push(item.file_path)}})uni.previewImage({urls})}

探索关系:Python中的Statsmodels库进阶

目录 写在开头1. 多元线性回归场景介绍 2. Logistic回归2.1 Logistic回归的概念2.2 应用案例2.2.1 建立模型和预测2.2.2 模型结论2.2.3 模型优化 3. 时间序列分析3.1 时间序列分析中的应用3.2 利用Statsmodels进行简单的时间序列分析 写在最后 写在开头 在数据分析的旅程中&am…

【react】原理简介

一、setState() 的说明 1.1 更新数据 setState() 是异步更新数据的注意:使用该语法时,后面的 setState() 不要依赖于前面的 setState()可以多次调用 setState() ,只会触发一次重新渲染 1.2 推荐语法 推荐:使用 setState((sta…

C++内存布局

温故而知新,本文浅聊和回顾下C内存布局的知识。 一、c内存布局 C的内存布局主要包括以下几个部分: 代码段:存储程序的机器代码。.数据段:存储全局变量和静态变量。数据段又分为初始化数据段(存储初始化的全局变量和…

深入解析Guava范围类(Range)

第1章:范围类Range的重要性 大家好,我是小黑,今天咱们聊聊一个在Java编程世界里非常实用但又被低估的角色——Guava库中的Range类。你知道吗,在处理涉及到数值范围的问题时,Range类就像是咱们的救星。不论是判断某个数…

Redis设计与实现之订阅与发布

目录 一、 订阅与发布 1、 频道的订阅与信息发送 2、订阅频道 3、发送信息到频道 4、 退订频道 5、模式的订阅与信息发送 ​编辑 6、 订阅模式 7、 发送信息到模式 8、 退订模式 三、订阅消息断连 1、如果订阅者断开连接了,再次连接会不会丢失之前发布的消…