java类io.netty.channel.socket.DatagramChannel的实例源码

TF2UdpServer.java 文件源码 项目:Mods 阅读 26 收藏 0 点赞 0 评论 0
public void run() {
    try {
        Bootstrap boot = new Bootstrap();
        boot.group(group)
         .channel(NioDatagramChannel.class)
         .handler(new ChannelInitializer<DatagramChannel>() {

            @Override
            protected void initChannel(DatagramChannel ch) throws Exception {
                channel = ch;
                ch.pipeline().addLast(new UdpChannelHandlerServer(TF2UdpServer.this));
            }

         });
        boot.bind(port).sync().channel().closeFuture();
    } catch (InterruptedException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
MulticastEndpoint.java 文件源码 项目:yajsw 阅读 31 收藏 0 点赞 0 评论 0
public void init(ChannelPipelineFactory factory) throws Exception
{
    id = String.format("%1$020d",
            Math.abs(new Random(System.currentTimeMillis()).nextLong()))
            .getBytes();

    group = new OioEventLoopGroup();
    connectionlessBootstrap = new Bootstrap();
    connectionlessBootstrap.group(group);
    connectionlessBootstrap.option(ChannelOption.SO_BROADCAST, true);
    connectionlessBootstrap.handler(factory);
    connectionlessBootstrap.channel(OioDatagramChannel.class);
    ;
    datagramChannel = (DatagramChannel) connectionlessBootstrap
            .bind(new InetSocketAddress(mcastGroupPort)).sync().channel();
    multicastAddress = new InetSocketAddress(mcastGroupIp, mcastGroupPort);
    NetworkInterface networkInterface = NetworkInterface
            .getByInetAddress(InetAddress.getByName(bindAddress));
    // for (Enumeration nifs = NetworkInterface.getNetworkInterfaces();
    // nifs.hasMoreElements(); )
    datagramChannel.joinGroup(multicastAddress, null);// (NetworkInterface)
                                                        // nifs.nextElement());
    init = true;
    if (debug)
        factory.debug();
}
UdpServer.java 文件源码 项目:reactor-netty 阅读 30 收藏 0 点赞 0 评论 0
@Override
public Mono<? extends NettyContext> newHandler(BiFunction<? super UdpInbound, ? super UdpOutbound, ? extends Publisher<Void>> handler) {
    final BiFunction<? super UdpInbound, ? super UdpOutbound, ? extends Publisher<Void>>
            targetHandler =
            null == handler ? ChannelOperations.noopHandler() : handler;

    return Mono.create(sink -> {
        Bootstrap b = options.get();
        SocketAddress adr = options.getAddress();
        if(adr == null){
            sink.error(new NullPointerException("Provided UdpServerOptions do not " +
                    "define any address to bind to "));
            return;
        }
        b.localAddress(adr);
        ContextHandler<DatagramChannel> c = doHandler(targetHandler, sink, adr);
        b.handler(c);
        c.setFuture(b.bind());
    });
}
UdpClient.java 文件源码 项目:reactor-netty 阅读 27 收藏 0 点赞 0 评论 0
@Override
public Mono<? extends NettyContext> newHandler(BiFunction<? super UdpInbound, ? super UdpOutbound, ? extends Publisher<Void>> handler) {
    final BiFunction<? super UdpInbound, ? super UdpOutbound, ? extends Publisher<Void>>
            targetHandler =
            null == handler ? ChannelOperations.noopHandler() : handler;

    return Mono.create(sink -> {
        Bootstrap b = options.get();
        SocketAddress adr = options.getAddress();
        if(adr == null){
            sink.error(new NullPointerException("Provided UdpClientOptions do not " +
                    "define any address to bind to "));
            return;
        }
        b.remoteAddress(adr);
        ContextHandler<DatagramChannel> c = doHandler(targetHandler, sink, adr);
        b.handler(c);
        c.setFuture(b.connect());
    });
}
UDPListener.java 文件源码 项目:DistributedLog4j 阅读 32 收藏 0 点赞 0 评论 0
public void activateOptions() throws InterruptedException {

        Bootstrap b = new Bootstrap();
        b.group(group)
            .channel(NioDatagramChannel.class)
            .handler(new Log4jHandler());
        b.option(ChannelOption.SO_REUSEADDR, true);
        b.option(ChannelOption.IP_MULTICAST_IF, MulticastSettings.getIface());
        b.option(ChannelOption.TCP_NODELAY, true);

        InetSocketAddress addr = new InetSocketAddress(MulticastSettings.getAddressToBind(), port);

        b.localAddress(port).remoteAddress(addr);
        ch = (DatagramChannel) b.bind().sync().channel();

        ch.joinGroup(multicastAddress, MulticastSettings.getIface()).sync();
    }
TftpServer.java 文件源码 项目:jtftp 阅读 30 收藏 0 点赞 0 评论 0
public void run() throws Exception {
    // Configure the server.
    EventLoopGroup group = new NioEventLoopGroup();
    try {
        Bootstrap b = new Bootstrap();
        b.group(group)
                .channel(NioDatagramChannel.class)
                .handler(new ChannelInitializer<DatagramChannel>() {
                    @Override
                    public void initChannel(DatagramChannel ch) throws Exception {
                        ch.pipeline().addLast(
                                new LoggingHandler(LogLevel.INFO),
                                new TftpServerHandler());
                    }
                });

        b.bind(port).sync().channel().closeFuture().await();
    } finally {
        group.shutdownGracefully();
    }
}
UAS.java 文件源码 项目:sipstack 阅读 28 收藏 0 点赞 0 评论 0
public static void main(final String[] args) throws Exception {
    final UAS uas = new UAS();
    final EventLoopGroup udpGroup = new NioEventLoopGroup();

    final Bootstrap b = new Bootstrap();
    b.group(udpGroup)
    .channel(NioDatagramChannel.class)
    .handler(new ChannelInitializer<DatagramChannel>() {
        @Override
        protected void initChannel(final DatagramChannel ch) throws Exception {
            final ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast("decoder", new SipMessageDatagramDecoder());
            pipeline.addLast("encoder", new SipMessageEncoder());
            pipeline.addLast("handler", uas);
        }
    });

    final InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 5060);
    b.bind(socketAddress).sync().channel().closeFuture().await();
}
UDPMulticastBeacon.java 文件源码 项目:dnd 阅读 32 收藏 0 点赞 0 评论 0
public void sendBeacon() {
    final BeaconMessage msg = beacon.get();
    channelLock.readLock().lock();
    try {
        for (final DatagramChannel channel : channels.values()) {
            if (channel.isActive()) {
                LOGGER.trace("trying to send on {}", channel);
                channel.writeAndFlush(msg).addListener(
                        new GenericFutureListener<io.netty.util.concurrent.Future<Void>>() {
                            @Override
                            public void operationComplete(io.netty.util.concurrent.Future<Void> future)
                                    throws Exception {
                                LOGGER.trace(future);
                            }
                        });
            }
        }
    } finally {
        channelLock.readLock().unlock();
    }
}
UDPMulticastChannelFactory.java 文件源码 项目:dnd 阅读 31 收藏 0 点赞 0 评论 0
private void doJoin(final ChannelFuture bindFuture, final DatagramChannel channel, final NetworkInterface interf,
        final InetSocketAddress address, final ChannelPromise promise) {
    channel.eventLoop().execute(new Runnable() {
        @Override
        public void run() {
            LOGGER.entry(bindFuture, channel, interf, address, promise);
            if (bindFuture.isSuccess()) {
                LOGGER.debug("joining group {} with {} using {} and promise {}", address, interf, channel, promise);
                channel.joinGroup(address, interf, promise);
            } else {
                promise.setFailure(bindFuture.cause());
            }
            LOGGER.exit();
        }
    });
}
TestMessage.java 文件源码 项目:tomp2p_5 阅读 45 收藏 0 点赞 0 评论 0
/**
 * Mock Nettys ChannelHandlerContext with the minimal functions.
 * 
 * @param buf
 *            The buffer to use for decoding
 * @param m2
 *            The message reference to store the result
 * @return The mocked ChannelHandlerContext
 */
@SuppressWarnings("unchecked")
private ChannelHandlerContext mockChannelHandlerContext(final ByteBuf buf,
        final AtomicReference<Message2> m2) {
    ChannelHandlerContext ctx = mock(ChannelHandlerContext.class);
    ByteBufAllocator alloc = mock(ByteBufAllocator.class);
    when(ctx.alloc()).thenReturn(alloc);
    when(alloc.ioBuffer()).thenReturn(buf);
    DatagramChannel dc = mock(DatagramChannel.class);
    when(ctx.channel()).thenReturn(dc);
    when(ctx.writeAndFlush(any(), any(ChannelPromise.class))).thenReturn(null);

    Attribute<InetSocketAddress> attr = mock(Attribute.class);
    when(ctx.attr(any(AttributeKey.class))).thenReturn(attr);

    when(ctx.fireChannelRead(any())).then(new Answer<Void>() {
        @Override
        public Void answer(final InvocationOnMock invocation) throws Throwable {
            Object[] args = invocation.getArguments();
            m2.set((Message2) args[0]);
            return null;
        }
    });

    return ctx;
}
NettyUDPConnector.java 文件源码 项目:mpush 阅读 31 收藏 0 点赞 0 评论 0
private void createServer(Listener listener, EventLoopGroup eventLoopGroup, ChannelFactory<? extends DatagramChannel> channelFactory) {
    this.eventLoopGroup = eventLoopGroup;
    try {
        Bootstrap b = new Bootstrap();
        b.group(eventLoopGroup)//默认是根据机器情况创建Channel,如果机器支持ipv6,则无法使用ipv4的地址加入组播
                .channelFactory(channelFactory)
                .option(ChannelOption.SO_BROADCAST, true)
                .handler(getChannelHandler());

        initOptions(b);

        //直接绑定端口,不要指定host,不然收不到组播消息
        b.bind(port).addListener(future -> {
            if (future.isSuccess()) {
                logger.info("udp server start success on:{}", port);
                if (listener != null) listener.onSuccess(port);
            } else {
                logger.error("udp server start failure on:{}", port, future.cause());
                if (listener != null) listener.onFailure(future.cause());
            }
        });
    } catch (Exception e) {
        logger.error("udp server start exception", e);
        if (listener != null) listener.onFailure(e);
        throw new ServiceException("udp server start exception, port=" + port, e);
    }
}
UDPChannelHandler.java 文件源码 项目:mpush 阅读 33 收藏 0 点赞 0 评论 0
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
    connection.init(ctx.channel(), false);
    if (multicastAddress != null) {
        ((DatagramChannel) ctx.channel()).joinGroup(multicastAddress, networkInterface, null).addListener(future -> {
            if (future.isSuccess()) {
                LOGGER.info("join multicast group success, channel={}, group={}", ctx.channel(), multicastAddress);
            } else {
                LOGGER.error("join multicast group error, channel={}, group={}", ctx.channel(), multicastAddress, future.cause());
            }
        });
    }
    LOGGER.info("init udp channel={}", ctx.channel());
}
UDPChannelHandler.java 文件源码 项目:mpush 阅读 30 收藏 0 点赞 0 评论 0
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    connection.close();
    if (multicastAddress != null) {
        ((DatagramChannel) ctx.channel()).leaveGroup(multicastAddress, networkInterface, null).addListener(future -> {
            if (future.isSuccess()) {
                LOGGER.info("leave multicast group success, channel={}, group={}", ctx.channel(), multicastAddress);
            } else {
                LOGGER.error("leave multicast group error, channel={}, group={}", ctx.channel(), multicastAddress, future.cause());
            }
        });
    }
    LOGGER.info("disconnect udp channel={}, connection={}", ctx.channel(), connection);
}
PipelineUtil.java 文件源码 项目:PocketServer 阅读 31 收藏 0 点赞 0 评论 0
@Override
protected void initChannel(DatagramChannel ch) throws Exception {
    ch.pipeline().addLast("wrapper_encoder", new PocketWrapperEncoder());
    ch.pipeline().addLast("packet_decoder", new PocketDecoder());
    ch.pipeline().addLast("packet_encoder", new PocketEncoder());
    ch.pipeline().addLast("packet_handler", new PocketHandler());
}
Server.java 文件源码 项目:timely 阅读 42 收藏 0 点赞 0 评论 0
protected ChannelHandler setupUdpChannel() {
    return new ChannelInitializer<DatagramChannel>() {

        @Override
        protected void initChannel(DatagramChannel ch) throws Exception {
            ch.pipeline().addLast("logger", new LoggingHandler());
            ch.pipeline().addLast("packetDecoder", new UdpPacketToByteBuf());
            ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
            ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));
            ch.pipeline().addLast("putDecoder", new UdpDecoder());
            ch.pipeline().addLast(udpWorkerGroup, "putHandler", new TcpPutHandler(dataStore));
        }
    };
}
TestServer.java 文件源码 项目:timely 阅读 50 收藏 0 点赞 0 评论 0
@Override
protected ChannelHandler setupUdpChannel() {
    return new ChannelInitializer<DatagramChannel>() {

        @Override
        protected void initChannel(DatagramChannel ch) throws Exception {
            ch.pipeline().addLast("logger", new LoggingHandler());
            ch.pipeline().addLast("packetDecoder", new UdpPacketToByteBuf());
            ch.pipeline().addLast("buffer", new MetricsBufferDecoder());
            ch.pipeline().addLast("frame", new DelimiterBasedFrameDecoder(8192, true, Delimiters.lineDelimiter()));
            ch.pipeline().addLast("putDecoder", new UdpDecoder());
            ch.pipeline().addLast("capture", udpRequests);
        }
    };
}
EventLoopUtil.java 文件源码 项目:incubator-pulsar 阅读 29 收藏 0 点赞 0 评论 0
public static Class<? extends DatagramChannel> getDatagramChannelClass(EventLoopGroup eventLoopGroup) {
    if (eventLoopGroup instanceof EpollEventLoopGroup) {
        return EpollDatagramChannel.class;
    } else {
        return NioDatagramChannel.class;
    }
}
UdpServer.java 文件源码 项目:reactor-netty 阅读 30 收藏 0 点赞 0 评论 0
/**
 * Create a {@link ContextHandler} for {@link Bootstrap#handler()}
 *
 * @param handler user provided in/out handler
 * @param sink user provided bind handler
 *
 * @return a new {@link ContextHandler}
 */
protected ContextHandler<DatagramChannel> doHandler(BiFunction<? super UdpInbound, ? super UdpOutbound, ? extends Publisher<Void>> handler,
        MonoSink<NettyContext> sink,
        SocketAddress providedAddress) {
    return ContextHandler.newClientContext(sink,
            options,
            loggingHandler,
            false,
            providedAddress,
            (ch, c, msg) -> UdpOperations.bind(ch, handler, c));
}
UdpClient.java 文件源码 项目:reactor-netty 阅读 27 收藏 0 点赞 0 评论 0
/**
 * Create a {@link ContextHandler} for {@link Bootstrap#handler()}
 *
 * @param handler user provided in/out handler
 * @param sink user provided bind handler
 *
 * @return a new {@link ContextHandler}
 */
protected ContextHandler<DatagramChannel> doHandler(BiFunction<? super UdpInbound, ? super UdpOutbound, ? extends Publisher<Void>> handler,
        MonoSink<NettyContext> sink,
        SocketAddress providedAddress) {
    return ContextHandler.newClientContext(sink,
            options,
            loggingHandler,
            false,
            providedAddress,
            (ch, c, msg) -> UdpOperations.bind(ch, handler, c));
}
ChannelOperations.java 文件源码 项目:reactor-netty 阅读 37 收藏 0 点赞 0 评论 0
@Override
public InetSocketAddress address() {
    Channel c = channel();
    if (c instanceof SocketChannel) {
        return ((SocketChannel) c).remoteAddress();
    }
    if (c instanceof DatagramChannel) {
        InetSocketAddress a = ((DatagramChannel) c).remoteAddress();
        return a != null ? a : ((DatagramChannel)c ).localAddress();
    }
    throw new IllegalStateException("Does not have an InetSocketAddress");
}
NettyContext.java 文件源码 项目:reactor-netty 阅读 30 收藏 0 点赞 0 评论 0
/**
 * Return remote address if remote channel {@link NettyContext} otherwise local
 * address if server selector channel.
 *
 * @return remote or local {@link InetSocketAddress}
 */
default InetSocketAddress address(){
    Channel c = channel();
    if (c instanceof SocketChannel) {
        return ((SocketChannel) c).remoteAddress();
    }
    if (c instanceof ServerSocketChannel) {
        return ((ServerSocketChannel) c).localAddress();
    }
    if (c instanceof DatagramChannel) {
        InetSocketAddress a = ((DatagramChannel) c).remoteAddress();
        return a != null ? a : ((DatagramChannel)c ).localAddress();
    }
    throw new IllegalStateException("Does not have an InetSocketAddress");
}
McpeOverRakNetNetworkListener.java 文件源码 项目:voxelwind 阅读 26 收藏 0 点赞 0 评论 0
@Override
protected void initChannel(DatagramChannel channel) throws Exception {
    this.channel = channel;
    channel.pipeline()
            .addLast("simpleRaknetHandler", new SimpleRakNetPacketCodec())
            .addLast("raknetDirectPacketHandler", new RakNetDirectPacketHandler(server))
            .addLast("raknetDatagramHandler", new DatagramRakNetPacketCodec(server))
            .addLast("voxelwindDatagramHandler", new RakNetDatagramHandler(server))
            .addLast("tailHandler", new TailHandler());
}
UdpLogServer.java 文件源码 项目:Okra-LOG 阅读 23 收藏 0 点赞 0 评论 0
@Override
protected ChannelHandler newChannelInitializer() {
    return new ChannelInitializer<DatagramChannel>() {
        @Override
        protected void initChannel(DatagramChannel ch) throws Exception {
            ChannelPipeline cp = ch.pipeline();
            cp.addLast("ipMatcher", new UdpProtocolHandler(ipMatcher));
            cp.addLast("handler", new LogRecordHandler(board));
        }
    };
}
OioDatagramChannel.java 文件源码 项目:netty4.0.27Learn 阅读 31 收藏 0 点赞 0 评论 0
private void ensureBound() {
    if (!isActive()) {
        throw new IllegalStateException(
                DatagramChannel.class.getName() +
                " must be bound to join a group.");
    }
}
NioDatagramChannelTest.java 文件源码 项目:netty4.0.27Learn 阅读 29 收藏 0 点赞 0 评论 0
/**
 * Test try to reproduce issue #1335
 */
@Test
public void testBindMultiple() throws Exception {
    DefaultChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    NioEventLoopGroup group = new NioEventLoopGroup();
    try {
        for (int i = 0; i < 100; i++) {
            Bootstrap udpBootstrap = new Bootstrap();
            udpBootstrap.group(group).channel(NioDatagramChannel.class)
                    .option(ChannelOption.SO_BROADCAST, true)
                    .handler(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) {
                            // Discard
                            ReferenceCountUtil.release(msg);
                        }
                    });
            DatagramChannel datagramChannel = (DatagramChannel) udpBootstrap
                    .bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
            channelGroup.add(datagramChannel);
        }
        Assert.assertEquals(100, channelGroup.size());
    } finally {
        channelGroup.close().sync();
        group.shutdownGracefully().sync();
    }
}
PipelineUtils.java 文件源码 项目:PocketServer-Ref 阅读 79 收藏 0 点赞 0 评论 0
@Override
protected void initChannel(DatagramChannel ch) throws Exception {
    ChannelPipeline pipeline = ch.pipeline();
    pipeline.addLast("encoder", new PacketEncoder());
    pipeline.addLast("decoder", new PacketDecoder());
    pipeline.addLast("handler", new PocketServerHandler());
}
OioDatagramChannel.java 文件源码 项目:netty4study 阅读 28 收藏 0 点赞 0 评论 0
private void ensureBound() {
    if (!isActive()) {
        throw new IllegalStateException(
                DatagramChannel.class.getName() +
                " must be bound to join a group.");
    }
}
NioDatagramChannelTest.java 文件源码 项目:netty4study 阅读 30 收藏 0 点赞 0 评论 0
/**
 * Test try to reproduce issue #1335
 */
@Test
public void testBindMultiple() throws Exception {
    DefaultChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    NioEventLoopGroup group = new NioEventLoopGroup();
    try {
        for (int i = 0; i < 100; i++) {
            Bootstrap udpBootstrap = new Bootstrap();
            udpBootstrap.group(group).channel(NioDatagramChannel.class)
                    .option(ChannelOption.SO_BROADCAST, true)
                    .handler(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) {
                            // Discard
                            ReferenceCountUtil.release(msg);
                        }
                    });
            DatagramChannel datagramChannel = (DatagramChannel) udpBootstrap
                    .bind(new InetSocketAddress(0)).syncUninterruptibly().channel();
            channelGroup.add(datagramChannel);
        }
        Assert.assertEquals(100, channelGroup.size());
    } finally {
        channelGroup.close().sync();
        group.shutdownGracefully().sync();
    }
}
MeMemberUDP.java 文件源码 项目:bigio 阅读 27 收藏 0 点赞 0 评论 0
public DataServerThread() {
    dataBossGroup = new NioEventLoopGroup(DATA_BOSS_THREADS);
    dataWorkerGroup = new NioEventLoopGroup(DATA_WORKER_THREADS);
    try {
        Bootstrap b = new Bootstrap();
        b.group(dataWorkerGroup)
                .channelFactory(new ChannelFactory<Channel>() {
                    @Override
                    public Channel newChannel() {
                        return new NioDatagramChannel(InternetProtocolFamily.IPv4);
                    }

                    @Override
                    public String toString() {
                        return NioDatagramChannel.class.getSimpleName() + ".class";
                    }
                }).handler(new ChannelInitializer<DatagramChannel>() {
                    @Override
                    public void initChannel(DatagramChannel ch) throws Exception {
                        ch.config().setAllocator(UnpooledByteBufAllocator.DEFAULT);
                        ch.pipeline().addLast(new DataMessageHandler());
                        if (LOG.isTraceEnabled()) {
                            ch.pipeline().addLast(new LoggingHandler(LogLevel.TRACE));
                        }
                    }

                    @Override
                    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                        LOG.error("Cannot initialize data server.", cause);
                    }
                });

        // Bind and start to accept incoming connections.
        f = b.bind(getIp(), getDataPort()).sync();
    } catch (InterruptedException ex) {
        LOG.error("Message data interrupted.", ex);
    }
}
ServerUDPChannelFactory.java 文件源码 项目:distributeTemplate 阅读 32 收藏 0 点赞 0 评论 0
protected static Channel createAcceptorChannel(
          final ChannelType channelType,
          final InetSocketAddress localAddress,
          final ServerUDPHandler serverHandler
  ) {
      final Bootstrap serverBootstrap = ServerUDPBootstrapFactory.createServerBootstrap(channelType);

      serverBootstrap
              .option(ChannelOption.SO_REUSEADDR, false)


            .handler(new ChannelInitializer<DatagramChannel>() {
                  @Override
                  protected void initChannel(final DatagramChannel ch) throws Exception {
                      final ChannelPipeline pipeline = ch.pipeline();
                     //pipeline.addLast("readTimeoutHandler", new ReadTimeoutHandler(60));
                      pipeline.addLast("messageDecoder", serverHandler);
                      //pipeline.addLast("handler", serverHandler);
                  }
              });

      try {

          ChannelFuture channelFuture = serverBootstrap.bind(
                  new InetSocketAddress(localAddress.getPort())).sync();

          //channelFuture.channel().closeFuture().awaitUninterruptibly();//.awaitUninterruptibly();
          channelFuture.awaitUninterruptibly();
          if (channelFuture.isSuccess()) {
              return channelFuture.channel();

          } else {

          }
      } catch (InterruptedException e) {

      }
return null;

  }


问题


面经


文章

微信
公众号

扫码关注公众号