网络编程实质实质就是两个(或多个)设备(例如计算机)之间的数据传输。而要实现两台计算机通过互联网连接进行数据传输,必输要满足计算机网络的5层协议(物理层,数据链路层,网络层,运输层,应用层);当然有划分可能不同,但现在大家比较容易接受理解的是五层模型。

IO在计算机中指Input/Output,也就是输入和输出。IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。对于浏览网页来说,浏览器和新浪服务器之间至少需要建立两根水管,才可以既能发数据,又能收数据。


同步阻塞IO

    网络编程的基本模型是C/S模型,即两个进程间的通信。
    服务端提供IP和监听端口,客户端通过连接操作想服务端监听的地址发起连接请求,通过三次握手连接,如果连接成功建立,双方就可以通过套接字进行通信。
    传统的同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。

采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行链路处理没处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答通宵模型。

该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死掉了。

public class BioServer {public static void main(String[] args) throws IOException {ServerSocket server = null;int port = 8080;try {server = new ServerSocket(port);System.out.println("The time server is start in port : " + port);Socket socket = null;while (true) {socket = server.accept();new Thread(new BioServerHandler(socket)).start();}} finally {if (server != null) {System.out.println("The time server close");server.close();server = null;}}}
}
public class BioServerHandler implements Runnable {private Socket socket;public BioServerHandler(Socket socket) {this.socket = socket;}@Overridepublic void run() {BufferedReader in = null;PrintWriter out = null;try {in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));out = new PrintWriter(this.socket.getOutputStream(), true);String currentTime = null, body = null;while (true) {body = in.readLine();if (body == null) break;System.out.println("The time server receive order : " + body);currentTime = "heqing".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";out.println(currentTime);}} catch (Exception e) {if (in != null) {try {in.close();} catch (IOException e1) {e1.printStackTrace();}}if (out != null) {out.close();out = null;}if (this.socket != null) {try {this.socket.close();} catch (IOException e1) {e1.printStackTrace();}this.socket = null;}}}
}
public class BioClient {public static void main(String[] args) {int port = 8080;Socket socket = null;BufferedReader in = null;PrintWriter out = null;try {socket = new Socket("127.0.0.1", port);in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(socket.getOutputStream(), true);out.println("heqing");System.out.println("Send order 2 server succeed.");String resp = in.readLine();System.out.println("Now is : " + resp);} catch (Exception e) {e.printStackTrace();} finally {if (out != null) {out.close();out = null;}if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}in = null;}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}socket = null;}}}
}

伪异步I/O编程

    可以使用线程池来管理这些线程,实现1个或多个线程处理N个客户端的模型(但是底层还是使用的同步阻塞I/O),通常被称为“伪异步I/O模型“。

使用FixedThreadPool我们就有效的控制了线程的最大数量,保证了系统有限的资源的控制,实现了N:M的伪异步I/O模型。 但是,正因为限制了线程数量,如果发生大量并发请求,超过最大数量的线程就只能等待,直到线程池中的有空闲的线程可以被复用。而对Socket的输入流就行读取时,会一直阻塞,直到发生:1.有数据可读; 2.可用数据以及读取完毕; 3.发生空指针或I/O异常;
所以在读取数据较慢时(比如数据量大、网络传输慢等),大量并发的情况下,其他接入的消息,只能一直等待,这就是最大的弊端。

public class PioServer {public static void main(String[] args) throws IOException {int port = 8080;ServerSocket server = null;try {server = new ServerSocket(port);System.out.println("The time server is start in port : " + port);Socket socket = null;PioServerHandlerExecutePool singleExecutor = new PioServerHandlerExecutePool(50, 10000);// 创建IO任务线程池while (true) {socket = server.accept();singleExecutor.execute(new PioServerHandler(socket));}} finally {if (server != null) {System.out.println("The time server close");server.close();server = null;}}}
}
public class PioServerHandler implements Runnable {private Socket socket;public PioServerHandler(Socket socket) {this.socket = socket;}@Overridepublic void run() {BufferedReader in = null;PrintWriter out = null;try {in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));out = new PrintWriter(this.socket.getOutputStream(), true);String currentTime = null, body = null;while (true) {body = in.readLine();if (body == null) break;System.out.println("The time server receive order : " + body);currentTime = "heqing".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";out.println(currentTime);}} catch (Exception e) {if (in != null) {try {in.close();} catch (IOException e1) {e1.printStackTrace();}}if (out != null) {out.close();out = null;}if (this.socket != null) {try {this.socket.close();} catch (IOException e1) {e1.printStackTrace();}this.socket = null;}}}
}
public class PioServerHandlerExecutePool {private ExecutorService executor;public PioServerHandlerExecutePool(int maxPoolSize, int queueSize) {executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize, 120L,TimeUnit.SECONDS, new ArrayBlockingQueue<java.lang.Runnable>(queueSize));}public void execute(java.lang.Runnable task) {executor.execute(task);}
}
public class PioClient {public static void main(String[] args) {int port = 8080;Socket socket = null;BufferedReader in = null;PrintWriter out = null;try {socket = new Socket("127.0.0.1", port);in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(socket.getOutputStream(), true);out.println("heqing");System.out.println("Send order 2 server succeed.");String resp = in.readLine();System.out.println("Now is : " + resp);} catch (Exception e) {e.printStackTrace();} finally {if (out != null) {out.close();out = null;}if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}in = null;}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}socket = null;}}}
}

NIO 编程

    JDK 1.4中的java.nio.*包中引入新的Java I/O库,其目的是提高速度。实际上,“旧”的I/O包已经使用NIO重新实现过,即使我们不显式的使用NIO编程,也能从中受益。
    NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现。新增的着两种通道都支持阻塞和非阻塞两种模式。
    对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式来开发。

public class NioServer {public static void main(String[] args) throws IOException {int port = 8080;MultiplexerNioServer timeServer = new MultiplexerNioServer(port);new Thread(timeServer, "NIO-heqing").start();}
}
public class MultiplexerNioServer implements Runnable {private Selector selector;private ServerSocketChannel servChannel;private volatile boolean stop;/*** 初始化多路复用器、绑定监听端口* * @param port*/public MultiplexerNioServer(int port) {try {//1.创建多路复用器selector = Selector.open();//2.用于监听客户端的链接,所有客户端连接的父管道servChannel = ServerSocketChannel.open();//3.绑定端口,设置为非阻塞式servChannel.socket().bind(new InetSocketAddress(port), 1024);servChannel.configureBlocking(false);//4.将ServerSocketChannel绑定到多路复用器,并监听ACCEPT事件servChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("The time server is start in port : " + port);} catch (IOException e) {e.printStackTrace();System.exit(1);}}public void stop() {this.stop = true;}@Overridepublic void run() {while (!stop) {try {//5.多路复用器轮询准备好的KEYselector.select(1000);Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> it = selectedKeys.iterator();SelectionKey key = null;while (it.hasNext()) {key = it.next();it.remove();try {handleInput(key);} catch (Exception e) {if (key != null) {key.cancel();if (key.channel() != null)key.channel().close();}}}} catch (Throwable t) {t.printStackTrace();}}// 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源if (selector != null)try {selector.close();} catch (IOException e) {e.printStackTrace();}}// 处理新接入的请求消息private void handleInput(SelectionKey key) throws IOException {if (key.isValid()) {if (key.isAcceptable()) {ServerSocketChannel ssc = (ServerSocketChannel) key.channel();//6.多路复用器监听到新的客户端链接,完成3次握手,建立物理路由,处理新请求SocketChannel sc = ssc.accept();//7.设置客户端路由为非阻塞式sc.configureBlocking(false);//8.将新接入的客户端注册到多路由上,监听操作,读取客户端传递来的数据sc.register(selector, SelectionKey.OP_READ);}if (key.isReadable()) {// Read the dataSocketChannel sc = (SocketChannel) key.channel();ByteBuffer readBuffer = ByteBuffer.allocate(1024);//9.异步读取客户端传递数据放入到缓冲区int readBytes = sc.read(readBuffer);if (readBytes > 0) {readBuffer.flip();byte[] bytes = new byte[readBuffer.remaining()];readBuffer.get(bytes);//10.处理消息文件String body = new String(bytes, "UTF-8");System.out.println("The time server receive order : "+ body);String currentTime = "heqing".equalsIgnoreCase(body) ? new java.util.Date(System.currentTimeMillis()).toString(): "BAD ORDER";doWrite(sc, currentTime);} else if (readBytes < 0) {// 对端链路关闭key.cancel();sc.close();} // 读到0字节,忽略}}}private void doWrite(SocketChannel channel, String response)throws IOException {if (response != null && response.trim().length() > 0) {byte[] bytes = response.getBytes();ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);writeBuffer.put(bytes);writeBuffer.flip();//11.将返回信息转换为byteBuffer,调用SocketChannel的异步write接口,将消息异步发送到客户端channel.write(writeBuffer);}}
}
public class NioClient {public static void main(String[] args) {int port = 8080;new Thread(new NioClientHandle("127.0.0.1", port), "NIO-heqing-001").start();}
}
public class NioClientHandle implements Runnable {private String host;private int port;private Selector selector;private SocketChannel socketChannel;private volatile boolean stop;public NioClientHandle(String host, int port) {this.host = host == null ? "127.0.0.1" : host;this.port = port;try {//1.创建多路复用器selector = Selector.open();//2.创建父管道,并设为异步socketChannel = SocketChannel.open();socketChannel.configureBlocking(false);} catch (IOException e) {e.printStackTrace();System.exit(1);}}public void run() {try {doConnect();} catch (IOException e) {e.printStackTrace();System.exit(1);}while (!stop) {try {//6.多路复用器轮询准备好的KEYselector.select(1000);Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> it = selectedKeys.iterator();SelectionKey key = null;while (it.hasNext()) {key = it.next();it.remove();try {handleInput(key);} catch (Exception e) {if (key != null) {key.cancel();if (key.channel() != null)key.channel().close();}}}} catch (Exception e) {e.printStackTrace();System.exit(1);}}// 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源if (selector != null)try {selector.close();} catch (IOException e) {e.printStackTrace();}}private void handleInput(SelectionKey key) throws IOException {if (key.isValid()) {// 7.判断是否连接成功SocketChannel sc = (SocketChannel) key.channel();if (key.isConnectable()) {if (sc.finishConnect()) {//8.将新接入的服务端注册到多路由上,监听操作,读取客户端传递来的数据sc.register(selector, SelectionKey.OP_READ);doWrite(sc);} elseSystem.exit(1);// 连接失败,进程退出}if (key.isReadable()) {ByteBuffer readBuffer = ByteBuffer.allocate(1024);//9.异步读取客户端传递数据放入到缓冲区int readBytes = sc.read(readBuffer);if (readBytes > 0) {readBuffer.flip();byte[] bytes = new byte[readBuffer.remaining()];readBuffer.get(bytes);//10.处理消息文件String body = new String(bytes, "UTF-8");System.out.println("Now is : " + body);this.stop = true;} else if (readBytes < 0) {// 对端链路关闭key.cancel();sc.close();} // 读到0字节,忽略}}}private void doConnect() throws IOException {// 如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答if (socketChannel.connect(new InetSocketAddress(host, port))) {//4.将ServerSocketChannel绑定到多路复用器,并监听ACCEPT事件socketChannel.register(selector, SelectionKey.OP_READ);doWrite(socketChannel);} elsesocketChannel.register(selector, SelectionKey.OP_CONNECT);}private void doWrite(SocketChannel sc) throws IOException {byte[] req = "heqing".getBytes();ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);writeBuffer.put(req);writeBuffer.flip();//5.调用SocketChannel的异步write接口,将消息异步发送到服务端sc.write(writeBuffer);if (!writeBuffer.hasRemaining())System.out.println("Send order 2 server succeed.");}}

AIO编程

    NIO 2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。
    异步的套接字通道时真正的异步非阻塞I/O,对应于UNIX网络编程中的事件驱动I/O(AIO)。他不需要过多的Selector对注册的通道进行轮询即可实现异步读写,从而简化了NIO的编程模型。

public class AioServer {public static void main(String[] args) throws IOException {int port = 8080;AioServerHandler timeServer = new AioServerHandler(port);new Thread(timeServer, "AIO-heqing").start();}
}
public class AioServerHandler implements Runnable {CountDownLatch latch;AsynchronousServerSocketChannel asynchronousServerSocketChannel;public AioServerHandler(int port) {try {asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();asynchronousServerSocketChannel.bind(new InetSocketAddress(port));System.out.println("The time server is start in port : " + port);} catch (IOException e) {e.printStackTrace();}}@Overridepublic void run() {latch = new CountDownLatch(1);doAccept();try {latch.await();} catch (InterruptedException e) {e.printStackTrace();}}public void doAccept() {asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());}
}
public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AioServerHandler> {public void completed(AsynchronousSocketChannel result, AioServerHandler attachment) {attachment.asynchronousServerSocketChannel.accept(attachment, this);ByteBuffer buffer = ByteBuffer.allocate(1024);result.read(buffer, buffer, new ReadCompletionHandler(result));}public void failed(Throwable exc, AioServerHandler attachment) {exc.printStackTrace();attachment.latch.countDown();}
}
public class ReadCompletionHandler implementsCompletionHandler<Integer, ByteBuffer> {private AsynchronousSocketChannel channel;public ReadCompletionHandler(AsynchronousSocketChannel channel) {if (this.channel == null)this.channel = channel;}@Overridepublic void completed(Integer result, ByteBuffer attachment) {attachment.flip();byte[] body = new byte[attachment.remaining()];attachment.get(body);try {String req = new String(body, "UTF-8");System.out.println("The time server receive order : " + req);String currentTime = "heqing".equalsIgnoreCase(req) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";doWrite(currentTime);} catch (UnsupportedEncodingException e) {e.printStackTrace();}}private void doWrite(String currentTime) {if (currentTime != null && currentTime.trim().length() > 0) {byte[] bytes = (currentTime).getBytes();ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);writeBuffer.put(bytes);writeBuffer.flip();channel.write(writeBuffer, writeBuffer,new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer buffer) {// 如果没有发送完成,继续发送if (buffer.hasRemaining()) channel.write(buffer, buffer, this);}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {try {channel.close();} catch (IOException e) {// ingnore on closee.printStackTrace();}}});}}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {try {this.channel.close();} catch (IOException e) {e.printStackTrace();}}
}
public class AioClient {public static void main(String[] args) {        int port = 8080;new Thread(new AioClientHandler("127.0.0.1", port), "AIO-heqing-001").start();}
}
public class AioClientHandler implements CompletionHandler<Void, AioClientHandler>, Runnable {private AsynchronousSocketChannel client;private String host;private int port;private CountDownLatch latch;public AioClientHandler(String host, int port) {this.host = host;this.port = port;try {client = AsynchronousSocketChannel.open();} catch (IOException e) {e.printStackTrace();}}public void run() {latch = new CountDownLatch(1);client.connect(new InetSocketAddress(host, port), this, this);try {latch.await();client.close();} catch (InterruptedException e1) {e1.printStackTrace();} catch (IOException e) {e.printStackTrace();}}public void completed(Void result, AioClientHandler attachment) {byte[] req = "heqing".getBytes();ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);writeBuffer.put(req);writeBuffer.flip();client.write(writeBuffer, writeBuffer,new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer buffer) {if (buffer.hasRemaining()) {client.write(buffer, buffer, this);} else {ByteBuffer readBuffer = ByteBuffer.allocate(1024);client.read( readBuffer, readBuffer,new CompletionHandler<Integer, ByteBuffer>() {@Overridepublic void completed(Integer result, ByteBuffer buffer) {buffer.flip();byte[] bytes = new byte[buffer.remaining()];buffer.get(bytes);String body;try {body = new String(bytes, "UTF-8");System.out.println("Now is : " + body);latch.countDown();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {try {client.close();latch.countDown();} catch (IOException e) {// ingnore on closee.printStackTrace();}}});}}@Overridepublic void failed(Throwable exc, ByteBuffer attachment) {try {client.close();latch.countDown();} catch (IOException e) {// ingnore on closee.printStackTrace();}}});}@Overridepublic void failed(Throwable exc, AioClientHandler attachment) {exc.printStackTrace();try {client.close();latch.countDown();} catch (IOException e) {e.printStackTrace();}}
}

各种I/O的对比

具体选择什么样的模型或者NIO框架,完全基于业务的实际应用场景和性能需求,如果客户端很少,服务器负荷不重,就没有必要选择开发起来相对不那么简单的NIO做服务端;相反,就应考虑使用NIO或者相关的框架了。

Java 网络IO编程相关推荐

  1. JAVA网络IO编程

    2019独角兽企业重金招聘Python工程师标准>>> JAVA网络IO编程(BIO NIO AIO) 一.传统的BIO编程 1.网络编程的基本模型是C/S模型,即两个进程间的通信. ...

  2. Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)

    本文会从传统的BIO到NIO再到AIO自浅至深介绍,并附上完整的代码讲解. 下面代码中会使用这样一个例子:客户端发送一段算式的字符串到服务器,服务器计算后返回结果到客户端. 代码的所有说明,都直接作为 ...

  3. (转载)Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)

    转载请注明出处:http://blog.csdn.net/anxpp/article/details/51512200,谢谢! 本文会从传统的BIO到NIO再到AIO自浅至深介绍,并附上完整的代码讲解 ...

  4. Java网络IO演进之路

    前言 说起IO,很多人对它应该都有所耳闻,可能很多人对IO都有着一种既熟悉又陌生的感觉,因为IO这一块内容还是比较广泛杂乱的,整个IO的体系也是十分庞大.那么IO到底是个什么东西呢?IO 是主存和外部 ...

  5. java 网络io详解_Java网络socket编程详解

    或许有点长 但是一步步教你 我想你也愿意看7.2面向套接字编程 我们已经通过了解Socket的接口,知其所以然,下面我们就将通过具体的案例,来熟悉Socket的具体工作方式7.2.1使用套接字实现基于 ...

  6. Java 网络 socket 编程

    使用套接字实现基于 TCP 协议的服务器和客户机程序 依据 TCP 协议,在 C/S 架构的通讯过程中,客户端和服务器的 Socket 动作如下: 客户端: 1.用服务器的 IP 地址和端口号实例化 ...

  7. hadoop---Java 网络IO编程总结BIO、NIO、AIO

    转载请注明出处:http://blog.csdn.net/anxpp/article/details/51512200,谢谢! 本文会从传统的BIO到NIO再到AIO自浅至深介绍,并附上完整的代码讲解 ...

  8. java 网络 io流_【015期】JavaSE面试题(十五):网络IO流

    什么是bio 同步阻塞式IO,服务端创建一个ServerSocket,然后客户端用一个Socket去连接那个ServerSocket,然后ServerSocket接收到一个Socket的连接请求就创建 ...

  9. JAVA的IO编程:管道流

    掌握线程通讯流(管道流)的使用 管道流的主要作用是可以进行两个线程间的通讯,分为管道输入流(PipeOutputStream)和管道输出流(PipeInputStream). 如果要想进行管道输出,则 ...

  10. java基础-IO编程

    流是一组有序的数据序列,分为输入流和输出流.IO操作主要是对文件进行操作,而所有的IO操作都在java.io包中,主要是:File,InputStream,OutputStream,Reader,Wr ...

最新文章

  1. psql:FATAL:数据库“user”不存在
  2. 聚集索引和非聚集索引实例
  3. 苹果电脑删除软件_易我Mac数据恢复软件,解决苹果电脑T2芯片数据恢复难题!
  4. JConsole监控远程linux下的JVM
  5. git init github
  6. freeldr 如何调用_BootMain的
  7. python赋值运算符_解释一下python中的赋值运算符
  8. 流程机器人 RPA:AI落地的接盘侠 | 甲子光年
  9. 阿里云Flink SQL开发指南——字符串函数——REGEXP_REPLACE正则替换
  10. Python 增加时间戳和今日日期
  11. 红帽 Linux 考试 要求
  12. 基于PaaS和SaaS研发的商业云平台实战 - 精华篇
  13. php 情人节语句,告辞情话最暖心短句向男生 情人节表明语录
  14. 007-2虚拟地址空间布局
  15. 二进制颜色代码大全(含图)——转
  16. autohotkey 变量
  17. 【译】代码中如何写出更有意义的命名
  18. 软件开发过程中提升用户体验的途径[自己整理了下]
  19. STM32Cube_FW_F1_V1.0.0固件库学习(一)环境准备
  20. 广东省数字政府网络安全评估体系与实践

热门文章

  1. 云计算数据中心网络的关键技术
  2. bootstrap-tagsinput 使用方法
  3. 高等数学基础:求导总结
  4. 电影《海贼王:红发歌姬》观后感
  5. 博士申请 | 浙江大学孙优贤院士课题组招收智能无人系统方向硕士生/博士生
  6. 2021年最佳Selenium替代品
  7. 2018年最新个人所得税EXCEL公式
  8. c++ 加载dll 和 生成dll
  9. java基础jdk,jre,jvm作业-答案
  10. PPPOE拨号下MTU设置