JAVA网络编程

本文详细解读了Java网络编程中的InetAddress、URL和URLConnection等基础概念,以及NIO、Netty和MINA等高级框架,涉及TCP/IP、Socket编程和异步IO等关键技术。
摘要由CSDN通过智能技术生成

API:
一、Java 网络工具类
InetAddress类:
使用 java.net 包下的 InetAddress 类表示互联网协议的 IP 地址。
在 TCP/IP 协议族中,是通过 IP 地址来标识网络上的一台主机的。
InetAddress下还有2个子类:Inet4Address、Inet6Address,它们分别代表Internet Protocol version 4(IPv4)地址和Internet Protocol version 6(IPv6)地址,不过这两个子类不常用,这里也不在赘述。

通过 InetAddress 类的静态方法获取 InetAddress 对象的方法 :
InetAddress[] getAllByName(String host):在给定主机名的情况下,根据系统上配置的名称服务 host 返回其 IP 地址所组成的数组。
InetAddress getByAddress(byte[] addr):给定字节数组形式的 IP 地址,返回 InetAddress 对象。
InetAddress getByAddress(String host, byte[] addr):根据提供的主机名 host 和字节数组形式的 IP 地址 addr,创建 InetAddress 对象。
InetAddress getByName(String host):给定主机名 host,返回 InetAddress 对象。
InetAddress getLocalHost():返回本地主机 InetAddress 对象。

InetAddress 类的其他常用方法有以下几种:
byte[] getAddress():返回此 InetAddress 对象的原始 IP 地址。
String getCanonicalHostName():返回此 IP 地址的完全限定域名。完全限定域名是指主机名加上全路径,全路径中列出了序列中所有域成员。
String getHostAddress():返回 IP 地址字符串。
String getHostName():返回此 IP 地址的主机名。

URL类:
URL 类代表一个统一资源定位符,它是指向互联网资源的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。
//通过URL字符串创建URL对象
URL tURL= new URL(url);

URLConnection 类:
前面介绍的 URL 类代表的是一个网络资源的位置,而接下来要介绍的 URLConnection 代表的是一种连接。此类的实例可用于读取和写入对应 URL 引用的资源。
通常,创建一个到 URL 的连接 URLConnection 的对象需要以下几个步骤:
1通过在 URL 上调用 openConnection() 方法创建连接对象。
2设置参数和一般请求属性。
3使用 connect() 方法建立到远程对象的实际连接。
4远程对象变为可用,其中远程对象的头字段和内容变为可访问。

以下,列出一些 URLConnection 类的属性及其含义。
boolean doInput :将 doInput 标志设置为 true,指示应用程序要从 URL 连接读取数据,此属性的默认值为 true。此属性由 setDoInput() 方法设置,其值由 getDoInput() 方法返回。
boolean doOutput :将 doOutput 标志设置为 true,指示应用程序要将数据写入 URL 连接,此属性的默认值为 false。此属性由 setDoOutput() 方法设置,其值由 getDoOutput() 方法返回。
boolean useCaches :如果其值为 true,则只要有条件就允许协议使用缓存;如果其值为 false,则该协议始终必须获得此对象的新副本,其默认值为上一次调用 setDefaultUseCaches() 方法时给定的值。此属性由 setUseCaches() 方法设置,其值由 getUseCaches() 方法返回。

二、Socket 编程
Socket 通常也称为套接字,应用程序通常通过套接字向网络发出请求或者应答网络请求。Java 语言中的 Socket 编程常用到 Socket 和 ServerSocket 这两个类, Socket 用于端对端的通信,而 ServerSocket 常用于服务端对象的创建,它们都位于 java.net 包中。
Socket 编程可以基于 TCP 编程,也可基于 UDP 编程,其中 TCP 方式主要是通过 Socket 和 ServerSocket 类实现,而 UDP 方式主要通过 DatagramSocket 和 DatagramPacket 类实现


网络编程的相关概念:
网络:
局域网
城域网
广域网
ip地址:用于唯一标识网络中的每台计算机。
域名:将ip地址映射成域名;域名就代表ip地址,并且方便记忆
端口号:用于标识计算机上某个特定的网络程序。常用的网络程序端口号:tomcat(8080),mysql(3306),oracle(1521),sqlserver(1433);
0~1024已被占用,比如:ssh 22,ftp 21,smtp 25,http 80;
整数形式,范围0~65535。
网络通讯协议:语言本身就是一种协议,在网络编程中,数据的组织形式就是协议;
TCP协议:传输控制协议,三次握手;
UDP协议:用户数据协议,发送数据结束时无需释放资源;
TCP字节流编程:
TCP字符流编程:
Socket: Socket允许程序把网络连接看成一个流,数据在两个Socket间通过IO流传输
netstat -an    可以查看当前主机网络情况,包括端口监听情况和网络连接情况


UDP网络通信编程:
UDP还支持单播、广播和组播三种传输方式。其中单播是指一对一的传输方式,广播是指将数据包发送到同一网络中的所有设备,而组播是指将数据包发送到指定的一组设备。这些传输方式可以根据应用需要进行灵活的选择。
UDP通常用于对实时性要求较高的场景,如语音通信,视频通话,直播流媒体,实时多人游戏等,这些场景中,丢失一些数据包对整体效果影响不大,但是要求传输延迟较低。
 
UDP原理:
●基本介绍
类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 的协议网络程序;
1UDP数据通过数据报套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也无法确定什么时候可以抵达;
2DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号;
3UDP协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接;
●基本流程
1. 核心的两个类/对象,DatagramSocket 和 DatagramPacket;
2. 建立发送端,接收端(没有服务端和客户端概念);
3. 发送数据前,建立数据包/报 DatagramPacket对象;
4. 调用DatagramSocket的发送,接受方法;
5. 关闭DatagramSocket;

NIO学习:
可简单认为:IO是面向流的处理,NIO是面向块(缓冲区)的处理
面向流的I/O 系统一次一个字节地处理数据。
一个面向块(缓冲区)的I/O系统以块的形式处理数据。

NIO主要有三个核心部分组成:
buffer缓冲区
Channel管道
Selector选择器

buffer缓冲区和Channel管道:
在NIO中并不是以流的方式来处理数据的,而是以buffer缓冲区和Channel管道配合使用来处理数据。
简单理解一下:
Channel管道比作成铁路,buffer缓冲区比作成火车(运载着货物)
而我们的NIO就是通过Channel管道运输着存储数据的Buffer缓冲区的来实现数据的处理!
要时刻记住:Channel不与数据打交道,它只负责运输数据。与数据打交道的是Buffer缓冲区
Channel-->运输
Buffer-->数据
相对于传统IO而言,流是单向的。对于NIO而言,有了Channel管道这个概念,我们的读写都是双向的(铁路上的火车能从广州去北京、自然就能从北京返还到广州)!

buffer缓冲区核心要点:
Buffer是缓冲区的抽象类。负责数据的存储,缓冲区就是数组,用于存储不同类型的数据。
其中ByteBuffer是用得最多的实现类(在管道中读写字节数据)。

缓冲区类
根据数据类型不同,提供了对应类型的缓冲区:
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
分别对应基本数据类型: byte, char, double, float, int, long, short。当然NIO中还有MappedByteBuffer, HeapByteBuffer, DirectByteBuffer等这里先不进行陈述。

缓冲区方法:
上述缓冲区的管理方式几乎一致,都是通过allocate()获取缓冲区;
allocate()获取缓冲区;
put()写数据导缓冲区;
get()读取缓冲区数据;
filp()为“切换成读模式”
clear()函数,这个函数会“清空”缓冲区:数据没有真正被清空,只是被遗忘掉了,切换导写模式;

缓冲区属性:
Buffer类维护了4个核心变量属性来提供关于其所包含的数组的信息。它们是:
容量Capacity:
缓冲区能够容纳的数据元素的最大数量。容量在缓冲区创建时被设定,并且永远不能被改变。(不能被改变的原因也很简单,底层是数组嘛)
上界Limit:
缓冲区里的数据的总数,代表了当前缓冲区中一共有多少数据。
位置Position:
下一个要被读或写的元素的位置。Position会自动由相应的 get( )和 put( )函数更新。
标记Mark:
一个备忘位置。用于记录上一次读写的位置。
ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
        //存数据
        String data="java";
        byteBuffer.put(data.getBytes());
        //读数据
        byteBuffer.flip();//切换导读模式
        byte[] bytes=new byte[byteBuffer.limit()];
        byteBuffer.get(bytes);// 将读取的数据装进我们的字节数组中
        System.out.println("空间大小="+byteBuffer.capacity());
        System.out.println("数据大小="+byteBuffer.limit());
        System.out.println("当前位置="+byteBuffer.position());
        System.out.println("上一次操作位置="+byteBuffer.mark());
        System.out.println("读取的字符串="+new String(bytes,0,bytes.length));

Channel通道核心要点:
Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream.而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。
通道Channel用户源节点与目标节点的连接,在NIO中负责数据的传输。
主要实现类:
FileChannel
SocketChannel
ServerSocketChannel
DatagramChannel
这里看名字就可以猜出个所以然来:分别可以对应文件IO、UDP和TCP(Server和Client)。下面演示的案例基本上就是围绕这4个类型的Channel进行陈述的。

获取通道:
1、java针对支持通道的类提供了getChannel()方法;
本地IO:
FileInputStream、FileOutputStream
RandomAccessFile
网络IO:
Socket
ServerSocket
DatagramSocket
2、针对各个通道提供了静态方法open();
3、Files工具类的newByteChannel();

使用NIO完成网络通信:
一、使用NIO完成网络通信的三个核心:
1 通道Channel负责连接:
SelectableChannel
SocketChannel
ServerSocketChannel
DatagramChannel
Pipe.SinkChannel
Pipe.sourceChannel
2 缓冲区Buffer负责数据的存取:
3 选择器Selector:是SelectableChannel的多路复用器,用于监控SelectableChannel的IO状况;
Selector选择器就可以比喻成麦当劳的广播。
一个线程能够管理多个Channel的状态

Netty框架:
导包:
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.76.Final</version>
</dependency>

ByteBuf:
Netty没有使用ByteBuffer来进行数据装载,而是自定义了一个ByteBuf;
写操作完成后无需进行flip()翻转
具有比ByteBuffer更快的响应速度
动态扩容
index分为读和写两个指针,每写入一次,writeIndex向后移动一位;每读取一次,readerIndex向后移动一位。
readerIndex不能大于writeIndex,这样就不需要翻转了
mark操作也分为两种
readerIndex和writeIndex之间的部分就是可读的内容,而writeIndex到capacity都是可写的部分

Netty抽象出两组线程池BossGroup和WorkGroup
BossGroup专门负责接受客户端的连接
WorkGroup专门负责读写
BossGroup和WorkGroup都是使用EventLoop来进行事件监听的,整个Netty也是使用事件驱动来运作的,比如当前客户端已经准备好读写、建立连接时,都会进行事件通知,就像多路复用那样。
在BossGroup之后,会正常将SocketChannel绑定到WorkGroup中的其中一个EventLoop上,进行后续的读写操作监听。

Netty中的Channel
通道Channel可以用来进行数据的传输,并且通道支持双向传输
Channel的父接口ChannelOutboundInvoker定义了大量IO操作

ChannelHandler
有了通道之后,要怎么操作?将需要处理的操作放在ChannelHandler中,ChannelHandler充当了所有入站和出站数据的应用程序逻辑的容器,实际上就是之前Reactor模式中的Handler,用来处理读写操作
通过ChannelPipeline来进行流水线处理

下一级接口ChannelInboundHandler
 //用于处理入站相关事件
public interface ChannelInboundHandler extends ChannelHandler
//当Channel已经注册到自己的EventLoop上时调用(一个Channel只会被注册到一个EventLoop上,注册后,才会在发生对应时间时被通知
    void channelRegistered(ChannelHandlerContext var1) throws Exception;
    //在EventLoop上取消注册时
    void channelUnregistered(ChannelHandlerContext var1) throws Exception;
    //Channel处于活跃状态时别调用,此时Channel已经连接/绑定,且已经就绪
    void channelActive(ChannelHandlerContext var1) throws Exception;
    //不活跃了
    void channelInactive(ChannelHandlerContext var1) throws Exception;
    //当Channel读取数据时被调用,数据包被自动包装成了一个Object(默认是ByteBuf)
    void channelRead(ChannelHandlerContext var1, Object var2) throws Exception;
    //上一个读取操作完成后调用
    void channelReadComplete(ChannelHandlerContext var1) throws Exception;

    void userEventTriggered(ChannelHandlerContext var1, Object var2) throws Exception;
    //Channel的可写状态发生改变时调用
    void channelWritabilityChanged(ChannelHandlerContext var1) throws Exception;
    //出现异常时调用
    void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;

ChannelInboundHandlerAdapter:
public class TestChannelHandler extends ChannelInboundHandlerAdapter
ChannelInboundHandlerAdapter实际上就是对这些方法实现的抽象类,相比直接用接口,可以只重写我们需要的方法,没有重写的方法会默认向流水线下一个ChannelHander发送

服务端使用自己定义的Handler:
 //获取流水线,当需要处理客户端的数据时,像流水线一样处理,流水线中有很多Handler
                        channel.pipeline().addLast(new TestChannelHandler());

ChannelPipeline:
每个Channel都对应一个ChannelPipeline(在Channel初始化时就被创建了)
流水线上有多个Handler
给流水线添加2个Handler:一个用于消息接收,一个用于异常处理;
//获取流水线,当需要处理客户端的数据时,像流水线一样处理,流水线中有很多Handler
                channel.pipeline()
                    .addLast(new ChannelInboundHandlerAdapter() { //第一个用于消息接收
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            ByteBuf buf = (ByteBuf) msg;
                            System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                            throw new RuntimeException("发生异常");
                        }
                    })
                    .addLast(new ChannelInboundHandlerAdapter() { //第二个用于处理异常
                        @Override
                        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                            System.out.println("异常处理:" + cause);
                        }
                    });

ChannelOutboundHandlerAdapter:
出站操作也可以加入到Pipeline上的,通过ChannelOutboundHandlerAdapter完成
出站操作应该在入站操作前面,当使用ChannelOutboundHandlerAdapter的write方法时,是从流水线的当前位置倒着往前找下一个ChannelOutboundHandlerAdapter

EventLoop和任务调度:
EventLoop本质上就是一个事件等待/处理线程
一个EventLoopGroup,包含很多个EventLoop,每创建一个连接,就需要绑定到一个EventLoop上,之后EventLoop就会开始监听这个连接,而一个EventLoop可以同时监听很多个Channel,跟之前的Selector一样

Future和Promise:
ChannelFuture,Netty中的Channel的相关操作都是异步进行的,并不是在当前线程同步执行,无法立即的到结果,如果需要结果,就必须利用到Futrue
Promise接口,支持手动设定成功和失败的结果:

编码器和解码器
Netty内置的一些编码器和解码器
之前的数据发送和接收都是使用ByteBuf形式传输。
能否在处理数据之前,过滤一次,将数据转换成想要的类型,也可以将数据进行转换,这就用到了编码器和解码器
当然还有 编解码器ChannelDuplexHandler

MINA网络通信框架:
Mina的底层依赖的主要是Java NIO库,上层提供的是基于事件的异步接口。
整体的结构:
IoService:负责具体的IO相关工作。IOSocketAcceptor和IOSocketChannel,分别对应TCP协议下的服务端和客户端的IOService。
IoProcessor:接口在另一个线程上,负责检查是否有数据在通道上读写。另外,IoProcessor 负责调用注册在IoService 上的过滤器,并在过滤器链之后调用IoHandler。
IoFilter:接口定义一组拦截器,这些拦截器可以包括日志输出、黑名单过滤、数据的编码(write 方向)与解码(read 方向)等
IoHandler:这个接口负责编写业务逻辑,也就是接收、发送数据的地方。IoHandler可以看成是Mina处理流程的终点,每个IoService都需要指定一个IoHandler。
IoSession:一个IoSession对应于一个底层的IO连接

 服务端流程
1、通过SocketAcceptor 同客户端建立连接;
2、连接建立之后 I/O的读写交给了I/O Processor线程,I/O Processor是多线程的;
3、通过I/O Processor 读取的数据经过IoFilterChain里所有配置的IoFilter,IoFilter进行消息的过滤,格式的转换,在这个层面可以制定一些自定义的协议;
4、最后IoFilter将数据交给 Handler  进行业务处理,完成了整个读取的过程;
写入过程也是类似,只是刚好倒过来,通过IoSession.write 写出数据,然后Handler进行写入的业务处理,处理完成后交给IoFilterChain,进行消息过滤和协议的转换,最后通过 I/O Processor 将数据写出到 socket 通道。


 

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值