JAVAEE初阶相关内容第十五弹--网络編程

写在前

简单描述一下关于路由器的三层转发和交换机的二层转发。

路由器是三层转发-->在网络层转发。【需要解析出IP协议中的源IP、目的IP来规划路径】

交换机是二层转发-->在数据链路层转发。【只需要关注下一步发展到哪个相邻的设备上,不需要IP地址,在数据链路层。有一个mac地址。】

这一部分开始进行网络部分的内容,涉及到编程相关的知识,好好学习!!

本篇博客主要是对网络编程进行初步的了解,socket的理解,学习相关API、完成客户端、服务器的【回显服务器】代码编写,在同一台主机上完成通信。

目录

写在前

1.网络编程基础

1.1什么是网络编程

1.2常见的客户端服务端模型

2.Socket套接字

2.1概念

2.2分类

2.2.1流套接字-TCP

2.2.2数据报套接字-UDP

2.3Java数据报套接字通信模型

2.4UDP数据报套接字编程

[2.4.1DatagramSocket API](#2.4.1DatagramSocket API)

[2.4.2DatagramPacket API](#2.4.2DatagramPacket API)

[2.4.3InetSocketAddress API](#2.4.3InetSocketAddress API)

2.4.4服务器代码实现

核心思想:

代码实现:

加注释版本:

需要强调的内容:

​2.4.5客户端代码实现

核心思想:

代码实现:

加注释版本:

理解服务器与客户端端口:

2.4.6服务器和客户端执行顺序:

2.4.7IDEA多个客户端设置操作

2.4.8客户端服务器执行结果


1.网络编程基础

1.1什么是网络编程

网络编程,指网络上的主机,通过不同的进程,【只要满足不同的进程就行,所以即便是同一个主机,只要是进程不同,基于网络来传输数据,也属于网络编程。】以编程的方式实现网络通信(或称为网络数据传输。)

1.2常见的客户端服务端模型

最常见的场景,客户端是指给用户使用的程序,服务端是指提供用户服务的程序。

(1)客户端先发送请求到服务端。

(2)服务端根据请求数据,执行相应的业务处理。

(3)服务端返回响应,发送业务处理结果。

(4)客户端根据响应数据,展示处理结果(战术获取的资源,或提示保存资源的处理结果)

2.Socket套接字

2.1概念

Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本单元。基于Socket套接字的网络程序开发就是网络编程。

【socket 英文原意为:插座。socket是操作系统给应用程序提供的网络编程的API,可以认为socket API是和传输层密切相关的,传输层里提供了两个核心的协议:TCP和UDP。所以socket api提供了两种风格TCP和UDP 准确的讲有三种unix域套接字,几乎没人用】

2.2分类

一些解释:

可靠传输:网络环境是天然复杂的。不可能保证传输的百分百到达,可靠传输指的是发送方能知道自己的消息发过去了。

是否是可靠传输与是否有无连接没有关系:例如在一些聊天软件上的"已读"功能,是无连接的可靠传输。

**全双工半双工:**一个通信通道可双向传输(既可以发送也可以接收),就是全双工;一根水管只能单项传输,叫做半双工。TCP/UDP都是全双工是因为一根网线中有八根线

2.2.1流套接字-TCP

TCP,Transmission Control Protocol(传输控制协议),传输层协议。

特点:有连接,可靠传输,面对字节流,有接收缓冲区也有发送缓冲区,大小不限,全双工。

类似于打电话,打电话就是有连接的,需要连接建立才能通信。连接建立需要对方"接受",如果连接没建立好,则不能进行通信。

对于字节流来说,可以简单地理解为,传输数据是基于IO流的,流式数据的特征就是在IO流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。

2.2.2数据报套接字-UDP

UDP,User Datagram Protocol(用户数据报协议),传输层协议。

特点:无连接,不可靠传输,面向数据报,有接受缓冲区无发送缓冲区,大小受限:一次最多传输64K,全双工。

类似于发微信,

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

拓展:关于网速快慢

"木桶原理" 在网络通信的过程中有很多路由器、交换机。转发能力存在上限,转发的不止是一个用户的数据。某个设备达到转发能力的上限,就会卡顿丢包。

2.3Java数据报套接字通信模型

对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次会发送全部数据报。一次接收全部数据报。

Java中使用UDP协议通信,主要基于DatagramSocket类来创建数据报套接字,并使用DatagramPacket作为发送或接收的UDP数据报,对于一次发送及接收UDP数据报的流程如下:

以上只是一次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据,也就是只有请求,没有响应。对于一个服务器来说,重要的是提供多个客户端的请求及响应。

下面实现一个最简单的UDP版本的客户端服务器程序--回显服务器(echo serve)

2.4UDP数据报套接字编程

一个普通的服务器:收到请求,根据请求计算响应,返回响应。

echo serve:省略了其中的"根据请求计算响应",请求是啥,就返回啥(这个代码没有实际的业务,也没有太大作用和意义,只是为了展示socket api 基本用法),但是对于一个服务器来说,一定"根据请求计算响应"这个环节是最重要的。

2.4.1DatagramSocket API

使用这个类表示一个socket对象。【在操作系统中,把socket对象也是当作一个文件来处理的,相当于是文件描述符表中的一项】普通文件对应硬盘设备;socket文件对应网卡。

1个socket对象就可以和另外一台主机进行通信,如果需要多个不同主机通信,则需要创建多个socket对象。

DatagramScocket是UDP Socket,用于发送和接收UDP数据报。

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。

数据报(Data gram) ,通过网络传输的数据的基本单元,包含一个报头(header)和数据本身,其中报头描述了数据的目的地以及和其它数据之间的关系。 完备的、独立的数据实体,该实体携带要从源计算机传递到目的计算机的信息,该信息不依赖以前在源计算机和目的计算机以及传输网络间交换。

|--------------------------|---------------------------------------------|
| 方法签名 | 方法说明 |
| DatagramSocket() | 创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(一般用于客户端) |
| DatagramSocket(int port) | 创建一个UDP数据报套接字的Socket,绑定到本机指定的端口(一般用于服务器) |
[DatagramSocket构造方法]

|----------------------------------------------------------------------------------------------------|---------------------------------|
| 方法签名 | 方法说明 |
| void receive(DatagramPacket p) 此处传入的相当于是一个空对象,receive方法内部,会对参数的这个空对象进行内容填充,从而构造出结果数据,参数也是一个"输出型参数" | 从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待) |
| void send(Datagram Packet p) | 从此套接字发送数据报(不会阻塞等待,直接发送) |
| void close() | 关闭此数据报套接字 |
[DatagramSocket方法]

2.4.2DatagramPacket API

DatagramPacket是UDP Socket发送和接收的数据报,表示UDP中传输的一个报文。

|------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| 方法签名 | 方法说明 |
| DatagramPacket(byte buf ,int length) 把buf这个缓冲区给设置进去了 | 构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length) |
| DatagramPacket(byte[ ] buf,int offset,int length,SocketAddress address) Socket Address Ip端口号 | 构造一个DatagramSocketPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号。 |
[DatagramPacket构造方法]

|--------------------------|---------------------------------------------|
| 方法签名 | 方法说明 |
| InetAddress getAddress() | 从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址。 |
| int getPort() | 从接收端的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号。 |
| byte[ ] getData() | 获取数据报中的数据 |
[DatagramPacket方法]

构造UDP发送的数据时,需要传入socketAddress,该对象可以使用InetSocketAddress来创建。

2.4.3InetSocketAddress API

|----------------------------------------------|--------------------------|
| 方法签名 | 方法说明 |
| InetSocketAddress(InetAddress addr,int port) | 创建一个Socket地址,包含IP地址和端口号。 |
[InetSocketAddress(Socket Address 的子类)构造方法]

2.4.4服务器代码实现

核心思想:

(1)读取请求并解析

(2)根据请求计算响应

(3)构造响应并写回给客户端

数据到达网卡,经过内核的层层分用,最终到达UDP传输层协议。

调用receive相当于执行到内核中的相关udp代码,就会把这个udp数据报里面载荷部分取出来,拷贝到用户提供的byte[ ]数组中。

代码实现:
java 复制代码
package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;


//UDP版本的回显服务器
public class UdpEcSe {
   
    private DatagramSocket socket = null;

    public  UdpEcSe(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }
  
    public void start() throws IOException {
        System.out.println("服务器启动!");
       
        while (true){
            
            //1.读取客户端发来的请求是啥
           
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);//通过receive方法来读取请求,receive内部会针对参数对象填充数据。填充的数据来自于网卡
          
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());

            //2.根据请求计算响应,此处是一个回显服务器,所以请求和响应是相同的
            String response = process(request);

            //3.将响应写回客户端,需要用到send方法
           
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());

            socket.send(requestPacket);
            //4.打印一下,当前这次请求响应的处理中间结果

            System.out.printf("[%s:%d] req: %s;resp:%s\n",requestPacket.getAddress().toString(),
                    requestPacket.getPort(),request,response);
        }
    }

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

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

}
加注释版本:
java 复制代码
package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 苏西西
 * Date: 2023-10-18
 * Time: 14:59
 */
//UDP版本的回显服务器
public class UdpEcSe {
    //首先需要创建一个成员
    //网络编程首先是要操作网卡,网卡不方便直接操作
    // 在操作系统内核中使用了一种特殊的叫做"socket"这样的文件来抽象表示网卡
    //因此进行网络通信需要先有一个对象
    private DatagramSocket socket = null;

    //构造方法将对象实例化
    //对于服务器来说,创建的同时还需要关联上一个端口号
    //服务器是网络传输中被动的一方,如果是操作系统随机分配的端口,
    public  UdpEcSe(int port) throws SocketException {
        socket = new DatagramSocket(port);
    }
    //启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        //服务器不是只给一个客户端提供服务,需要去服务很多客户端
        while (true){
            //只要有客户端过来就可以提供服务
            //1.读取客户端发来的请求是啥
            //对于UDP来说,传输数据的基本单位是DatagramPacket
            //receive方法的参数是一个输出型参数,需要先构造好一个空白的DatagramPacket对象交给receive 来进行填充
            //DatagramPacket相当于是在点餐的时候给顾客一个空白的纸条,客户写完之后商家再根据纸条的需求来做食物
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacket);//通过receive方法来读取请求,receive内部会针对参数对象填充数据。填充的数据来自于网卡
            //此时这个DatagramPacket 是一个特殊的对象,并不方便直接进行处理
            //可以将这里包含的数据拿出来构造一个字符串
            //字节数组作为存储数据的缓冲区,此处给的最大的长度是4096,但是这里的空间不一定会被用满,可能只使用小部分
            //因此构造字符串,哪些用了构造那部分,就可以通过getLength获取到实际的数据报的长度
            //只把这个实际有效部分给构造成字符串即可
            String request = new String(requestPacket.getData(),0, requestPacket.getLength());

            //2.根据请求计算响应,此处是一个回显服务器,所以请求和响应是相同的
            String response = process(request);

            //3.将响应写回客户端,需要用到send方法
            // send的参数也是一个DatagramPacket,需要将这个对象构造好
            //这里也需要通过字节数组进行构造,不同的是读请求是需要构造一个空的字节数组
            //读响应的字节数组就不能是空的了,得带有respose里面的内容--响应内容
            //需要注意的是response.getBytes().length是计算字节的个数,这里不能写作response.length() 【字符的个数】
            //DatagramPacket只认字节不认字符。
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());

            socket.send(requestPacket);
            //4.打印一下,当前这次请求响应的处理中间结果
            System.out.printf("[%s:%d] req: %s;resp:%s\n",requestPacket.getAddress().toString(),
                    requestPacket.getPort(),request,response);
        }
    }

    //根据请求计算响应
    //如果不是回显服务器,则可以在process中灵活的进行处理!!
    public String process(String request){
        return request;
    }

    public static void main(String[] args) throws IOException {
        //端口号指定,可以在1024->65535范围内随便挑一个数字!
        UdpEcSe server = new UdpEcSe(9090);
        server.start();
    }

}
需要强调的内容:

2.4.5客户端代码实现

核心思想:

(1)从控制台读取一个要发送的数据
(2)把这个数据构造成一个UDP的请求并发送
(3)读取服务器的 UDP 响应,并解析
(4)把解析好的结果显示出来

代码实现:
java 复制代码
package network;

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


//UDP版本的回显客户端
public class UdpEcCl {

   
    private DatagramSocket socket = null;
    private String serverIp = null;
    private int serverPort = 0;

   
    public UdpEcCl(String serverIp,int serverPort) throws SocketException {
        socket = new DatagramSocket();
        this.serverIp = serverIp;
        this.serverPort = serverPort;
    }
    public void start() throws IOException {
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);
        while (true){
            //1.从控制台读取一个要发送的数据
            System.out.print(">");
           
            String request = scanner.next();
          
            if(request.equals("exit")){
                System.out.println("bye");
                break;
            }

            //2.把这个数据构造成一个UDP的请求并发送
            
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);

            //3.读取服务器的 UDP 响应,并解析
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            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 {
        UdpEcCl  clinet = new UdpEcCl("127.0.0.1",9090);
        clinet.start();
    }

}
加注释版本:
java 复制代码
package network;

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


//UDP版本的回显客户端
public class UdpEcCl {
    //1.首先需要创建出来一个socket对象
    //网络编程需要操控网卡,就需要创建一个socket对象进行远程操控
    private DatagramSocket socket = null;
    //需要将ip和端口存一下
    private String serverIp = null;
    private int serverPort = 0;

    //写一个构造方法
    //指定服务器的ip和服务器的端口;一次通信需要有俩ip,俩端口
    //客户端的ip是127.0.0.1;端口是系统自动分配的
    //服务器的ip和端口也需要告诉客户端,才能顺利将消息发给服务器
    public UdpEcCl(String serverIp,int serverPort) throws SocketException {
        socket = new DatagramSocket();
        this.serverIp = serverIp;
        this.serverPort = serverPort;
    }
    public void start() throws IOException {
        System.out.println("客户端启动!");
        Scanner scanner = new Scanner(System.in);
        while (true){
            //1.从控制台读取一个要发送的数据
            System.out.print(">");
            //客户端发送的请求
            String request = scanner.next();
            //对客户端输入的内容进行进行简单的翻译
            if(request.equals("exit")){
                System.out.println("bye");
                break;
            }

            //2.把这个数据构造成一个UDP的请求并发送
            //构造这个packet的时候,需要把serverIp和port都传入过来
            // 但是由于此处的ip地址需要填写的是32位的整数形式
            //上述的IP地址是一个字符串,需要使用  InetAddress.getByName()来进行转换
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIp),serverPort);
            socket.send(requestPacket);

            //3.读取服务器的 UDP 响应,并解析
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
            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 {
        UdpEcCl  clinet = new UdpEcCl("127.0.0.1",9090);
        clinet.start();
    }

}
理解服务器与客户端端口:

对于服务器来说,端口必须是确定好的,对于客户端来说,端口是可以系统分配的。

来食堂买饭,商家给顾客的小票就相当于是端口号(小票上的编号究竟是多少,也是想当于是随机的,控制不了。就想在客户端这里手动指定端口是可行的,但是并不推荐这样。有可能指定的这个号码会被别人用了。)

客户端如果显示指定端口,就有可能和客户端电脑上的其他程序的端口就冲突了,这一冲突就可能会导致程序没有办法正常通信了。

那么为什么服务器这边指定的端口不怕重复呢?

服务器是程序员自己手里的机器,上面运行什么都是程序员可控的,程序员就可以安排哪个程序使用哪个端口,客户端的机器是在用户的手里,上面运行的程序也是不同的,是不可控的。

2.4.6服务器和客户端执行顺序:

一定是服务器先运行。服务器是被动的一方(类比于我们要去餐馆吃饭,一定是饭店先开门,才能去吃饭。)

第一步,服务器执行到receive进行阻塞。

第二步,客户端执行,客户端读取用户输入的内容。

第三步,客户端发送请求。

第四步,客户端等待响应。服务器收到请求,进行返回。【可以认为这两部分相同的时间执行的】

第五步,服务器根据请求计算响应。process

第六步,服务器执行send,返回响应。

第七步,客户端receive,从阻塞中返回,读到响应

对于客户端服务器程序来说,一个客户端要给很多个服务器提供服务,也就需要构造出多个客户端来进行测试。

2.4.7IDEA多个客户端设置操作

2.4.8客户端服务器执行结果

相关推荐
缺点内向2 小时前
Java:创建、读取或更新 Excel 文档
java·excel
带刺的坐椅2 小时前
Solon v3.4.7, v3.5.6, v3.6.1 发布(国产优秀应用开发框架)
java·spring·solon
四谎真好看4 小时前
Java 黑马程序员学习笔记(进阶篇18)
java·笔记·学习·学习笔记
桦说编程4 小时前
深入解析CompletableFuture源码实现(2)———双源输入
java·后端·源码
java_t_t4 小时前
ZIP工具类
java·zip
lang201509284 小时前
Spring Boot优雅关闭全解析
java·spring boot·后端
报错小能手5 小时前
linux学习笔记(43)网络编程——HTTPS (补充)
linux·网络·学习
pengzhuofan5 小时前
第10章 Maven
java·maven
百锦再6 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
刘一说6 小时前
Spring Boot 启动慢?启动过程深度解析与优化策略
java·spring boot·后端