同步 VS 异步
同步
同步编程是指当程序执行某个操作时,它必须等待该操作完成才能继续执行下一个操作。这意味着程序在执行网络请求时必须等待网络请求完成才能继续执行下一步操作,因此同步编程通常会导致程序的性能降低。在网络编程中,同步编程通常用于处理较慢的网络请求,例如文件读写操作。
异步
异步编程是指将一个操作 (如网络请求) 放置在线程中,但并不阻塞该线程,而是在操作完成或者出现异常时再通知线程。这意味着线程可以在执行其他任务的同时处理网络请求,从而提高了程序的效率。在异步编程中,线程池可以帮助管理多个异步操作,从而避免线程的频繁创建和销毁。
阻塞
阻塞编程是指在处理网络请求时,如果网络请求无法完成,程序将无法继续执行其他操作,而是被迫等待网络请求完成。这意味着程序在执行网络请求时必须等待网络请求完成才能继续执行下一步操作,因此阻塞编程通常也会导致程序的性能降低。在网络编程中,阻塞编程通常用于处理固定的、快速的网络请求,例如 HTTP 请求。
非阻塞
非阻塞编程是指在处理网络请求时,等待网络请求完成之后再继续执行下一个操作,而不是在处理网络请求时阻塞线程。这可以提高程序的响应速度,但需要更多的内存和资源来支持长时间运行的网络请求。在非阻塞编程中,可以使用 poll、epoll 等事件驱动模型来管理网络请求。
异步 VS 非阻塞
异步编程和非阻塞编程的区别主要在于它们的执行方式。异步编程是将操作放置在线程中,但并不阻塞线程,而是在操作完成或者出现异常时再通知线程。非阻塞编程则是等待网络请求完成之后再继续执行下一个操作。因此,异步编程更加灵活,可以在处理网络请求的同时执行其他任务,而非阻塞编程则需要更多内存和资源来支持长时间运行的网络请求。
同步 VS 阻塞
同步和阻塞的区别主要在于它们的执行方式。同步编程必须等待操作完成才能继续执行下一个操作,而阻塞编程则是被迫等待操作完成才能继续执行下一个操作。因此,同步编程通常适用于处理较慢的网络请求,而阻塞编程通常适用于处理固定的、快速的网络请求。
举例说明(个人理解【如有错误请指出联系笔者感激不尽!!!!】)
比如我们把客人去饭店吃饭的事情举例
【网络请求】 客人(最多) 【处理请求】服务员(中)【读写操作】 厨师(最少)
同步阻塞:
m个客人来到饭店,n个服务员对应n个客人,(m-n)客人等待【阻塞】服务员处理完自己负责的客人,当服务员把客人点的菜单交给厨师时,此时服务员不能离开,而且客人也不能干自己事情【同步】,等待厨师做完饭,服务员上完菜,这时客人可以做自己事情,服务员接着服务剩下的客人。
同步非阻塞:
m个客人来到饭店,n个服务员对应n个客人,但是此时服务员把客人点的菜单交给厨师的做菜队列中,接着服务剩下的(m-n)客人【非阻塞】。而且此时客人也不能干自己事情【同步】,等待厨师做完饭,通知服务员上完菜,这时客人可以做自己事情,服务员接着服务剩下的客人。
异步阻塞(没有此情况)
异步非阻塞(AIO):
m个客人来到饭店,n个服务员对应n个客人,但是此时服务员把客人点的菜单交给厨师的做菜队列中,接着服务剩下的(m-n)客人【非阻塞】。点完菜的客人可以离开饭店做自己事情【异步】等待厨师做完饭,通知服务员上菜,服务员通知客人可以吃饭。
NIO的核心组成部分
Channel
可以翻译成“通道”。Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如: InputStream, OutputStream.而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。
Buffer
Selector
FileChannel(只能工作在阻塞的状态下)
基本的读写操作
public void readFile() throws Exception {
RandomAccessFile file = new RandomAccessFile("D:\\code\\JVMCODE\\a.txt", "r");
FileChannel channel = file.getChannel();
ByteBuffer buf = ByteBuffer.allocate(10);
buf.clear();//相当于清空缓存
int len = 0;//记录读入
while((len = channel.read(buf)) != -1){
buf.flip();//读写指针指到缓存头部
while(buf.hasRemaining()){
System.out.println((char) buf.get());
}
System.out.println("进入下一个读操作");
buf.clear();
}
channel.close();
file.close();
}
@Test
public void writeFile() throws Exception{
RandomAccessFile file = new RandomAccessFile("D:\\code\\JVMCODE\\a.txt", "rw");
FileChannel channel = file.getChannel();
ByteBuffer buf = ByteBuffer.allocate(40);
buf.clear();//相当于清空缓存
buf.put("你好世界".getBytes());
buf.flip();//读写指针指到缓存头部
while (buf.hasRemaining()){
channel.write(buf);
System.out.println("进行下一次的写操作");
}
channel.close();
file.close();
}
FileChannel的常用方法
方法 | 描述 |
int read(ByteBuffer dst) | 从Channel中读取数据到ByteBuffer |
int read(ByteBufer[] dsts) | 将Channel中的数据“分散”到ByteBuffer[] |
int write(ByteBuffer src) | 将ByteBuffer 中的数据写入Channel |
long write(ByteBuffer[] srcs) | 将ByteBuffer[] 中的数据“聚集”到Channel |
long position() | 返回此通道的文件位置 |
FileChannel position(long p) | 设置此通道的文件位置 |
long size() | 返回此通道的文件的当前大小 |
FileChannel truncate(long s) | 将此通道的文件截取为给定大小 |
void force(boolean metaData) | 强制将所有对此通道的文件更新写入到存储设备中 |
transferTo(初始位,长度,目的通道)
|
将数据从 FileChannel 传输到其他的 channel 中
|
transferFrom(原通道,初始位,长度)
|
将FileChannel数据从源通道传输到 FileChannel 中
|
SocketChannel
SocketChannel
使用的步骤
【1】 创建SocketChannel
public void createSocketChannel() throws Exception {
//方式一:使用SocketChannel的静态方法open(套接字)【推荐使用】
SocketChannel.open(new InetSocketAddress("www.baidu.com",80));
//方式二:使用功connect
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("www.baidu.com",80));
}
【2】连接检验
public void isConnect() throws Exception{
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com",80));
System.out.println(socketChannel.isOpen());//测试是否为open状态
System.out.println(socketChannel.isConnected());//测试是否为已经被连接
System.out.println(socketChannel.isConnectionPending());//测试是正在被连接
System.out.println(socketChannel.finishConnect());//是否已经完成连接
}
【3】读写模式(是否为阻塞或者非阻塞)
@Test
public void isBlocking() throws Exception{
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com",80));
System.out.println(socketChannel.isBlocking());
socketChannel.configureBlocking(false);//设置非阻塞
System.out.println(socketChannel.isBlocking());
}
【4】读写
阻塞读写
@Test
public void read1() throws Exception{
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com",80));
ByteBuffer buf = ByteBuffer.allocate(1024);
socketChannel.read(buf);//线程被阻塞因为百度不为发给你消息
socketChannel.close();
System.out.println("read over");//不会打印
}
非阻塞读写
@Test
public void read2() throws Exception{
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com",80));
ByteBuffer buf = ByteBuffer.allocate(1024);
socketChannel.configureBlocking(false);//设置非阻塞
socketChannel.read(buf);
socketChannel.close();
System.out.println("read over");//会打印
}
【5】设置参数
@Test
public void set() throws Exception{
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com",80));
socketChannel.setOption(StandardSocketOptions.TCP_NODELAY,Boolean.TRUE);
System.out.println(socketChannel.getOption(StandardSocketOptions.TCP_NODELAY));
}
ServerSocketChannel
非阻塞的accpet()方法
@Test
public void notBlockAccept() throws Exception{
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put("hello".getBytes());
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(1234));
ssc.configureBlocking(false);
while (true){
System.out.println("等待连接!");
SocketChannel accept = ssc.accept();
if(accept == null){
System.out.println("null connect");
Thread.sleep(1000);
}else {
System.out.println("建立连接:"+accept.getRemoteAddress());
buf.rewind();
/*
Buffer.rewind() 方法用于将字符缓冲区恢复到最初的状态,
即缓冲区中的数据没有被读取或写入。
*/
accept.write(buf);//获取hello值
accept.close();
}
}
}
【1】打开ServerSocketChannel
@Test
public void open() throws Exception {
ServerSocketChannel ssc = ServerSocketChannel.open();
}
【2】监听新的链接
@Test
public void accept() throws Exception{
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(1222));
while (true){
System.out.println("等待连接");
SocketChannel accept = ssc.accept();
}
}
}
只会执行一次因为这是阻塞的
DatagramChannel
@Test
public void open() throws Exception{
DatagramChannel server = DatagramChannel.open();
server.socket().bind(new InetSocketAddress(1243));
}
【2】链接
client.connect(new InetSocketAddress("127.0.0.1",10086));
int readSize= client.read(sendBuffer);
server.write(sendBuffer);
【3】接收数据 recevice()接收UDP包
@Test
public void reviceUDP() throws Exception{
DatagramChannel server = DatagramChannel.open();
server.socket().bind(new InetSocketAddress(1243));
server.configureBlocking(false);
ByteBuffer receviceBuffer = ByteBuffer.allocate(34);
receviceBuffer.clear();
SocketAddress receive = server.receive(receviceBuffer);
System.out.println(receive);
// SocketAddress 可以获得发包的 ip、端口等信息,用 toString 查看,格式如下
}
【4】发送数据 send()发送UDP包
@Test
public void sendUDP() throws Exception {
DatagramChannel server = DatagramChannel.open();
ByteBuffer buf = ByteBuffer.wrap("hello".getBytes());
server.send(buf, new InetSocketAddress("127.0.0.1", 1003));
}
综合案列
/**
* 收包端
*/
@Test
public void receive() throws Exception {
DatagramChannel receiveChannel= DatagramChannel.open();
InetSocketAddress receiveAddress= new InetSocketAddress(9999);
receiveChannel.bind(receiveAddress);
ByteBuffer receiveBuffer= ByteBuffer.allocate(512);
while (true) {
receiveBuffer.clear();
SocketAddress sendAddress= receiveChannel.receive(receiveBuffer);
receiveBuffer.flip();
System.out.print(sendAddress.toString() + " ");
System.out.println(Charset.forName("UTF-8").decode(receiveBuffer));
}
}
/**
* 只接收和发送 9999 的数据包
*/
@Test
public void testConect1() throws Exception {
DatagramChannel connChannel= DatagramChannel.open();
connChannel.bind(new InetSocketAddress(9998));
connChannel.connect(new InetSocketAddress("127.0.0.1",9999));
connChannel.write(ByteBuffer.wrap("发包".getBytes("UTF-8")));
ByteBuffer readBuffer= ByteBuffer.allocate(512);
while (true) {
try {
readBuffer.clear();
connChannel.read(readBuffer);
readBuffer.flip();
System.out.println(Charset.forName("UTF-8").decode(readBuffer));
}catch(Exception e) {
}
}
}