范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

JavaNIO三组件SelecotrChannel实现原理解析

  一、NIO聊天室入门案例
  在学习原理之前,先来了解一个Java NIO实现聊天室的小案例,该案例只有三个类:NioServer 聊天室服务端、NioClient 聊天室客户端、ClientThread 客户端线程。  服务端代码: /**  * Nio聊天室服务端  *  * @author csp  * @date 2021-11-30 4:13 下午  */ public class NioServer {      /**      * 聊天室成员列表:      */     Map memberChannels;      /**      * 端口      */     private static final int PORT = 8080;      /**      * 选择器      */     private Selector selector;      /**      * 管道      */     private ServerSocketChannel server;      /**      * 缓冲      */     private ByteBuffer buffer;      public NioServer() throws IOException {         // 初始化Selector选择器         this.selector = Selector.open();         // 初始化Channel通道         this.server = getServerChannel(selector);         // 初始化Buffer缓冲:         this.buffer = ByteBuffer.allocate(1024);         // 初始化聊天室成员列表         memberChannels = new ConcurrentHashMap<>();     }      /**      * 初始化Channel通道      *      * @param selector      * @return      * @throws IOException      */     private ServerSocketChannel getServerChannel(Selector selector) throws IOException {         // 开辟一个Channel通道         ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();         // 通道设置为非阻塞模式         serverSocketChannel.configureBlocking(false);         // 通道注册绑定Selector选择器,通道中数据的事件类型为OP_ACCEPT         serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);         // 通道绑定端口         serverSocketChannel.socket().bind(new InetSocketAddress(PORT));          return serverSocketChannel;     }      /**      * 事件监听      */     public void listen() throws IOException {         System.out.println("服务端启动......");         try {             // 无限循环             while (true) {                 // 作用:至少需要有一个事件发生,否则(如果count == 0)就继续阻塞循环                 int count = selector.select();                 if (count == 0) {                     continue;                 }                 // 获取SelectorKey的集合                 Set keySet = selector.selectedKeys();                  Iterator iterator = keySet.iterator();                 while (iterator.hasNext()) {                     // 当前事件对应的SelectorKey                     SelectionKey key = iterator.next();                     // 删除当前事件:表示当前事件已经被消费了                     iterator.remove();                     if (key.isAcceptable()) {                         // Accept类型事件:                         // 通过key获取ServerSocketChannel                         ServerSocketChannel server = (ServerSocketChannel) key.channel();                         // 通过ServerSocketChannel获取SocketChannel                         SocketChannel channel = server.accept();                         // channel设置为非阻塞模式                         channel.configureBlocking(false);                         // channel绑定选择器                         channel.register(selector, SelectionKey.OP_READ);                          // 从channel中获取Host、端口等信息                         System.out.println("客户端连接:"                                 + channel.socket().getInetAddress().getHostName() + ":"                                 + channel.socket().getPort());                     } else if (key.isReadable()) {                         // Read类型事件                         SocketChannel channel = (SocketChannel) key.channel();                         // 用于解密消息内容                         CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();                          // 将消息数据从通道channel读取到缓冲buffer                         //ByteBuffer buffer = ByteBuffer.allocate(50);                         buffer.clear();                         channel.read(buffer);                         buffer.flip();                         // 获取解密后的消息内容:                         String msg = decoder.decode(buffer).toString();                         if (!"".equals(msg)) {                             System.out.println("收到:" + msg);                             if (msg.startsWith("username=")) {                                 String username = msg.replaceAll("username=", "");                                 memberChannels.put(username, channel);                                 System.out.println("用户总数:" + memberChannels.size());                             } else {                                 // 转发消息给客户端                                 String[] arr = msg.split(":");                                 if (arr.length == 3) {                                     // 发送者                                     String from = arr[0];                                     // 接收者                                     String to = arr[1];                                     // 发送内容                                     String content = arr[2];                                     System.out.println(from + "发送给" + to + "的消息:" + content);                                      if (memberChannels.containsKey(to)) {                                         // 解密                                         CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();                                         // 给接收者发送消息                                         memberChannels.get(to).write(encoder.encode(CharBuffer.wrap(from + ":" + content)));                                     }                                 }                             }                         }                      }                 }             }         }catch (Exception e){             System.out.println("服务端启动失败......");             e.printStackTrace();         }finally {             try {                 // 先关闭选择器,在关闭通道                 // 调用 close() 方法将会关闭Selector,同时也会将关联的SelectionKey失效,但不会关闭Channel。                 selector.close();                 server.close();             } catch (IOException e) {                 e.printStackTrace();             }         }     }      public static void main(String[] args) throws IOException {         // 服务端启动:         new NioServer().listen();     } } 客户端线程类: /**  * Nio聊天室客户端线程  *  * @author csp  * @date 2021-11-30 4:13 下午  */ public class ClientThread extends Thread {     /**      * 解密      */     private CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();      /**      * 加密      */     private CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();      /**      * 选择器      */     private Selector selector = null;      /**      * 通道      */     private SocketChannel socket = null;      /**      * 通道key      */     private SelectionKey clientKey = null;      /**      * 用户名      */     private String username;      public ClientThread(String username) {         try {             // 创建一个Selector             selector = Selector.open();              // 创建Socket并注册             socket = SocketChannel.open();             socket.configureBlocking(false);             clientKey = socket.register(selector, SelectionKey.OP_CONNECT);              // 连接到远程地址             InetSocketAddress ip = new InetSocketAddress("localhost", 8080);             socket.connect(ip);              this.username = username;         } catch (IOException e) {             e.printStackTrace();         }     }      /**      * 开辟读取事件的线程      */     @Override     public void run() {         try {             // 监听事件(无限循环)             while (true) {                 // 监听事件                 selector.select();                 // 事件来源列表                 Iterator it = selector.selectedKeys().iterator();                 while (it.hasNext()) {                     SelectionKey key = it.next();                     // 删除当前事件                     it.remove();                      // 判断事件类型                     if (key.isConnectable()) {                         // 连接事件                         SocketChannel channel = (SocketChannel) key.channel();                         if (channel.isConnectionPending())                             channel.finishConnect();                         channel.register(selector, SelectionKey.OP_READ);                         System.out.println("连接服务器端成功!");                          // 发送用户名                         send("username=" + this.username);                     } else if (key.isReadable()) {                         // 读取数据事件                         SocketChannel channel = (SocketChannel) key.channel();                          // 读取数据                         ByteBuffer buffer = ByteBuffer.allocate(50);                         channel.read(buffer);                         buffer.flip();                         String msg = decoder.decode(buffer).toString();                         System.out.println("收到:" + msg);                     }                 }             }         } catch (IOException e) {             e.printStackTrace();         } finally {             // 关闭             try {                 selector.close();                 socket.close();             } catch (IOException e) {             }         }     }      /**      * 发送消息      *      * @param msg      */     public void send(String msg) {         try {             SocketChannel client = (SocketChannel) clientKey.channel();             client.write(encoder.encode(CharBuffer.wrap(msg)));         } catch (Exception e) {             e.printStackTrace();         }     }      /**      * 关闭客户端      */     public void close() {         try {             selector.close();             socket.close();         } catch (IOException e) {         }     } } 客户端代码:/**  * Nio聊天室客户端  *  * @author csp  * @date 2021-12-09 17:03:33  */ public class NioClient {     public static void main(String[] args) {         // 当前客户端的用户名         String username = "lufei";         // 为当前客户端开辟一个线程         ClientThread client = new ClientThread(username);         client.start();          // 输入输出流         BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));          try {             // 循环读取键盘输入             String readline;             while ((readline = sin.readLine()) != null) {                 if (readline.equals("bye")) {                     client.close();                     System.exit(0);                 }                 // 发送消息                 client.send(username + ":" + readline);             }         } catch (IOException e) {             e.printStackTrace();         }     } } 运行测试:
  启动运行测试一下效果!
  服务端先启动,控制台打印:  服务端启动......
  接着启动客户端,控制台打印:  连接服务器端成功!
  这时候服务端会打印客户端的连接信息以及用户名等信息:
  图片
  测试客户端向服务的发送消息,客户端控制台输入  Hello 我是lufei!  ,这时候服务端会收到发送过来的消息内容:
  图片
  我们可以再建立一个客户端启动类NioClient2,并将其启动,服务端会收到客户端2的消息:
  图片
  让客户端1和客户端2之间发送消息:
  图片
  图片
  服务端控制台打印:
  图片
  这样,一个简单的聊天室就搭建成功了,如果小伙伴想自行完善,可以把代码拷贝一下,自己去设计自己想要实现的聊天室功能。
  熟悉了NIO通信的小案例之后,我们通过一张图来分析一下其实现原理:
  从图中可以看出,当有读或写等任何注册的事件发生时,可以从 Selector中获得相应的SelectionKey,同时从SelectionKey中可以找到发生的事件和该事件所发生的具体的SelectableChannel,以获得客户端发送过来的数据。  二、Selector 选择器1、Selector 继承体系
  NIO中实现非阻塞 I/O的核心对象是Selector,Selector是注册各种I/O事件的地方,而且当那些事件发生时,就是Seleetor告诉我们所发生的事件。
  使用NIO中非阻塞I/O编写服务器处理程序,大体上可以分为下面三个步骤:  (1) 向Selector对象注册感兴趣的事件。  (2) 从Selector中获取感兴趣的事件。  (3) 根据不同的事件进行相应的处理。  2、Selector 选择器的创建
  在聊天室案例的NioServer服务端类中,选择器的初始化创建位于其构造函数中:  public NioServer() throws IOException {     // 初始化Selector选择器     // 也可以通过实现java.nio.channels.spi.SelectorProvider.openSelector()抽象方法自定义一个Selector。     this.selector = Selector.open();     // 初始化Channel通道     // 初始化Buffer缓冲:     // 初始化聊天室成员列表 }
  Selector  可以通过它自己的open()  方法创建,借助java.nio.channels.spi.SelectorProvider  类创建一个新的 Selector 选择器。也可以通过实现java.nio.channels.spi.SelectorProvider  类的抽象方法openSelector()  来自定义实现一个Selector。Selector 一旦创建将会一直处于 open 状态直到调用了close()  方法为止。
  我们跟进这个  open()  方法:public abstract class Selector implements Closeable {        protected Selector() {}        // 该方法返回一个Selector选择器:     public static Selector open() throws IOException {        // 通过SelectorProvider构建选择器         return SelectorProvider.provider().openSelector();     }    ... }
  继续向下跟进  SelectorProvider.provider()  方法:// 该方法位于SelectorProvider类中吗,可以获取能够构建Selector选择器的SelectorProvider对象 public static SelectorProvider provider() {     synchronized (lock) {         if (provider != null)             return provider;         return AccessController.doPrivileged(             new PrivilegedAction() {                 public SelectorProvider run() {                         if (loadProviderFromProperty())                             return provider;                         if (loadProviderAsService())                             return provider;                      // DefaultSelectorProvider会根据不同的操作系统去创建不同的SelectorProvider                         provider = sun.nio.ch.DefaultSelectorProvider.create();                         return provider;                     }                 });     } }
  我们跟进  DefaultSelectorProvider.create()  方法,它会根据不同的操作系统去创建不同的SelectorProvider:如果是Windows操作系统,则创建的是  WindowsSelectorProvider  对象。  如果是MacOS操作系统,则创建的是  KQueueSelectorProvider  对象。  如果是Linux操作系统,则创建的是  EPollSelectorProvider  对象。
  例如我使用的是Mac系统,那么跟进  create()  方法时,进入如下代码:public class DefaultSelectorProvider {     private DefaultSelectorProvider() {     }      public static SelectorProvider create() {         return new KQueueSelectorProvider();     } }  // 继续往下跟进KQueueSelectorProvider(),进入KQueueSelectorProvider类: public class KQueueSelectorProvider extends SelectorProviderImpl {     public KQueueSelectorProvider() {     }      public AbstractSelector openSelector() throws IOException {         // KQueueSelectorImpl是具体实现类,我们继续往下跟进,去看看实现逻辑:         return new KQueueSelectorImpl(this);     } }
  继续跟进,进入KQueueSelectorImpl类内部:  class KQueueSelectorImpl extends SelectorImpl {     // 用于存放read端的文件描述     protected int fd0;     // 用于存放write端的文件描述     protected int fd1;          ......        KQueueSelectorImpl(SelectorProvider var1) {         super(var1);        // makePipe方法是native修饰的本地方法,其作用是返回两个文件描述符var2,         // var2高位存放的是通道read端的文件描述符,低32位存放的是write端的文件描述符。         long var2 = IOUtil.makePipe(false);         // 文件描述符fd0只读数据         this.fd0 = (int)(var2 >>> 32);         // 文件描述符fd1只写数据         this.fd1 = (int)var2;                  ......     }        ...... }
  问题:为什么在不同操作系统平台,Provider不同呢?
  因为网络IO是跟操作系统息息相关的,不同的操作系统的实现可能都不一样。比如我们在Linux操作系统安装的JDK版本,和Windows操作系统上就不太一样。  3、Selector 选择器绑定 Channel 管道
  如聊天室案例中服务端 NioServer,Channel 管道与 Selector 选择器的绑定通过如下方式:  /**  * 初始化Channel通道  *  * @param selector  * @return  * @throws IOException  */ private ServerSocketChannel getServerChannel(Selector selector) throws IOException {     // 开辟一个Channel通道     ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();     // 通道设置为非阻塞模式     serverSocketChannel.configureBlocking(false);     // 为了将Channel跟Selector绑定在一起,我们需要将Channel注册到Selector上,调用Channel的register()方法     // 通道中数据的事件类型为OP_ACCEPT(事件类型总共有4种,详情请参考下文的第4小节,管道与选择器之间的桥梁 SelectionKey )     serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);     // 通道绑定端口     serverSocketChannel.socket().bind(new InetSocketAddress(PORT));     return serverSocketChannel; }
  注意  :Channel必须是非阻塞模式才能注册到Selector上,所以,无法将一个FileChannel注册到Selector,因为FileChannel没有所谓的阻塞还是非阻塞模式。
  管道 Channel 和 选择器 Selector 的关系  :  Selector 通过不断轮询的方式同时监听多个 Channel 的事件,注意,这里是同时监听,一旦有 Channel 准备好了,它就会返回这些准备好了的 Channel,交给处理线程去处理。  在NIO编程中,通过 Selector 我们就实现了一个线程同时处理多个连接请求的目标,也可以一定程序降低服务器资源的消耗。  4、管道与选择器之间的桥梁 SelectionKey
  我们再来看一下 Selector 与 Channel 的关系图:
  图片
  如上图所示,将管道与注册到选择器上时,  register()  方法需要传递2个参数,一个是 Selector 选择器对象,另一个是管道中事件的类型 SelectionKey,选择器通过该对象的静态变量值去识别不同管道中的事件内容。所以SelectionKey  又可以看作是Channel和Selector之间的一座桥梁,把两者绑定在了一起。// 为了将Channel跟Selector绑定在一起,我们需要将Channel注册到Selector上,调用Channel的register()方法 // 通道中数据的事件类型为OP_ACCEPT serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
  SelectionKey 有如下4种事件类型:  /**  * 读事件:0000 0001  */ public static final int OP_READ = 1 << 0;  /**  * 写事件:0000 0100  */ public static final int OP_WRITE = 1 << 2;  /**  * 连接事件:0000 1000,连接操作,Client端支持的一种操作  */ public static final int OP_CONNECT = 1 << 3;  /**  * 接受事件:0001 0000,可接受操作仅ServerSocketChannel支持  */ public static final int OP_ACCEPT = 1 << 4;  5、SelectionKey 的几个常用方法
  参考文章:https://juejin.cn/post/6844903440573792270  5.1、interestOps()方法作用:返回代表需要Selector监控的IO操作的,可以通过以下方法来判断Selector是否对Channel的某种事件感兴趣。  interest数据集:当前Channel感兴趣的操作,此类操作将会在下一次选择器   select()   操作时被交付,可以通过selectionKey.interestOps(int)  进行方法修改。使用方式如下:  // 获取该selectionKey感兴趣的事件集 int interestSet = selectionKey.interestOps();   boolean isInterestedInAccept =     (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean isInterestedInWrite = interestSet & SelectionKey.OP_WRITE; 5.2、readyOps()方法作用:获取此selectionKey键上的ready操作集合,即在当前通道上已经就绪的事件。可以通过  readyOps()  方法获取所有就绪了的事件,也可以通过isXxxable()  方法检查某个事件是否就绪。ready数据集:表示此选择键上,已经就绪的操作,每次   select()   时,选择器都会对ready集合进行更新,外部程序无法修改此集合。使用方式如下:   // 创建ready集合的方法 int readySet = selectionKey.readyOps(); // 检查这些操作是否就绪的方法 boolean isReadable();// 是否可读,是返回 true,检测此键是否为"read"事件.等效于:k.,readyOps() & OP_READ != 0; boolean isWritable():// 是否可写,是返回 true boolean isConnectable():// 是否可连接,是返回 true boolean isAcceptable():// 是否可接收,是返回 true 5.3、channel()、selector()方法作用:通过  channel()、selector()  方法可以获取绑定的通道Channel和选择器Selector,代码案例如下:Channel channel = selectionKey.channel();Selector selector = selectionKey.selector();  5.4、attachment()方法
  可以将一个或者多个附加对象绑定到SelectionKey上,以便容易的识别给定的通道。通常有两种方式:  在注册的时候直接绑定:    SelectionKey key=channel.register(selector,SelectionKey.OP_READ,theObject); 在绑定完成之后附加:  selectionKey.attach(theObject);// 绑定
  绑定之后,可通过对应的SelectionKey取出该对象:  selectionKey.attachment();
  如果要取消该对象,则可以通过该种方式:  selectionKey.attach(null)
  需要注意的是如果附加的对象不再使用,一定要人为清除,因为垃圾回收器不会回收该对象,若不清除的话会成内存泄漏。
  一个单独的通道可被注册到多个选择器中,有些时候我们需要通过  isRegistered()  方法来检查一个通道是否已经被注册到任何一个选择器上。通常来说,我们并不会这么做。
  有点类似于ThreadLocal,可以让不同的线程都拥有一份自己的变量副本,且相互隔离,但是区别在于ThreadLocal的对象过期后会被自动回收的!  6、Selector 的几个常用方法这一部分参考自这 2 篇文章:
  https://blog.csdn.net/tangtong1/article/details/103414499https://blog.csdn.net/weixin_34237596/article/details/92738331  6.1、select()方法
  一旦将一个或多个Channel注册到Selector上了,我们就可以调用它的  select()  方法了,它会返回注册时感兴趣的事件中就绪的事件。
  select()  方法有三种变体:select()  ,无参数,阻塞到至少有一个通道在你注册的事件上就绪了才返回。(当然是我们注册的感兴趣的事件)。select(timeout)  ,带超时,阻塞直到某个Channel有就绪的事件了,或者超时了才返回。selectNow()  ,立即返回,非阻塞,只要有通道就绪就立刻返回。
  select()  方法返回的 int 值表示有多少通道已经就绪,是自上次调用select()  方法后有多少通道变成就绪状态。之前在select()  调用时进入就绪的通道不会在本次调用中被记入,而在前一次select()  调用进入就绪但现在已经不在处于就绪的通道也不会被记入。例如:首次调用select()  方法,如果有一个通道变成就绪状态,返回了1,若再次调用select()  方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的Channel没有做任何操作,现在就有两个就绪的通道,但在每次select()  方法调用之前,只有一个通道就绪了。
  一旦调用  select()  方法,并且返回值不为 0 时,则可以通过调用Selector的selectedKeys()  方法来访问已选择键集合。如下:Set selectionKeySet = selector.selectedKeys(); 6.2、selectKeys()方法
  Selector.selectKeys()  ,可以获取该选择器相关联的SelectionKey集合,通过遍历这些SelectorKey,可以进一步获取其感情兴趣的事件类型,以及其关联的Channel通道。Set selectedKeys = selector.selectedKeys();  Iterator it = selectedKeys.iterator();  while(it.hasNext()) {     SelectionKey key = keyIterator.next();      if(key.isAcceptable()) {         // 可连接事件     } else if (key.isConnectable()) {         // 连接事件     } else if (key.isReadable()) {         // 读取数据事件     } else if (key.isWritable()) {         // 写入事件     }     it.remove(); }
  最后,一定要记得调用  it.remove();  移除已经处理的 SelectionKey。6.3、wakeUp()方法
  前面我们说了调用  select()  方法时,调用者线程会进入阻塞状态,直到有就绪的 Channel 才会返回。其实也不一定,wakeup()  就是用来破坏规则的,可以在另外一个线程调用wakeup()  方法强行唤醒这个阻塞的线程,这样select()  方法也会立即返回。
  如果调用  wakeup()  时并没有线程阻塞在select()  上,那么,下一次调用select()  将立即返回,不会进入阻塞状态。这跟LockSupport.unpark()  方法是比较类似的。6.4、close()方法
  调用   close()  方法将会关闭 Selector,同时也会将关联的 SelectionKey 失效,但不会关闭 Channel。三、Channel 通道1、Channel 继承体系
  图片
  通道是一个对象,通过它可以读取和写入数据,当然所有数据都通过  Buffer  对象来处理。  我们永远不会将字节直接写入通道,而是将数据写入包含一个或者多个字节的缓冲区。同样也不会直接从通道中读取字节,而是将数据从通道读入缓冲区,再从缓冲区获取这个字节  。
  使用NIO读取数据可以分为下面三个步骤:  (1)从FileInputStream获取Channel。  (2)创建Buffer。  (3)将数据从Channel读取到Buffer中。
  使用NIO写入数据同样分为三个步骤:  (1)从FileInputStream获取Channel。  (2)创建Buffer。  (3)将数据从Channel写入Buffer。  2、Channel 与 Steam 流的区别
  参考自:https://blog.csdn.net/tangtong1/article/details/103341597
  BIO是面向流(Stream)编程的,流又分成 InputStream 和 OutputStream ,那么 Channel 和 Stream 有什么区别呢?  Channel 可以同时支持读和写,而 Stream 只能支持单向的读或写  (所以分成InputStream和OutputStream)。  Channel支持异步读写,Stream通常只支持同步  。  Channel总是读向(read into)Buffer,或者写自(write from)Buffer(有点绕,以 Channel 为中心,从 Channel 中读出数据到 Buffer,从 Buffer 中往 Channel 写入数据),可以参考如下代码:
  将 Channel 管道中的数据读取到 Buffer:  if (key.isReadable()) {    // 读取数据事件    SocketChannel channel = (SocketChannel) key.channel();     // 读取数据    ByteBuffer buffer = ByteBuffer.allocate(50);    // 将管道中的数据读取到缓冲Buffer中:    channel.read(buffer);    buffer.flip();    String msg = decoder.decode(buffer).toString();    System.out.println("收到:" + msg); }
  将数据通过 Buffer 写入到 Channel 管道:  /**  * 发送消息  *  * @param msg  */ public void send(String msg) {     try {         SocketChannel client = (SocketChannel) clientKey.channel();         // 将数据通过 Buffer 写入到 Channel 管道:         client.write(encoder.encode(CharBuffer.wrap(msg)));     } catch (Exception e) {         e.printStackTrace();     } }
  由此可知,管道中的数据传输、从管道中读取/写入数据,数据本身都是需要包装再 Buffer 缓冲中的  !  原文:兴趣使然的草帽路飞
  https://mp.weixin.qq.com/s/Sjn5pUUDUFb0VOFoeGnj1A

中国电信构建基于IPv6的网络空间命运共同体IPv6将是网络基础设施发展以及产业生态构建的最终方向,加快部署IPv6是加快网络强国建设加速国家信息化进程助力经济社会发展赢得未来国际竞争新优势的紧迫需求。现在我国IPv6部署情从目前的困局来看,滴滴会从美国退市吗刚刚上市一周,就开始考虑退市,滴滴这也算是创下记录了。目前来看,最坏的结果是退市,不过可能性比较低,滴滴这种市值数百亿美元的大公司,退市一时半会无人承接,也不符合各方利益。滴滴在招不做手机一切都好!LG第二季度业绩创12年来新高前段时间,当LG宣布退出手机业务的时候,曾经在业内引起了不小的轰动。同时,由于LG到目前为止每年依然有两千多万台的出货量,所以也让不少人都感到非常可惜。不过,现在看来,LG当初的这滴滴被下架,网民说要卸载,网约车垄断格局能改写吗?滴滴因为严重违规违法收集个人信息问题,APP被下架,成为媒体关注的焦点,也是网民热论的焦点。不少网友说,他们已经卸载滴滴软件,今后再也不会使用滴滴出行了。从笔者在网络上发起的调查来荣耀30和荣耀50哪款更值得入手,看完对比,你就清楚了荣耀50系列发布之后,整体的热度还是非常高,大家对于这款手机整体的评价也不错,虽然处理器搭载的是骁龙778G,并不是非常强悍,但使用起来,还是完全可以满足需求。其中销量最好的应该是LG暂停了在韩国零售店销售iPhone等苹果产品的计划根据一份新的报告,LG最近暂停了在其韩国零售店销售iPhone等苹果产品的计划。早在6月份,据报道,这家韩国公司正在与苹果就零售合作进行谈判。然而,在一些反对声音之后,现在LG似乎续航525公里补贴后27。6万元特斯拉ModelY新增标准续航版7月8日,据特斯拉中国官网信息显示,特斯拉ModelY新增标准续航版车型,补贴前起售价29。184万元,补贴后的起售价为27。6万元(私人购买补贴金额为1。584万元)。作为特斯拉2021AppleTV4K可能是未来4年苹果最保值的产品之一2021年苹果终于推出了AppleTV4K,作为电视盒子没啥好说的,肯定是目前市上最好用的一款。我也于6月5日拿到了全新一代的AppleTV4K,只是最近有点懒不想写文章,闲来无事什么样的人不适合戴助听器,带上头晕也听不清楚,还不如不戴,怎么办?调试合适的话,是不会出现戴上助听器后出现头晕现象的。如出现戴听器后头晕。可能是助听器的调试出了问题。也有可能是患者本身的身体情况,所以如出现这个情况一定要确认是什么原因。最后对症处华为鸿蒙用户超3000万,为什么其他智能手机厂商不愿意使用?希望华为尽快向这些厂家收取5G手机专利费!同样是安卓套壳系统,别家的系统经过这么多年的发展都形成自己独有的核心竞争力了,凭什么放弃?用华为这种安卓套鸿蒙壳的系统,把系统产生的大把收2021年近六成安卓手机在用!国产厂商引领,高刷屏已成行业大趋势屏幕作为用户与手机交互的第一通道,近年来手机厂商们下了不少功夫,总体的趋势呈现往更高画质更高刷新率的全面屏进化。安兔兔平台最新发布了2021年Q2季度安兔兔用户屏幕刷新率分布情况,
技术狂魔最新力作,比亚迪EA1能否如愿大卖?以下文章来源于车与舆2020年的比亚迪可谓是出尽了风头,无论是快速研发口罩刀片电池还是旗舰车型汉,都取得了巨大的成果,而在新的一年比亚迪也没有丝毫的懈怠,在上海车展上推出了代号为E360为何选择哪吒造车?周鸿祎硬核PPT给出答案当前国内汽车圈最大的变化,就是各家互联网科技公司,相继跨界造车。百度小米华为,都已经开始深入参与进智能汽车行业,谁也不想在下一个风口错失先机。5月10日,360公司在其官方微博发文4月车企销量TOP10长安进前三甲,上汽通用跌至第七5月11日,乘联会发布了4月乘用车产销数据。数据显示,4月狭义乘用车批发销量为165。9万辆,同比增长10。5,环比3月下降9。8。今年前四个月,狭义乘用车批发销量累计为667。6美国销量最火车型换电!福特F150纯电版将发布重启闪电命名这可能是美国今年年度最火爆的车型,当地时间周一,福特汽车在官网发布声明,正式宣布将于美东时间5月19日举办纯电动F150皮卡发布会。美国作为车轮上的国家,皮卡也是其国内最受欢迎的车智伴新品发布!携手思必驰深度解读产品背后的AI教育方式2月21日,智伴科技于广州长隆酒店召开了唤醒新视界发布会。智伴儿童机器人1X(以下简称智伴1X)正式亮相,吸引了广大媒体的关注。智伴1X的正式发布,预示着智伴科技在儿童人工智能产品美国允许供应商向华为出售汽车零部件芯片盖世汽车讯据外媒报道,两名知情人士透露,美国官员已经批准了华为为其不断增长的汽车零部件业务购买芯片的许可证申请。由于此前特朗普政府对网络设备和智能手机业务中使用的芯片和其他部件的销重启汽车业务,力帆首款换电新车量产下线5月18日,力帆科技发布消息称,公司将重启汽车业务。力帆科技首款换电新车型力帆80V在两江新区量产下线,这也标志着力帆已经走出病房,重新回归正常并步入新的发展阶段。据了解,力帆80从长安蔚来到阿维塔华为与宁德时代入局,蔚来离场?5月20日,长安汽车发布公告称,原长安蔚来新能源汽车科技有限公司更名为阿维塔科技有限公司(以下简称阿维塔)。公告显示,阿维塔将完全市场化运作,独立运营,独立发展,与长安汽车华为宁德李峰加盟华人运通任联席总裁,全面负责高合汽车销售等业务5月19日,李峰在离开东风悦达起亚后下一站去向已经确定。今日华人运通官方宣布,李峰加盟该公司任联席总裁,兼任高合汽车销售服务公司董事长,向华人运通董事长兼CEO丁磊汇报。据悉,李峰比亚迪第100万辆新能源汽车下线何猷君成第100万位车主5月19日,比亚迪举办第100万辆新能源汽车正式下线仪式,成为首个进入新能源汽车百万辆俱乐部的中国品牌。下线仪式上,王传福向深圳V5电子竞技俱乐部董事长兼CEO何猷君,交付比亚迪第三款适合年轻人的纯电车,宏光miniev马卡龙强势领衔很多朋友在从大学毕业后,都面临着一个问题,那就是上班的焦虑。这份焦虑不仅来自于工作的压力,而且在上下班路上面对拥挤的公交地铁,都是让人感觉到崩溃的地方。因此不少年轻人即使让自己背负