java nio doug_深入的聊聊 Java NIO

趁着三天假期,把Java NIO和Reactor模式整理总结了下,文章特别细节的知识点没有写,如一些API的具体实现。类似数据读到Buffer后再写出时,为什么需要复位操作,这些都属于NIO基础知识,是学习Reactor模式的前置条件。

1. 原始Ractor模式

aa24f3b13c5f2248bc8fd0a159e831eb.png

相关组件的解释

Handle(句柄或是描述符):本质上表示一种资源,是操作系统提供的;该资源用于表示一个个事件,比如文件描述符,或者是针对于网络编程中的Socket描述符。事件既可以来自于外部,也可以来自内部;外部事件比如说客户端的连接请求,客户端发送过来数据等;内部事件比如说操纵系统产生的定时器事件等。它本质上就是一个文件描述符。Handle是事件产生的发源地。

Synchronous Event Demultiplexer(同步事件分离器):它本身是一个系统调用,用于等待事件的发生(事件可能是一个,也可能是多个)。调用方在调用它的时候会被阻塞,一直阻塞到同步事件分离器上有事件产生为止。对于Linux来说,同步事件分离器指的就是常用的I/O多路复用机制,比如说select、poll、epoll等。在Java NIO中,同步事件分离器对应的组件就是Selector;对应的阻塞方法就是select方法。

Event Handler(事件处理器) 本身由多个回调方法构成,这些回调构成了与应用相关的对于某个事件的反馈机制。Netty相比于Java NIO来说,在事件处理器这个角色上进行一个升级,它为我们开发者提供了大量的回调方法,供我们在待定事件产生时实现相应的回调方法进行业务逻辑的处理。

Concrete Event Handler(具体事件处理器):它本身实现了事件处理所提供的各个回调方法,从而实现了特定于业务的逻辑。它本质上就是我们所编写的一个个的处理器实现。

Initiation Dispatcher(初始分发器):实际上就是Reactor角色。它本身定义了一些规范,这些规范用于控制事件的调度方式,同时又提供了应用进行事件处理器的注册、删除等。Initiation Dispatcher会通过同步事件分离器来等待事件的发生,一旦事件发生,Initiation Dispatcher首先会分离出每一个事件,然后调用事件处理器,最后调用相关的回调方法来处理事件。

执行流程分析

当应用像Initiation Dispatcher注册具体的事件处理器时,应用会标识出事件处理器希望Initiation Dispatcher在某个事件发生时向其通知该事件,该事件与Handle关联。

Initiation Dispatcher会要求每个事件向其传递内部的Handle。该Handle向操作系统标识了事件处理器。

当所有事件处理器注册完毕后,应用会调用handle_events方法来启动Initiation Dispatcher的事件循环。这时,Initiation Dispatcher会将每个注册的事件管理器的Handle合并起来,并使用同步事件分离器等待这些事件的发生。比如说,TCP协议层使用select同步事件分离器操作来等待客户端发送的数据到达连接的socker handle上。

当与某个事件源对应的Handle变为ready状态时(比如说,TCP socker变为等待读状态时),同步事件分离器就会通知Initiation Dispatcher。

Initiation Dispatcher会触发事件处理器的回调方法,从而响应这个处于ready状态的Handle。Initiation Dispatcher会回调事件处理器的handle_events回调方法来执行特定于应用的功能(开发者自己所编写的功能),从而响应这个事件。所发生的事件类型可以作为该方法参数并被该方法内部使用来执行额外的特定于服务的功能。

以上描述的内容似乎和本文的标题不大,其实不然,它正是下面介绍的内容的开端。

2. 通过一个例子拉近与Java NIO的距离

/**

* @Author CoderJiA

* @Description NIOServer

* @Date 13/2/19 下午4:59

**/

public class NIOServer {

public static void main(String[] args) throws Exception{

// 1.创建ServerSocketChannel

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.configureBlocking(false);

ServerSocket serverSocket = serverSocketChannel.socket();

serverSocket.bind(new InetSocketAddress(8899));

// 2.创建Selector,并ServerSocketChannel注册OP_ACCEPT事件,接收连接。

Selector selector = Selector.open();

serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

// 3.开启轮询

while (selector.select() > 0) {

// 从selector所有事件就绪的key,并遍历处理。

Set selectionKeys = selector.selectedKeys();

selectionKeys.forEach(selectionKey -> {

SocketChannel client;

try {

if (selectionKey.isAcceptable()) { // 接受事件就绪

// 获取serverSocketChannel

ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();

// 接收连接

client = server.accept();

client.configureBlocking(false);

client.register(selector, SelectionKey.OP_READ);

} else if (selectionKey.isReadable()) { // 读事件就绪

// 获取socketChannel

client = (SocketChannel) selectionKey.channel();

// 创建buffer,并将获取socketChannel中的数据读入到buffer中

ByteBuffer readBuf = ByteBuffer.allocate(1024);

int readCount = client.read(readBuf);

if (readCount <= 0) {

return;

}

Charset charset = Charset.forName(StandardCharsets.UTF_8.name());

readBuf.flip();

System.out.println(String.valueOf(charset.decode(readBuf).array()));

}

} catch (IOException e) {

e.printStackTrace();

}

selectionKeys.remove(selectionKey);

});

}

}

复制代码通过这个例子,与原始Reactor模式相对应的理解,比如同步事件分离器对应着Selector的select()方法,再比如ServerSocketChannel注册给Selector的OP_ACCEPT,还有SocketChannel的OP_READ与OP_WRITE,这些事件保存在操作系统上,其实就是原始Reactor中的Handle。

四个重要api

Channel:Connections to files,sockets etc that support non-blocking reads.

Buffer:Array-like objects that can be directly read or written by Channels.

Selector:Tell which of a set of Channels have IO events.

SelectionKeys:Maintain IO event status and bingdings.

3.用Java NIO对Reactor模式的应用。

48c6e5c3d7dd7ea23a5abd28c71662e5.png

3.1 Single threaded version

/**

* @Author CoderJiA

* @Description Reactor

* @Date 5/4/19 下午2:25

**/

public abstract class Reactor implements Runnable{

protected final Selector selector;

protected final ServerSocketChannel serverSocket;

protected final long port;

protected final long timeout;

public Reactor(int port, long timeout) throws IOException {

this.port = port;

this.timeout = timeout;

selector = Selector.open();

serverSocket = ServerSocketChannel.open();

serverSocket

.socket()

.bind(new InetSocketAddress(port));

serverSocket.configureBlocking(false);

SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);

sk.attach(newAcceptor(selector));

}

@Override

public void run() {

try {

while (!Thread.interrupted()) {

if (selector.select(timeout) > 0) {

Set selected = selector.selectedKeys();

selected.forEach(sk -> {

dispatch(sk);

selected.remove(sk);

});

}

}

} catch (IOException e) {

e.printStackTrace();

}

}

private void dispatch(SelectionKey sk) {

Runnable r = (Runnable)(sk.attachment());

if (Objects.nonNull(r)) {

r.run();

}

}

public abstract Acceptor newAcceptor(Selector selector);

}

复制代码/**

* @Author CoderJiA

* @Description Acceptor

* @Date 5/4/19 下午2:58

**/

public class Acceptor implements Runnable {

private final Selector selector;

private final ServerSocketChannel serverSocket;

public Acceptor(Selector selector, ServerSocketChannel serverSocket) {

this.selector = selector;

this.serverSocket = serverSocket;

}

@Override

public void run() {

try {

SocketChannel socket = serverSocket.accept();

if (Objects.nonNull(socket)) {

new Handler(selector, socket);

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

复制代码/**

* @Author CoderJiA

* @Description Handler

* @Date 5/4/19 下午4:25

**/

public class Handler implements Runnable {

private static final int MB = 1024 * 1024;

protected final SocketChannel socket;

protected final SelectionKey sk;

protected final ByteBuffer input = ByteBuffer.allocate(MB);

protected final ByteBuffer output = ByteBuffer.allocate(MB);

private static final int READING = 0, SENDING = 1;

private int state = READING;

public Handler(Selector selector, SocketChannel socket) throws IOException {

this.socket = socket;

socket.configureBlocking(false);

sk = socket.register(selector, SelectionKey.OP_READ);

sk.attach(this);

}

@Override

public void run() {

try {

if (state == READING) read();

else if (state == SENDING) send();

} catch (Exception e) {

e.printStackTrace();

}

}

private void read() throws IOException {

socket.read(input);

if (inputIsComplete()) {

state = SENDING;

sk.interestOps(SelectionKey.OP_WRITE);

}

input.clear();

}

private void send() throws IOException {

socket.write(output);

if (outputIsComplete()) {

sk.cancel();

}

}

private boolean inputIsComplete() {

return input.position() > 0;

}

private boolean outputIsComplete() {

return !output.hasRemaining();

}

}

复制代码/**

* @Author CoderJiA

* @Description EchoReactor

* @Date 5/4/19 下午5:01

**/

public class EchoReactor extends Reactor {

private static final int PORT = 9999;

private static final long TIME_OUT = TimeUnit.MILLISECONDS.toMillis(10);

public EchoReactor(int port, long timeout) throws IOException {

super(port, timeout);

}

@Override

public Acceptor newAcceptor(Selector selector) {

return new Acceptor(selector, this.serverSocket);

}

public static void main(String[] args) throws IOException {

new EchoReactor(PORT, TIME_OUT).run();

}

}

复制代码

核心组件组件分析

Reactor等同于原始Reactor模式的Initiation Dispatcher,它负责所有就绪事件统一分发到事件处理器,如Acceptor和Hanlder。

Acceptor用于将接收到的SocketChannel交给Handler处理。

Handler处理读写操作。

这是Reactor的单线程版本,这个版本一个线程处理客户端的接收和数据处理以及读写操作,数据处理往往就是我们实际开发中的业务处理,是比较耗时的。如果一个处理过程处于阻塞,那么这个模型所表现出的就处于阻塞,所以一个数据处理的阻塞会导致不能处理客户端连接的接收。因此衍生出来下面的多工作线程版本来优化Handler。

3.2 Worker Threads version

bd2fa17ad25127bb95a19e84e1cb57ec.png

调整下Handler

package cn.coderjia.nio.douglea.reactor2;

import java.io.IOException;

import java.nio.ByteBuffer;

import java.nio.channels.SelectionKey;

import java.nio.channels.Selector;

import java.nio.channels.SocketChannel;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

/**

* @Author CoderJiA

* @Description Handler

* @Date 5/4/19 下午4:25

**/

public class Handler implements Runnable {

private static final int MB = 1024 * 1024;

protected final SocketChannel socket;

protected final SelectionKey sk;

protected final ByteBuffer input = ByteBuffer.allocate(MB);

protected final ByteBuffer output = ByteBuffer.allocate(MB);

private static final int READING = 0, SENDING = 1, PROCESSING = 3;

private int state = READING;

private static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

public Handler(Selector selector, SocketChannel socket) throws IOException {

this.socket = socket;

socket.configureBlocking(false);

sk = socket.register(selector, SelectionKey.OP_READ);

sk.attach(this);

}

@Override

public void run() {

try {

if (state == READING) read();

else if (state == SENDING) send();

} catch (Exception e) {

e.printStackTrace();

}

}

private void read() throws IOException {

socket.read(input);

if (inputIsComplete()) {

state = PROCESSING;

EXECUTOR_SERVICE.execute(new Processer());

}

input.clear();

}

private void send() throws IOException {

socket.write(output);

if (outputIsComplete()) {

sk.cancel();

}

}

private void process() {

System.out.println("Handler.process()...");

}

private boolean inputIsComplete() {

return input.position() > 0;

}

private boolean outputIsComplete() {

return !output.hasRemaining();

}

class Processer implements Runnable {

public void run() {

processAndHandOff();

}

}

synchronized void processAndHandOff() {

process();

state = SENDING;

sk.interestOps(SelectionKey.OP_WRITE);

}

}

复制代码Handler多工作线程版本将耗时的process(),创建线程去处理。这个版本Reactor既负责客户端的接收事件,又负责读写事件,因为对于高并发场景连接数巨大,Reactor可能有时候会力不从心。因此衍生出下面的主从Reactor模型。

3.3 Multiple Reactors Version

6e664eee300e268aba13740721d337e6.png

调整Acceptor

/**

* @Author CoderJiA

* @Description Acceptor3

* @Date 6/4/19 下午6:51

**/

public class Acceptor3 implements Runnable {

private final ServerSocketChannel serverSocket;

public Acceptor3(ServerSocketChannel serverSocket) {

this.serverSocket = serverSocket;

}

@Override

public void run() {

try {

SocketChannel socket = serverSocket.accept();

if (Objects.nonNull(socket)) {

new Handler(EchoReactor.nextSubReactor().selector, socket);

}

} catch (IOException e) {

e.printStackTrace();

}

}

}

复制代码

调整Reactor

/**

* @Author CoderJiA

* @Description Reactor3

* @Date 6/4/19 下午6:51

**/

public abstract class Reactor3 implements Runnable {

protected Selector selector;

protected ServerSocketChannel serverSocket;

protected final int port;

protected final long timeout;

protected final boolean isMainReactor;

public Reactor3(int port, long timeout, boolean isMainReactor) {

this.port = port;

this.timeout = timeout;

this.isMainReactor = isMainReactor;

}

@Override

public void run() {

try {

init();

while (!Thread.interrupted()) {

if (selector.select(timeout) > 0) {

System.out.println("isMainReactor:" + isMainReactor);

Set selected = selector.selectedKeys();

selected.forEach(sk -> {

dispatch(sk);

selected.remove(sk);

});

selected.clear();

}

}

} catch (IOException e) {

e.printStackTrace();

}

}

private void init() throws IOException {

selector = Selector.open();

if (isMainReactor) {

serverSocket = ServerSocketChannel.open();

serverSocket

.socket()

.bind(new InetSocketAddress(port));

serverSocket.configureBlocking(false);

SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);

sk.attach(newAcceptor());

}

}

private void dispatch(SelectionKey sk) {

Runnable r = (Runnable)(sk.attachment());

if (Objects.nonNull(r)) {

r.run();

}

}

public abstract Acceptor3 newAcceptor();

}

复制代码/**

* @Author CoderJiA

* @Description EchoReactor

* @Date 6/4/19 下午5:35

**/

public class EchoReactor extends Reactor3 {

private static final int PORT = 9999;

private static final long TIME_OUT = TimeUnit.MILLISECONDS.toMillis(10);

private static final int SUB_REACTORS_SIZE = 2;

private static final Reactor3[] SUB_REACTORS = new Reactor3[SUB_REACTORS_SIZE];

private static final AtomicInteger NEXT_INDEX = new AtomicInteger(0);

static {

// 初始化子Reactor

IntStream.range(0, SUB_REACTORS_SIZE).forEach(i -> SUB_REACTORS[i] = new EchoReactor(PORT, TIME_OUT, false));

}

public static Reactor3 nextSubReactor(){

int curIdx = NEXT_INDEX.getAndIncrement();

if(curIdx >= SUB_REACTORS_SIZE){

NEXT_INDEX.set(0);

curIdx = 0;

}

return SUB_REACTORS[(curIdx % SUB_REACTORS_SIZE)];

}

public EchoReactor(int port, long timeout, boolean isMainReactor) {

super(port, timeout, isMainReactor);

}

@Override

public Acceptor3 newAcceptor() {

return new Acceptor3(this.serverSocket);

}

public static void main(String[] args) {

Reactor3 mainReactor = new EchoReactor(PORT, TIME_OUT, true);

// 启动主Reactor

new Thread(mainReactor).start();

// 启动子Reactor

IntStream.range(0, SUB_REACTORS_SIZE).forEach(i -> new Thread(SUB_REACTORS[i]).start());

}

}

复制代码主从Reactor模型,主Reactor用于处理客户端连接的接收转发给Acceptor处理,子Reactor处理读写事件的接收转发给Handler处理。

参考文章

Scalable IO in Java

源码地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/500230.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java clone原理_详解Java中的clone方法 -- 原型模式

Java中对象的创建clone顾名思义就是复制&#xff0c; 在Java语言中&#xff0c; clone方法被对象调用&#xff0c;所以会复制对象。所谓的复制对象&#xff0c;首先要分配一个和源对象同样大小的空间&#xff0c;在这个空间中创建一个新的对象。那么在java语言中&#xff0c;有…

portal认证 java_华为5700交换机通过外部开源protal和本地aaa用户认证的一些问题

各位&#xff1a;您好&#xff0c;我通过一台华为5700交换机和一台portal服务器&#xff0c;想利用交换机本地的aaa认证&#xff0c;完成用户的上网认证。配置好后&#xff0c;用户可以重地向到portal页面&#xff0c;但是认证不能通过&#xff0c;具体配置如下&#xff1a;一、…

java复制一个对象_Java中对象的复制

假如说你想复制一个简单变量。很简单:1 int n 5;2 int m n;不仅仅是int类型&#xff0c;其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。但是如果你复制的是一个对象&#xff0c;情况就有些复杂了。假设说我是一个beginner&#xff0c…

HA集群实现原理 切换 JAVA_HA(一)高可用集群原理

高可用集群原理LVS集群DR模式简单的架构图如下所示&#xff1a;在上图的架构中&#xff0c;当Director服务器因软件、硬件、人为原因造成故障时&#xff0c;整个集群服务不可用&#xff0c;因此&#xff0c;需要再添加一台服务器实现Director服务高可用。整个系统的架构图如下所…

c语言指针没学可以学java_这是一篇来自刚脱离C语言的菜鸟所写下来的关于C语言之后转JAVA入门前期学习的感想...

/***My First writing*Name Li Tai Yue*Date 2018.12.14*/这是一篇来自刚脱离C语言的菜鸟所写下来的关于C语言之后转JAVA入门前期学习的感想。并且我也觉得这是一篇所有在校学习程序的小伙伴值得一看的文章。我想很多小伙帮都觉得敲代码是一件非常枯燥的事情&#xff0c;每天敲…

java 如何将数字倒置_每日一个小算法之整数中每位上的数字进行反转 20190810

题目要求&#xff1a;给出一个32位的有符号整数&#xff0c;你需要将这个整数中每位上的数字进行反转。示例 1:输入: 123输出: 321示例 2:输入: -123输出: -321示例 3:输入: 120输出: 21示例 4:输入&#xff1a;9646324351输出: 0注意&#xff1a;假设我们的环境只能存储得下 3…

java se 开发web程序_JDiy快速开发WEB之javaSE环境搭建-初级

大学的时候对web开发很感兴趣&#xff0c;对网页中的动画&#xff0c;对用户注册&#xff0c;对网页中表格填写等等都倍感兴趣。加之又有专业课程编程语言java&#xff0c;因此&#xff0c;对java web产生了浓厚的兴趣&#xff0c;再加之有北京圣思园 风中叶 大师的视频教程&am…

java apktoo_apktool.jar

apktool.jar是APKTOOL这个反编译工具必须用到的必备jar包&#xff0c;给大家提供最新的apktool.jar2.3.4&#xff0c;有需要的赶快下载吧&#xff01;。相关软件软件大小版本说明下载地址apktool.jar是APKTOOL这个反编译工具必须用到的必备jar包&#xff0c;给大家提供最新的ap…

Java 内存 关系_JVM和Linux之间的详细内存关系

JVM和Linux之间的详细内存关系在一些具有8g物理内存的服务器上&#xff0c;主要运行Java服务。系统内存分配如下&#xff1a;Java服务的JVM堆大小设置为6g&#xff0c;监视过程大约需要600m&#xff0c;Linux本身使用大约800m。从表面上看&#xff0c;物理记忆应该足够&#xf…

java遍历删除原理,Java 垃圾回收机制实现原理

一、垃圾回收机制的意义Java语言中一个显著的特点就是引入了垃圾回收机制&#xff0c;使c程序员最头疼的内存管理的问题迎刃而解&#xff0c;它使得Java程序员在编写程序的时候不再需要考虑内存管理。由于有个垃圾回收机制&#xff0c;Java中的对象不再有“作用域”的概念&…

matlab简易编程,MATLAB简单编程

本帖最后由 wanggh 于 2016-12-5 14:47 编辑用数值差分、SOR迭代法求雷诺方程和用牛顿迭代法求解轴向柱塞泵滑靴副压力场的算法&#xff0c;%油膜厚度场、压力场迭代 MATLAB只认弧度制&#xff0c;不认角度制clear all; % (60rpm1rad/s)wg1000; %1000…

php删除字段某个字段,php数如何组删除某个字段

【摘要】PHP即“超文本预处理器”&#xff0c;是一种通用开源脚本语言。PHP是在服务器端执行的脚本语言&#xff0c;与C语言类似&#xff0c;是常用的网站编程语言。PHP独特的语法混合了C、Java、Perl以及 PHP 自创的语法。下面是php数如何组删除某个字段&#xff0c;让我们一起…

JAVA table word,实战 | Java读取Word,包含表格!

本文转载自微信公众号「JAVA日知录」&#xff0c;作者单一色调。转载本文请联系JAVA日知录公众号。不能每天都发鸡汤呀&#xff0c;今天分享一篇开发实战。业务需求我们有这样一个需求&#xff0c;需要抽取出WORD文档中的内容&#xff0c;然后组装成特定的json格式发送给第三方…

mysql分列查询,Mysql导出问题,乱码问题,为分列问题解决!!!

前言&#xff1a;本文可以先阅读完再跟着做。Mysql查询出的数据导出为csv最近遇到同学的一个需求&#xff0c;要求差寻一些数据然后导出给他&#xff0c;因为之前也有做过类似的事情&#xff0c;觉得可以一下就搞定&#xff0c;但是居然出现乱码又出现没有分列的情况&#xff0…

微信你scope 参数错误 php,微信开发: scope参数错误或没有scope权限解决方法

scope为snsapi_userinfo 未关注者点击授权提示 scope参数错误或没有scope权限解决方法出现这种错误网上查出现有的原因是:订阅号没有相关的权限账号没有认证&#xff0c;没有相关的权限那么这里遇到问题两种都不是。开发账号是 服务号&#xff0c;而且也是认证号。解决方法:错…

Linux怎么查看保存的密码,在Linux中查看已保存的WiFi密码

在安装流行操作系统时&#xff0c;如 Windows 10、Ubuntu、macOS 等&#xff0c;都会要求用户提前输入 WiFi 密码。特别是 Ubuntu 系统&#xff0c;在配置安装向导时就可以连接 WiFi&#xff0c;以方便在系统安装过程就从网络获取最新更新&#xff0c;并在安装完成后就为用户提…

linux 网卡 巨帧,Linux Kernel e1000e驱动巨型帧处理绕过安全检查漏洞

发布日期&#xff1a;2009-12-29更新日期&#xff1a;2010-01-13受影响系统&#xff1a;Linux kernel 2.6.32.3描述&#xff1a;--------------------------------------------------------------------------------BUGTRAQ ID: 37523CVE(CAN) ID: CVE-2009-4538Linux Kernel是…

linux下如何启动vsftp服务,如何在Ubuntu 18.04上使用VSFTP快速设置FTP服务器

如果您需要配置一个FTP服务器快速启动运行&#xff0c;那么VSFTP的易用性是无与伦比的。如果你想将应用程序迁移到最新版本Ubuntu Linux服务器中&#xff0c;并且需要快速启动并运行FTP服务器。如何操作&#xff1f;这实际上非常简单。这里将使用VSFTP服务器向您展示如何做到这…

红帽linux kvm,初识RedHat虚拟化—KVM

redhat虚拟化之KVM初识虚拟化简介&#xff1a;虚拟化是指计算机元件在虚拟的基础上而不是真实的基础上运行。虚拟化技术可以扩大硬件的容量&#xff0c;简化软件的重新配置过程。CPU的虚拟化技术可以单CPU模拟多CPU并行&#xff0c;允许一个平台同时运行多个操作系统&#xff0…

Linux中的软件源详解,Ubuntu Linux 软件源详解

中国台湾 台湾大学更新服务器(推荐网通用户使用&#xff0c;强烈推荐此源&#xff0c;比较完整)&#xff1a;deb http://Ubuntu.csie.ntu.edu.tw/ubuntu/ gutsy main restricted universe multiversedeb-src http://ubuntu.csie.ntu.edu.tw/ubuntu/ gutsy main restricted univ…