Java Socket几个简单的入门示例

这篇具有很好参考价值的文章主要介绍了Java Socket几个简单的入门示例。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

Socket难的地方是服务端的编写,首先要合理地管理客户端连接,能让客户端持续不断地连接进来。其次每个连接的读写不能互相干扰,不能因为一个连接在传输数据,别的连接就得挂着。搞定了这两点,基本上就解决了Socket编程80%的问题。

以下是根据个人经验,写了几个示例,希望对看官有所帮助。

开发环境搭建,请点这里!!

纯Socket的实现

java的Socket有两个版本,一个服务端的(ServerSocket),一个客户端的(Socket )。服务端版本可以启用监听,接受客户端连接;客户端版本只能发起连接,概念上应该不难理解。

下边是官方服务端及客户端,分别启动后,客户端里打上几个字,两边在控制台里都会产生相应的输出。为了让程序代码能看得明白一点,我加了一些输出代码,有兴趣的可以运行起来看一下。

注:为节省篇幅,本文从头至尾只写了一个客户端代码。所以后边示例代码只例出服务端的。

初始代码

以下代码可能是大多数学习Socket编程的入门代码,也就是创建一个服务端和客户端,然后启动服务端,启动客户端,如果两端不报错,就意味着这个代码运行成功了。由于入门代码只演示了如何监听,建立连接以及简单的读写,所以这种代码在实现生产中没有一点用处。

有点编程基础的人,我想下边的代码还是比较容易看懂的,如果看不懂,就调试模式运行起,单步走一走,就知道每行代码是怎么触发的了。

服务端

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class AppServer {
	public static void main(String[] args) throws IOException {
		System.out.println("服务启动");
		try (ServerSocket serverSocket = new ServerSocket(8888);

				Socket clientSocket = serverSocket.accept();
				PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
				BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));) {

			System.out.println("客户端连接");
			
			String inputLine;
			while ((inputLine = in.readLine()) != null) {
				System.out.println("消息:" + inputLine);
				out.println(inputLine);
			}
			
		} catch (IOException e) {
			System.out.println(
					"Exception caught when trying to listen on port " + 8888 + " or listening for a connection");
			System.out.println(e.getMessage());
		}
		System.out.println("服务退出");

	}
}

客户端

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class AppClient {
	public static void main(String[] args) throws UnknownHostException, IOException {
		String hostName = "localhost";
		int portNumber = 8888;

		try (Socket echoSocket = new Socket(hostName, portNumber);
				PrintWriter out = new PrintWriter(echoSocket.getOutputStream(), true);
				BufferedReader in = new BufferedReader(new InputStreamReader(echoSocket.getInputStream()));
				BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {
			
			System.out.println("已连接,请输入内容");
			String userInput;
			while (!(userInput = stdIn.readLine()).equals("exit")) {
				out.println(userInput);
				System.out.println("回显: " + in.readLine());
			}
		} catch (UnknownHostException e) {
			System.err.println("Don't know about host " + hostName);
			System.exit(1);
		} catch (IOException e) {
			System.err.println("Couldn't get I/O for the connection to " + hostName);
			System.exit(1);
		}

		System.out.println("客户端退出");
	}
}

遗憾的是,这个服务端是一次性的,随着客户端退出,服务器端就挂了。其原因是只调用accept()了一次,为了解决这个问题,我们要让服务端不停地accept()。可以在accept()之上,套一层无限循环,这样就可以不停地接受客户端了。

第一次改进

这里改进其实是为了更深入理解服务端是如何接受客户端连接的。服务端起动监听后,还要显式地调用accept()方法,表明这个服务开始接受客户端的连接。不过,每次accept()只能接收一个客户端socket,而且在没有新的客户端连接之前,这个方法就会一直挂起在那里。为了能够不停地接受客户端,那么就要尽快地将连接过来的socket给接收下来,然后进入下一个等待。

因为调用会挂起(专业术语叫阻塞),所以不用考虑递归调用,使用一个无限循环就可以了。

注:
一个完整的应用程序,应该考虑程序的初始化,关闭,异常等逻辑。这里仅演示方便,并未考虑这些细节。所以,尽量避免将这样的代码直接应用到你的生产项目中去。

服务端改进1

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class AppServer {
	public static void main(String[] args) throws IOException {
		System.out.println("服务启动");
		try (ServerSocket serverSocket = new ServerSocket(8888);
		) {
			//增加一个无限循环
			while (true) {
				Socket clientSocket = serverSocket.accept();
				PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
				BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
				System.out.println("客户端连接");

				String inputLine;
				while ((inputLine = in.readLine()) != null) {
					System.out.println("消息:" + inputLine);
					out.println(inputLine);
				}
				in.close();
				out.close();
				clientSocket.close();
			}
		} catch (IOException e) {
			System.out.println(
					"Exception caught when trying to listen on port " + 8888 + " or listening for a connection");
			System.out.println(e.getMessage());
		}
		System.out.println("服务退出");

	}
}

但这个服务端还是有点问题,每次只能连接一个客户端,只有等到前一个客户端退出了,后边的才能正常连接。为了能够迸发连接,就会用到另一个技术——线程。我们可以将读写部分放到一个单独线程中,这样主线程就可以立即接受下一个连接了。

二次改进

在读取客户端连接的时候也是阻塞的,因而前边的代码就会出现一个问题。在你没放弃读取当前连接的数据时,其它客户端依然是连不进来的。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class AppServer {
	public static void main(String[] args) throws IOException {
		System.out.println("服务启动");
		try (ServerSocket serverSocket = new ServerSocket(8888);) {
			// 增加一个无限循环
			while (true) {
				Socket clientSocket = serverSocket.accept();
				Thread clientThread = new Thread(new Runnable() {

					@Override
					public void run() {
						PrintWriter out;
						try {
							out = new PrintWriter(clientSocket.getOutputStream(), true);

							BufferedReader in = new BufferedReader(
									new InputStreamReader(clientSocket.getInputStream()));
							System.out.println("客户端连接");

							String inputLine;
							while ((inputLine = in.readLine()) != null) {
								System.out.println("消息:" + inputLine);
								out.println(inputLine);
							}
							in.close();
							out.close();
							clientSocket.close();
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}

					}
				});
				clientThread.start();
			}
		} catch (IOException e) {
			System.out.println(
					"Exception caught when trying to listen on port " + 8888 + " or listening for a connection");
			System.out.println(e.getMessage());
		}
		System.out.println("服务退出");

	}
}

不过,据说不停地new Thread()比较奢侈,具体原因跟操作系统线程原理有关,具体这里不展开讲,总之线程多了即降低性能,又浪费内存。这时考虑使用线程池的方式来实现,线程池可以用JDK的现成类库。

三次改进

这次改进的重点是性能上的优化。因为接受客户端,以及读取客户端数据都是阻塞的,为了使每个客户端能并行执行,不可避免地要用到线程。但线程的使用要有个度,适当地使用线程能提升性能,将单线程的阻塞,变成并行的阻塞,确实是提升了CPU的复用率。但这里有个风险,就是线程数量超过某个阀值的时候,反而会降低性能。具体原理,网上有很多大神已经讲解得非常细致了,这里不赘述。有个原则就是在不阻塞的情况下,尽量少地去使用线程。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executors;

public class AppServer {
	public static void main(String[] args) throws IOException {
		System.out.println("Service starting...");
		try (ServerSocket serverSocket = new ServerSocket(8888);) {
			// 创建线程池,数量跟CPU的线程数相当
			var pool = Executors.newFixedThreadPool(8);
			// 增加一个无限循环
			while (true) {
				Socket clientSocket = serverSocket.accept();
				pool.submit(new Runnable() {

					@Override
					public void run() {
						PrintWriter out;
						try {
							out = new PrintWriter(clientSocket.getOutputStream(), true);

							BufferedReader in = new BufferedReader(
									new InputStreamReader(clientSocket.getInputStream()));
							System.out.println("Client connected...");

							String inputLine;
							while ((inputLine = in.readLine()) != null) {
								System.out.println("Message:" + inputLine);
								out.println(inputLine);
							}
							in.close();
							out.close();
							clientSocket.close();
							System.out.println("Client disconnected...");
						} catch (IOException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				});
			}
		} catch (IOException e) {
			System.out.println(
					"Exception caught when trying to listen on port " + 8888 + " or listening for a connection");
			System.out.println(e.getMessage());
		}
		System.out.println("服务退出");

	}
}

至此服务端已经改进不少,但有新的问题出现,如果长连接的客户端足够多,那么线程必然又要增加。我们的目标是提升服务器的性能,而不是无谓的增加线程数量。可以考虑使用非阻塞的方式来接收客户端的数据。

再次改进

代码改进如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;

public class AppServer {
	public static void main(String[] args) throws IOException {
		System.out.println("Service starting...");
		try (ServerSocket serverSocket = new ServerSocket(8888);) {
			// 创建线程池
			var pool = Executors.newFixedThreadPool(8);

			List<Socket> clients = Collections.synchronizedList(new ArrayList<Socket>(0));
			pool.submit(new Runnable() {

				@Override
				public void run() {
					while (true) {
						var iterator = clients.iterator();

						while (iterator.hasNext()) {
							var socket = iterator.next();
							if (socket.isClosed()) {
								System.out.println("Client disconnected...");
								iterator.remove();
								continue;
							}
							try {
								//如果此处有更为复杂的逻辑要处理,则可以创建一个Runnalbe对象,塞进线程池中
								PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
								if (socket.getInputStream().available() > 0) {
									System.out.println("Message received...");

									BufferedReader in = new BufferedReader(
											new InputStreamReader(socket.getInputStream()));

									String inputLine = in.readLine();
									System.out.println("Message:" + inputLine);
									out.println(inputLine);
									if (inputLine.equals("exit"))
										socket.close();
								} else {
									socket.getOutputStream().write(0);
								}

							} catch (IOException e) {
								e.printStackTrace();
								iterator.remove();
							}
						}
						try {
							Thread.sleep(50);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			});
			// 增加一个无限循环
			while (true) {
				Socket clientSocket = serverSocket.accept();
				System.out.println("Client connected...");
				clients.add(clientSocket);
			}
		} catch (IOException e) {
			System.out.println(
					"Exception caught when trying to listen on port " + 8888 + " or listening for a connection");
			System.out.println(e.getMessage());
		}
		System.out.println("服务退出");

	}
}

至此,纯Socket的写法已经有相当强的处理能力了。当然,这还是示例,并不能解决实际的业务,不过,方向是对的,实际项目中往这个思路上靠就是了。

非阻塞NIO的实现

NIO的实现大同小异,只是Accecpt()换成了select(),至于读消息是不是也可以使用select()就不得而知了,笔者才疏学浅没找到相关的API。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;

public class AppServer {
	public static void main(String[] args) throws IOException {
		System.out.println("Service starting...");
		try {
			// 创建线程池
			var pool = Executors.newFixedThreadPool(8);

			List<Socket> clients = Collections.synchronizedList(new ArrayList<Socket>(0));
			pool.submit(new Runnable() {

				@Override
				public void run() {
					while (true) {
						var iterator = clients.iterator();

						while (iterator.hasNext()) {
							var socket = iterator.next();
							if (socket.isClosed()) {
								System.out.println("Client disconnected...");
								iterator.remove();
								continue;
							}
							try {
								// 如果此处有更为复杂的逻辑要处理,则可以创建一个Runnalbe对象,塞进线程池中
								PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
								if (socket.getInputStream().available() > 0) {
									System.out.println("Message received...");

									BufferedReader in = new BufferedReader(
											new InputStreamReader(socket.getInputStream()));

									String inputLine = in.readLine();
									System.out.println("Message:" + inputLine);
									out.println(inputLine);
									if (inputLine.equals("exit"))
										socket.close();
								} else {
									socket.getOutputStream().write(0);
								}

							} catch (IOException e) {
								e.printStackTrace();
								iterator.remove();
							}
						}
						try {
							Thread.sleep(50);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			});

			// Selector for incoming time requests
			Selector acceptSelector = SelectorProvider.provider().openSelector();

			// Create a new server socket and set to non blocking mode
			ServerSocketChannel ssc = ServerSocketChannel.open();
			ssc.configureBlocking(false);

			// Bind the server socket to the local host and port

			InetSocketAddress isa = new InetSocketAddress(8888);
			ssc.socket().bind(isa);

			// Register accepts on the server socket with the selector. This
			// step tells the selector that the socket wants to be put on the
			// ready list when accept operations occur, so allowing multiplexed
			// non-blocking I/O to take place.
			SelectionKey acceptKey = ssc.register(acceptSelector, SelectionKey.OP_ACCEPT);

			int keysAdded = 0;

			// Here's where everything happens. The select method will
			// return when any operations registered above have occurred, the
			// thread has been interrupted, etc.
			while ((keysAdded = acceptSelector.select()) > 0) {
				// Someone is ready for I/O, get the ready keys
				Set<SelectionKey> readyKeys = acceptSelector.selectedKeys();
				Iterator<SelectionKey> i = readyKeys.iterator();

				// Walk through the ready keys collection and process date requests.
				while (i.hasNext()) {
					SelectionKey sk = (SelectionKey) i.next();
					i.remove();
					// The key indexes into the selector so you
					// can retrieve the socket that's ready for I/O
					ServerSocketChannel nextReady = (ServerSocketChannel) sk.channel();
					// Accept the date request and send back the date string
					Socket s = nextReady.accept().socket();
					System.out.println("Client connected...");
					clients.add(s);
					// Write the current time to the socket

				}
			}

		} catch (IOException e) {
			System.out.println(
					"Exception caught when trying to listen on port " + 8888 + " or listening for a connection");
			System.out.println(e.getMessage());
		}
		System.out.println("Service exited");

	}
}

Netty实现

使用Netty后,代码结构清爽了不少。不用自己写线程,数据读写方面只要会用ByteBuf就可以了。

import java.io.IOException;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class AppServer extends ChannelInboundHandlerAdapter {

	@Override
	public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
		System.out.println("Client disconnected...");
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		ByteBuf in = ((ByteBuf) msg).copy();
		ctx.write(msg); // (1)
		ctx.flush(); // (2)

		String str = "";
		while (in.isReadable()) { // (1)
			str += (char) in.readByte();
		}
		System.out.print("message:"+str);

	}

	public static void main(String[] args) throws IOException, InterruptedException {
		System.out.println("Service starting...");
		EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap b = new ServerBootstrap(); // (2)
			b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3)
					.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
						@Override
						public void initChannel(SocketChannel ch) throws Exception {
							System.out.println("Client connected...");
							ch.pipeline().addLast(new AppServer());
						}

					}).option(ChannelOption.SO_BACKLOG, 128) // (5)
					.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

			// Bind and start to accept incoming connections.
			ChannelFuture f = b.bind(8888).sync(); // (7)

			// Wait until the server socket is closed.
			// In this example, this does not happen, but you can do that to gracefully
			// shut down your server.
			f.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
		System.out.println("Service exited");

	}
}

Vert.x 实现

Vert.x是建立在Netty之上的,但对程序员更加友好。相同的功能,明显代码少了不少。文章来源地址https://www.toymoban.com/news/detail-464105.html

import java.io.IOException;

import io.vertx.core.Vertx;
import io.vertx.core.net.NetServer;

public class AppServer {

	public static void main(String[] args) throws IOException, InterruptedException {
		System.out.println("Service starting...");
		Vertx vertx = Vertx.vertx();
		NetServer server = vertx.createNetServer();
		server.connectHandler(socket -> {
			System.out.println("Client connected!");
			socket.handler(buffer -> {
				System.out.println("Message received!");
				var c = buffer.getString(0, buffer.length());
				socket.write(c);
				System.out.print("Message:"+c);
			});

		});

		server.listen(8888, res -> {
			if (res.succeeded()) {
				System.out.println("Server is now listening!");
			} else {
				System.out.println("Failed to bind!");
			}
		});
		System.out.println("Service exited");

	}
}


到了这里,关于Java Socket几个简单的入门示例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Kafka-入门及简单示例

    结果 一个生产者 一个消费者 生产者在某些事件时触发消息产生 消费者根据topic监听事件 如果有生产者产生消息 自动进行消费

    2024年02月05日
    浏览(39)
  • Spring for Apache Kafka概述和简单入门

    Spring for Apache Kafka 的高级概述以及底层概念和可运行的示例代码。 注意:进行工作开始之前至少要有一个 Apache Kafka 环境 使用 Spring Boot 使用 Spring Boot 时,省略版本,Boot 将自动引入与您的 Boot 版本兼容的正确版本 使用 Spring 使用Spring 时必须要申明使用的版本。 Apache Kafka 客户

    2024年02月09日
    浏览(39)
  • Java入门13(socket)

    服务器端Demo(ServreSocket) ​创建服务端时,如果不提供IP地址,则默认为本地连接(127.0.0.1),但是一定需要手动配置监听端口! 客户端Demo(Socket) ​创建客户端时,客户对象的端口由操作系统自动分配,参数传递服务端的IP地址以及服务器监听的端口

    2024年02月16日
    浏览(30)
  • java操作rabbitmq实现简单的消息发送(socket编程的升级)

    准备: 1.下载rabbitmq并搭建环境(和python那篇一样:http://www.cnblogs.com/g177w/p/8176797.html) 2.下载支持的jar包(http://repo1.maven.org/maven2/com/rabbitmq/amqp-client) 生产者方(Productor.java): View Code 消费者方(Consummer.java):

    2023年04月08日
    浏览(43)
  • Java面试被问了几个简单的问题,却回答的不是很好

    作者: 逍遥Sean 简介:一个主修Java的Web网站游戏服务器后端开发者 主页:https://blog.csdn.net/Ureliable 觉得博主文章不错的话,可以三连支持一下~ 如有需要我的支持,请私信或评论留言! 前言 前几天参加了一个做web开发的面试,被问了几个问题,虽然有些题目比较偏,但是确

    2024年02月08日
    浏览(63)
  • 「Java」Socket实现简单的客户端和服务端通讯 | 公网环境通讯

    💛前情提要💛 本章节是 番外篇 的 Socket 的相关知识~ 接下来我们即将进入一个全新的空间,对代码有一个全新的视角~ 以下的内容一定会让你对 Socket 有一个颠覆性的认识哦!!! 以下内容干货满满,跟上步伐吧~ 作者介绍: 🎓 作者: 热爱编程不起眼的小人物🐐 🔎 作者

    2023年04月16日
    浏览(47)
  • Python爬虫入门:HTTP与URL基础解析及简单示例实践

    在数字化时代,数据已成为一种宝贵的资源。Python作为一种强大的编程语言,在数据采集和处理方面表现出色。爬虫技术,即网络爬虫,是Python中用于数据采集的重要工具。本文作为Python爬虫基础教程的第一篇,将深入讲解URL和HTTP的基础知识,为后续的爬虫实践打下坚实的基

    2024年03月22日
    浏览(44)
  • Java对接kafka简单示例

    Java可以使用Apache Kafka提供的kafka-clients库来对接Kafka。下面是一个简单的示例代码,展示了如何使用Java对接Kafka并发送和接收消息: 首先,确保已经在项目中添加了kafka-clients库的依赖。 以上代码演示了如何使用Kafka的生产者将消息发送到指定的topic,以及如何使用消费者从指

    2024年04月28日
    浏览(37)
  • 【Java入门】Java的语言概述

    前言 📕作者简介: 热爱跑步的恒川 ,致力于C/C++、Java、Python等多编程语言,热爱跑步,喜爱音乐的一位博主。 📗本文收录于Java入门篇系列,该专栏主要讲解:什么是java、java的数据类型与变量、运算符、程序的逻辑控制、方法的使用、数组的定义与使、类和对象、继承和

    2024年02月05日
    浏览(37)
  • 聊聊分布式架构06——[NIO入门]简单的Netty NIO示例

    目录 Java NIO和Netty NIO比较 Java NIO: Netty: Netty NIO中的主要模块 Transport(传输层) Buffer(缓冲区) Codec(编解码器) Handler(处理器) EventLoop(事件循环) Bootstrap和Channel(引导和通道) Future和Promise(异步编程) Netty示例 服务端时序图 服务端代码 客户端时序图 客户端代码

    2024年02月07日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包