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.2DatagramPacket 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构造方法

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

void receive(DatagramPacket p) 

此处传入的相当于是一个空对象,receive方法内部,会对参数的这个空对象进行内容填充,从而构造出结果数据,参数也是一个”输出型参数“

从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(Datagram Packet p)从此套接字发送数据报(不会阻塞等待,直接发送)
void close()关闭此数据报套接字

2.4.2DatagramPacket API

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

DatagramPacket构造方法
方法签名方法说明

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()获取数据报中的数据

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

2.4.3InetSocketAddress API

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

2.4.4服务器代码实现

核心思想:

(1)读取请求并解析

(2)根据请求计算响应

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

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

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

代码实现:
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();
    }

}
加注释版本:
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)把解析好的结果显示出来

代码实现:
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();
    }

}
加注释版本:
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客户端服务器执行结果

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

西西¥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值