网络编程详解

news2024/11/15 22:44:17

什么是网络编程

网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信(或称为网络数据传输)

发送端和接收端

  • 发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
  • 接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。

//注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念

socket套接字

Socket套接字针对传输层协议主要划分为如下:

流套接字:传输层TCP协议

TCP协议特点:

  • 有链接
  • 可靠传输
  • 面向字节流
  • 有接收缓冲区和发送缓冲区
  • 大小不限

数据报套接字:传输层UDP协议

UDP协议特点:

  • 无连接
  • 不可靠传输
  • 面向数据报
  • 有接收缓冲区无发送缓冲区
  • 大小首先一次最多64kb

对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。

TCP和UDP的区别

  • TCP是有连接的UDP是无连接的

连接:是一种抽象的概念,本质上就是建立连接的双方,各自保存对方的信息。

TCP想要通信,就需要先建立连接,做完之后,才能进行后续通信,如果A想要和B建立连接,但是B拒绝了,通信就无法完成。

UDP想要通信就直接发送数据即可,不需要征得对方的同意UDP自身也不会保存对方的信息(UDP不知道但是写程序的人自己要知道,UDP自己不会保存,在调用UDP的socket api时要把对方的位置啥的传过去

  • TCP是可靠传输的,UDP是不可靠传输的

注意:在网上进行通信发送一个信息,这个消息是不可能做到100%送达的,所谓的可靠传输也是退而求其次,A向B发消息,消息是不是到达B这一方,A是可以知道的,如果传输失败,就可以才需一定的措施(比如重传之类的)

  • TCP是面向字节流的,UDP是面向数据报的

这个字节流和文件操作的字节流是一个意思,TCP也是和文件操作一样,以字节为单位来进行传输,UDP则是按照数据报(有严格的格式)为单位,来进行传输的

  • TCP和UDP都是全双工的

一个通信道路,允许双向通信,就是全双工,一个通信道路,只能单向通信,就是半双工,代码中使用一个Socket对象,就可以发送数据也能接收数据。

UDPapi的使用

使用DatagramPacket这个类来表示一个UDP数据报,每次进行传输都要以UDP数据报为基本单位

DatagramPacket 构造方法: 

方法签名
 

方法说明
 

DatagramPacket(byte[] buf, int length)

构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)

DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号

 DatagramPacket 方法:

socket其实也是操作系统中的一个概念,本质上是一种特殊的文件,Socket就属于是把网卡这个设备给抽象成文件了,往socket文件中写数据,就相当于通过网卡发送数据,从socket文件读取数据,就相当于通过网卡接收数据

 DatagramSocket 构造方法:

DatagramSocket 方法:

方法签名方法说明

void receive(DatagramPacket p);

从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)

void send(DatagramPacket p)

从此套接字发送数据报包(不会阻塞等待,直接发送)

void close()

关闭此数据报套接字

 使用UDP实现一个回显服务器

服务器和客户端都需要创建Socket对象,但是服务器的socket一般要显示的指定一个端口号,而客户端的socket一般不能显示指定,是由系统自动分配一个随机端口

服务器

public class UDPechoServer {
    //创建一个DatagramSocket对象,是后续网卡操作的基础
    public DatagramSocket socket = null;
    public UDPechoServer(int port) throws SocketException {
        //这样写是手动指定端口号
        socket = new DatagramSocket(port);
        //socket = new DatagramSocket();
        //这样写是系统自动分配端口号
    }
    public void start() throws IOException {
        System.out.println("服务器,启动!");
        while (true){
            //1.读取请求并解析
            //DatagramPacket就是一个数据包,里面储存要传输的信息
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024],1024);
            //接收客户端的请求,并将数据存储到requestPacket中,如果没有就会阻塞等待
            socket.receive(requestPacket);
            //当前完成recrive之后,数据是以二进制的形式储存到DatagramPacket中了
            //想要把这个数据显示出来还要把这个数据,转换成字符
            //这里的范围不是0~1024,而是具体接收到的数据所占空间的范围
            String request = new String(requestPacket.getData(),0,requestPacket.getLength());
            //这里的getLength()是接收的数据的实际长度不是1024
            //2.根据请求计算响应(由于此处是回显服务器,请求是啥,回应就是啥)
            String response = this.process(request);
            //3.把响应写到客户端,搞一个响应对象,DatagramPacket
            //往DatagramPacket里构造刚刚的数据,再通过send返回
            //这里不能使用response.length()因为字符集的原因,response里面的中文可能占三个字节
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            //返回请求
            socket.send(responsePacket);
            //打印一个日志,把这次数据的详细情况打印下来
            //这里显示的端口是系统分配给客户端的端口
            System.out.printf("[%s,%d] req:%s  res:%s\n",requestPacket.getAddress().toString(),
                   requestPacket.getPort(),request,response );
        }
    }

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

    public static void main(String[] args) throws IOException {
        //需要手动创建端口号
        UDPechoServer server = new UDPechoServer(9090);
        server.start();
    }
}

 

new byte[]这个对象是用来承载从网卡这边读到的数据,收到数据的时候,需要搞一个内存空间来保存这个数据,DatagramPacket内部不能自己分配空间,所以就需要程序员自己手动分配,再交给DatagramPacket

 

这里的 response.getBytes().length不能换成response.length(),如果这个字符串里都是英文字符,此时字节和字符个数是一样的,如果包含中文就不一样了

刚刚说了socket本质上是一个特殊的文件,那么为什么最后不用close?

socket是文件描述符表里的一个表项,每打开一个文件,就会占用里面的一个位置,文件描述符表是再pcb上的(跟随进程),这个socket在整个程序运行中都是需要使用的,不能提前关闭,当socket不再使用的时候,就意味着程序就要结束了,进程结束此时文件描述符表,就随着pcb销毁了,随着销毁的过程中,socket就被系统回收了。

什么时候会出现文件泄露?

代码中频繁的打开文件,但是不关闭,在一个进程中,不断积累打开的文件,文件描述符表里的空间不断被消耗,最终消耗殆尽。

客户端 

public class UDPechoClient {
    public DatagramSocket socket = null;
    public String serverid = "";
    public int serverport;
    public UDPechoClient(String serverid,int serverport) throws SocketException {
        //创建的对象,不能手动指定端口
        socket = new DatagramSocket();
        //由于UDP自身不会持有对端的信息,就需要在应用程序里,把对端的情况记录下来
        //这里主要记录对端ip和端口
        this.serverid = serverid;
        this.serverport = serverport;
    }
    public void start() throws IOException {
        System.out.println("客户端,启动!");
        Scanner scanner = new Scanner(System.in);
        while (true){
            //1.控制台读取数据,作为请求
            System.out.println("请输入你的请求");
            String request = scanner.next();
            //2.把请求内容构造成DatagramPacket对象,发给服务器
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverid),serverport);
            socket.send(requestPacket);
            //3,尝试读取服务器返回的响应
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024],1024);
            socket.receive(responsePacket);
            //4.把响应转换成字符串
            String response = new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.println("接收响应:" + response);
        }
    }

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

 在这个代码里用到了3个DatagramPacket的构造方法

  1. 只指定字节数组缓冲区(服务器收到请求和客户端受到响应的时候用到)
  2. 指定字节缓冲区,同时指定一个InetAddress对象,这个对象就同时包含了IP和端口(服务器返回响应给客户端)
  3. 指定字节数组缓冲区,同时指定IP+端口号

程序执行流程

  • 服务器先启动,服务器启动之后就会进入循环,执行到receive这里并阻塞(此时还没有客户端过来)
  • 客户端开始启动,也会先进入while循环,执行到scanner.next并且也在这里阻塞,当用户在控制台输入字符串之后,next就会返回,从而构造请求数据并发送出来
  • 客户端发送出数据后:

服务器:就会从receive中返回得到,进一步解析请求为字符串,执行process操作,执行send操作

客户端:继续往下执行,执行到receive,等待服务器的响应

  • 客户端收到响应后,就会从receive中返回得到,执行这里的打印操作,也就把响应给显示出来了
  • 服务器这边完成一次循环之后,又执行到receive这里,客户端这边完成一次循环后又执行到scanner.next这里

TCPapi的使用

TCP的socket api和UDP的socket api差异有很涉及到两个关键的类

  • ServerSocket(给服务器使用的类,使用这个类来绑定端口号)
  • Socket(既可以给服务器使用又可以给客户端使用)

 如果有客户端和服务器建立连接,这个时候服务器的应用程序是不需要做出任何操作(也没有任何感知的),内核就直接完成了连接建立的流程(三次握手),完成流程后,就会在内核的队列中排队(这个队列是每个serverSocket都有的)。应用程序要想和客户端进行通信,就需要通过一个accept方法,把内核队列里已经建立好的连接对象,拿到应用程序中。

ServerSocket API

ServerSocket 是创建TCP服务端Socket的API

ServerSocket构造方法:

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

ServerSocket方法:

 Socket API:

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket,不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

 Socket构造方法:

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

 Socket方法:

方法签名方法说明

InetAddress getInetAddress()

返回套接字所连接的地址

InputStream getInputStream()

返回此套接字的输入流

OutputStream getOutputStream()

返回此套接字的输出流

使用TCP实现一个回显服务器

服务器:

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

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){
            //接收到客户端的连接就会执行processClient
            Socket clientSocket = serverSocket.accept();
            //不能直接调用,不然当有多个客户端时,服务器会阻塞在processclient内部
            //还可以使用线程池来进一步提高效率
            Thread t = new Thread(() -> {
                try {
                    processCilent(clientSocket);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            });
          t.start();
        }
    }

    public void processCilent(Socket clientSocket) throws IOException {
        //打印日志表示客户端上线
        System.out.printf("[%s:%d客户端上线]\n",clientSocket.getInetAddress(),clientSocket.getPort());
        //接下来进行数据的交互
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            //使用try()的方式避免后续忘记close
            while (true){
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    //断开连接(已经读完或其他原因)
                    System.out.println("客户端下线");
                    break;
                }
                //1.读取数据并解析,此处使用next来作为读取的方式,next的规则是,读到“空白符就返回”
                String request = scanner.next();
                //根据请求,给出回应
                String response = process(request);
                //3.把响应写回到客户端,把Sting转成字节数组,写入到OutputStream
                //也可以使用PrintWriter把OutputStream包裹一下,来写入字符串
                PrintWriter printWriter = new PrintWriter(outputStream);
                //此处的println不是打印在控制台,而是写入到OutputStream对应的对象流,也就是写入到clientSocket文件
                //自然这个数据也就通过网络发送出去了(发给当前这个服务器连接的另外一端)
                //使用println带有\n也是为了后续,客户端方便使用next读取数据
                printWriter.println(response);
                //不要忘记刷新缓存区
                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 {
            //当process处理完一个连接就表示可以关闭了
            clientSocket.close();
        }
    }
    public String process(String request){
        return request;
    }

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

注意:TCP是以字节流的方式进行通信的,每次没有固定的说,传输多少个字节,所以一般程序员会手动约定出,从哪里到哪里是一个完整的数据报,每次循环一次,就处理一个数据报即可,这里就是约定了使用\n作为数据报结束标记,而且正好可以搭配scanner.next来完成请求的读取过程。 

客户端:

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class TCPechoCilent {
    private Socket socket = null;
    public TCPechoCilent(String ip,int port) throws IOException {
        socket = new Socket(ip,port);
    }
    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 scanner1 = new Scanner(inputStream);
                while (true){
                    //1.从控制台读取用户输入的内容
                    System.out.println("请输入请求");
                    String request = scanner.next();
                    //2.把请求把送给服务器
                    //使用println,是为了让请求后面带上换行,方便服务器读取
                    writer.println(request);
                    writer.flush();
                    //3.读取服务器返回的响应
                    String response = scanner1.next();
                    //4.打印响应
                    System.out.println("响应:"+response);
                }


            } catch (IOException e) {
                throw new RuntimeException(e);
            }

    }
    public static void main(String[] args) throws IOException {
        TCPechoCilent cilent = new TCPechoCilent("127.0.0.1",8080);
        cilent.Start();
    }
}

注意:这里和UDP不一样,这里的CilentSocket对象(Socket)要进行close,DatagramSocket和ServerSocket都是在程序中,只有这么一个对象,生命周期贯穿整个程序,CilentSocket则是在循环中每次有一个新客户来建立连接,都会创建出新的ClientSocket并且Socket最多使用到客户端退出(断开连接)。如果有很多客户端都要建立连接,每个客户端都会创建一个ClientSocket,如果断开连接时没有手动close,此时这个Socket对象就会占用文件描述符表的位置,数量多了之后就会导致文件泄露问题。

 以上就是博主对网络编程知识的分享,在之后的博客中会陆续分享有关线程的其他知识,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望多多支持博主之后和博客!!🥰🥰

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

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

相关文章

信创实践(2):利用Leapp工具迁移CentOS至AnolisOS,实现系统升级与自主可控

1. 引言 为了满足用户在CentOS退出后对操作系统使用的诉求,OpenAnolis龙蜥社区正式发布了Anolis OS。越来越多的CentOS客户期望能够迁移到Anolis OS上来。操作系统迁移是一个复杂工程,手工迁移技术要求高,操作复杂度强,需要耗费大…

Windows 欺骗主机 Burn-In 的自动化工具

Sinon 是一款开源的模块化工具,用于自动测试基于 Windows 的欺骗主机。 它旨在降低大规模编排欺骗主机的难度,同时通过生成功能实现多样性和随机性。 Sinon 旨在通过执行模拟实际用户活动的各种操作来自动设置欺骗主机。 目标是创建一个可以欺骗潜在入…

Python3.8绿色便携版安装版制作

Python 的绿色便携版有两种:官方 Embeddable 版本(嵌入式版);安装版制作的绿色版。Embeddable 版适用于需要将 Python 集成到其他应用程序或项目中的情况,它不包含图形界面的安装程序,只提供了 Python 解释器和必要的库…

国企民企协同共进,让长沙永远是当打之年

一提到长沙,大多都会跟“网红”二字联系在一起,随之而来的是巨大关注度与经济效应,但与此同时,争议也随之而来:这样的网红城市依赖单一的“网红”元素或流量效应,经济增长缺乏内生动力,十分不禁…

大数据-118 - Flink DataSet 基本介绍 核心特性 创建、转换、输出等

点一下关注吧!!!非常感谢!!持续更新!!! 目前已经更新到了: Hadoop(已更完)HDFS(已更完)MapReduce(已更完&am…

JVM 垃圾回收机制:GC

目录 一、死亡对象的判断算法 1.1 引用计数算法 1.2 可达性分析算法 二、垃圾回收算法 2.1 标记-清除算法 2.2 复制算法 2.3 标记-整理算法 2.4 分代算法 三、垃圾收集器 3.1 CMS收集器(老年代收集器,并发GC) 3.2 G1收集器(唯一一…

项目实战 - 贪吃蛇

目录 1. 基本功能 2. 技术要点 3. 环境 4. 效果演示 5. 控制台设置 6. Win32 API介绍 6.1 Win32 API 6.2 程序台控制(Console) 6.3 控制台屏幕上的坐标(COORD) 6.4 GetStdHandle 6.5 GetConsoleCursorInfo 6.5.1 CONSOLE_CURSOR_INFO 6.6 SetConsoleCursorInfo 6…

云原生架构概念

云原生架构概念 云原生架构(Cloud Native Architechtrue)作为一种现代软件开发的革新力量,正在逐渐改变企业构建、部署和管理应用程序的方式。它的核心优势在于支持微服务架构,使得应用程序能够分解为独立、松耦合的服务&#xf…

详解si5338 si53xx 设计使用及STM32 iic驱动设计

背景 在实际项目中经常使用si5338 si53xx,进行多路时钟的倍频以生成想要的时钟信号,但是针对si5338 si53xx设计使用缺少相关的资料,本文详解si5338 si53xx 设计使用及STM32 iic驱动设计,本文使用工程在项目中得到测试&#xff0c…

基于人体关节夹角的人体动作识别算法(代码+数据集)

为此本文提出了一个基于人体关节夹角的人体动作识别算法,主要做了以下工作: (1)提出了一个可解释性强,耗费算力较少且鲁棒性较高的基于人体关节夹角的人体动作序列的特征抽取方法。 (2)本文所使…

PyInstaller实战:打包Python应用并间接指定输出文件名

在深入探讨如何使用PyInstaller打包Python应用并指定输出文件名称的过程中,我们不仅可以了解基本的命令行操作和参数设置,还可以深入了解PyInstaller的工作机制、状态变化以及它在处理复杂Python项目时的优势。下面,我们将详细展开这一过程&a…

提升多跳问答中的语言模型知识编辑能力

人工智能咨询培训老师叶梓 转载标明出处 大模型在静态知识库的更新上存在局限,特别是在面对需要多步骤推理的多跳问题时,难以提供准确和最新的回答。为了解决这一问题,来自美国佐治亚大学、纽约大学、莱斯大学、北卡罗来纳州立大学等机构的研…

STM32F103C8----GPIO(跟着江科大学STM32)

一,GPIO简介 GPIO(General Purpose Input Output)通用输入输出口 可配置为8种输入输出模式 引脚电平:0V~3.3V(0V),部分引脚可容忍5V 输出模式下可控制端口输出高低电平,用以驱动…

idea2021安装教程与常见配置(可激活至2099年)

idea2021安装教程与常见配置(可激活至2099年) 下载 官网下载地址:https://www.jetbrains.com/zh-cn/idea/download/other.html 这里我们选择压缩包安装方式,选择2021.3 - Windows x64 ZIP Archive (zip),也可以选择exe安装方式 安装 解压缩安装方式 创建非中文目录D:\idea…

Win32绕过UAC弹窗获取管理员权限

在早些年写一些桌面软件时,需要管理员权限,但是又不想UAC弹窗,所以一般是直接将UAC的级别拉到最低,或者直接禁用UAC的相关功能。 什么是UAC(User Account Control) 用户帐户控制 (UAC) 是一项 Windows 安全功能,旨在保…

行走挖机多路比例阀控制放大器

挖掘机比例多路阀是挖掘机液压系统中的关键部件,它负责控制挖掘机各执行元件的运动方向、速度和力矩,从而影响挖掘机的作业效果。比例多路阀由多个阀块组成,其中比例控制阀由BEUEC比例放大器控制。每个阀块都有特定功能,如换向阀用…

昇腾大模型性能分析思路

性能分析 模型训练优化流程 我们根据性能问题的场景,按照单机和集群场景进行分类,再明确性能问题属于哪一类,明确好性能问题背景之后,才方便进行下一步问题的定位; 在明确问题背景后,参考性能分析工具介绍…

004、架构_详解(重点)

GoldenDB 分布式数据库框架 DN和RDB增加了备节点;引入新模块CM,且GTM、MDS、PM、CM都增加备节点;MDS、PM、CM、RDB被统一在了管理节点之中;GTM和MDS间多了一条连线,因为GTM的切换由MDS把控;初步系统架构mysqld:一般称为DB节点,负责单个节点的数据处理; dbproxy:一般…

FreeRTOS学习笔记—③RTOS内存管理篇(正在更新中)

二、RTOS的核心功能 RTOS的核心功能块主要分为任务管理、内核管理、时间管理以及通信管理4部分,框架图如下所示: (1)任务管理:负责管理和调度任务的执行,确保系统中的任务能够按照预期运行。 (…

【SpringBoot】使用Nacos服务注册发现与配置管理

前提:需要提前部署好nacos服务,这里可以参考我的文章:Windows下Nacos安装与配置 0. 版本信息 Spring Boot3.2.8Spring Cloud2023.0.1Spring Cloud alibaba2023.0.1.0nacos2.3.2本地安装的nacos2.3.0 Spring Boot、Spring Cloud、Spring Clo…