前面的话:自从接触网络模块,到现在有一阵子时间了,未来必定是网络的世界。学一些网络方面的知识是有必要的。我们ALINTEK 推出的ENC28J60网络模块块作为入门还是不错的。详细见此贴:
http://www.openedv.com/posts/list/9355.htm。时间对于一个开发人员是很宝贵的,如何快速应用是我们做技术的,都想要的。废话不多说了。因为主要集中在怎么应用所以有些细节可能不是正确的,
这个需要大家去质疑,去验证。

一、LWIP的应用
1.什么是LWIP?
lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。

2.哪里可以下载源码?
在这里可以下载到最新的应用:http://savannah.nongnu.org/projects/lwip/

3.更多详细介绍?
在这里有详细的:http://lwip.wikia.com/wiki/LwIP_Wiki

4.如何移植?
LWIP 有三种应用模式 RAW API,Netconn API,Socket API.这里我们主要简单讲解如何移植RAW API。移植LWIP的过程也是一个了解LWIP大概结构的过程。
当你把所有的错误,和警告干掉的后就差不多了,呵呵。 
①首先到官网上下载好源码和应用例程。
<ignore_js_op> 
lwip-1.4.1中src文件夹下的源码文件结构:
<ignore_js_op> ②按以下这个目录结构将源码加入到工程
<ignore_js_op> 
前面三个就不用解释了,看看上面的源码文档结构就知道了,LWIP_ARCH文件夹是什么东东呢?它是一个操作系统与处理器平台配置有关的代码,你要问我这个东西哪里来的,
嘿嘿,从ST的官方固件库中偷来滴。里面包含了大小端的配置,类型定义,和操作系统有关的部分,不过这里没有用到操作系统,所以这个sys_arch.c文件需要改装下。
LWIP_APP顾名思义就是LWIP的应用程序了,这里面编写了,TCP服务器,TCP客户端,UDP服务器,UDP客户端,Webserver(应用程序和服务器的接口技术(CGI),动态网页技术(SSI),和ajax技术,做异步提交,获取数据)。的方法和例程。接下来请看我如何修改移植。

③底层驱动的移植。
在LWIP-NETIF文件加下,我们需要对ethernetif.c文件修改。该文件是一个底层网络接口框架。LWIP的作者已经为我们搭好了框架,其中有些代码是伪代码,我们只需要去实现就好了。

A.修改底层硬件初始化函数low_level_init(struct netif *netif)函数,在函数中设置好,网络的MAC地址,最大传输单元,然后再初始化ENC28J60,如果初始化失败则返回错误给上层调用的函数。
<ignore_js_op>

B.修改底层发送包的函数low_level_output(struct netif *netif, struct pbuf *p),在这个函数中将LWIP数据包中的缓冲区pbuf 复制到待发送的缓冲区lwip_buf中来,
然后利用ENC28J60的发包函数发出去。
<ignore_js_op> 
C.修改底层接收包的函数low_level_input(struct netif *netif),这个过程就是把接收缓冲区lwip_buf中的数据复制到LWIP的pbuf中,如果收到了包, 我们就可以在lwip_buf中读取数据了。注:lwip_buf作为接收和发送数据的缓冲。
<ignore_js_op> 
移植完这三个函数就差不多啦,简单吧,嘿嘿。

④在主函数main.c初始化LWIP。
lwip_init()是用来LWIP初始化的,但是其中的netif_init()初始化的是回环网络接口,我们想把数据发出去必须初始化ENC28J60。所以我们先调用lwip_init()来初始化LWIP的各个模块。
再利用netif_add函数添加网络接口就可以。这个函数会调用底层的 low_level_init函数,如果返回值为空,则说明ENC28J60初始化失败了。然后再注册,建立一下这个网络接口就OK了。
<ignore_js_op> 注意到上面的有一个函数 init_lwip_timer();  这个是什么呢,这个是用来初始化LWIP的定时器,用来计时用的,相当于LWIP的心脏。

⑤在主函数main.c中编写轮训协议栈函数
LWIP需要周期性的处理一些函数,这些函数是是根据你使能的协议和功能模块来调用的。比如你要用到TCP协议,你就必须周期性的调用tcp_tmr()。
注意到有个timer_expired函数,这个函数就在sys_arch.c文件中,这个文件本来与操作系统有关,现在改装了。用来初始化LWIP的时钟,提供超时检测的功能,相当于定时器了。

<ignore_js_op> 
⑥配置LWIP
LWIP有一个标准配置文件在opt.h中,这个头文件又包含了lwipopts.h,通常我们配置LWIP是通过这个文件来配置的。
这个头文件可以覆盖opt.h中任何你需要的配置。这样做的好处,是源码更加健壮了,而且能够防止我们错误的配置,而改不回去了。
有几个配置是很重要的,NO_SYS  1-----无操作系统   MEM_ALIGNMENT    4 ---  分配内存是四字节对齐,这个很重要,曾经莫名其妙的出现hardfault
TCP_MSS      1460 --TCP最大段大小,这个是极限值了,我们可以传更多的数据。
<ignore_js_op>

⑦在main函数中加入LWIP_Polling(),基本上就移植完毕了。这时可以使用ping 命令。
比如我设置的IP地址为19.168.1.10则,通过cmd命令进入dos环境。通过 ping 192.168.1.10 -t 就可以不断查询网络是否联通了。

5.如何应用?
RAW API中文译为原始API可以说比较接近底层了,玩过socket编程的人都知道,socket编程用起来,是比较简单了,不像RAW API的模式,用起来比较麻烦,需要应用者对TCP,UDP这些协议,有一个
稍微深入的了解。这里我推荐一款抓包软件Wireshark。这个软件可以帮助你分析这些协议是怎么工作的。不过在用这个软件的,分析协议的时候最好不要连接到外网,会干扰的哦。

①TCP服务器和客户端
A.TCP通信简介
TCP是面向连接的,在进行通信前需要建立连接。下面以TCP服务器模式为例简单介绍下这个过程。
建立连接:(截图看不清见附件)建立连接.jpg发送数据:
发送数据.jpg
关闭连接:
服务器关闭连接.jpg

B.TCP服务器模式
RAW API大部分都是基于回调函数的API,我们需要按照一定的规范去实现这个回调函数。作为TCP服务器,必须要一个本地IP和端口。
处于服务器模式,是不需要设置远程主机IP和端口,因为远程主机在连接到服务器的过程当中,服务器会把它的IP地址等信息记录下来。就可以双向传输了。
tcp_bind(tcp_server_pcb,IP_ADDR_ANY,TCP_SERVER_PORT);//绑定本地所有IP地址和端口号 
tcp_listen(tcp_server_pcb); //开始监听端口

tcp_accept(tcp_server_pcb,tcp_server_accept); //指定监听状态的连接联通之后将要调用的回调函数
tcp_recv(newpcb, tcp_server_recv); //指定连接接收到新的数据之后将要调用的回调函数tcp_err(newpcb, tcp_server_error); //指定连接出错将要调用的函数tcp_poll(newpcb, tcp_server_poll, 0); //指定轮询时将要调用的回调函数对于不熟悉这种习惯的,开始可能有点迷糊糊,不过看多了就好了。上面四个函数,都是用来指定回调函数的。
他们指定的回调函数在指定事件(比如建立了连接,接收到了数据,连接空闲等等)发生的时候将会被调用。

C.TCP客户端模式
对于TCP客户端模式,我们需要指定连接的远程服务器的IP地址和端口号,其他的函数就不多说了,详细见代码
ip_addr_t ipaddr;IP4_ADDR(&ipaddr, 192, 168, 1, 101);   //设置本地ip地址tcp_connect(tcp_client_pcb,&ipaddr,TCP_CLIENT_PORT,tcp_client_connect);  //连接到远程服务器

②UDP的服务器和客户端
相对于TCP来说UDP则就简单了许多,他不需要像TCP一样需要建立连接通道才可以进行通信。没有重发机制。所以它是不可靠的通行。但是速度快,是他的一大亮点。
A:UDP服务器模式
udp_bind(udp_server_pcb,IP_ADDR_ANY,UDP_SERVER_PORT);  //帮顶本地IP地址和端口udp_recv(udp_server_pcb,udp_server_rev,NULL); //指定收到数据包时的回调函数不过需要注意在接受数据的udp_server_rev回调函数中需要将客户端的IP地址和端口记下来,方便UDP服务器发送数据给客户端。
udp_server_pcb->remote_ip = *addr; //记录远程主机的IPudp_server_pcb->remote_port = port;//记录远程主机的端口号
B:UDP客户端模式
udp_bind(udp_client_pcb,IP_ADDR_ANY,UDP_CLIENT_PORT); //这里可以绑定任意的端口,不一定是服务器的端口号,作为客户端主要关心的是要连接的端口和IPudp_connect(udp_client_pcb,&ipaddr,UDP_CLIENT_PORT);  //设置连接到远程主机
③web服务器
现在那种browse+server的模式很流行,在PC机中。这种模式,客户端只需要一个浏览器就OK了,不需要下载专门的客户端,现在的网页游戏这么流行就证明了这一点。你想想在家里,
我的手机通过WIFI连接到路由器,你的嵌入式服务器也连接到路由器,只要有手机,不需要携带笨重的电脑,装麻烦软件,就可以控制家里的电器,多爽啊,这就是物联网的应用。
如何减少嵌入式服务器的开发难度,是很多人研究的重点。感觉LWIP的CGI和SSI接口用起来还是比较爽的,结合一些流行的web前端技术,我们可以开发比较漂亮的应用。

A.什么是CGI?
CGI是Common Gateway Interface通用网关接口的简称。百度一下解释多多。其实,他的作用是用来让服务器和应用程序打交道的。当我们从浏览器获取参数和对应的值后,然后与应用程序
进行交互的这个函数,就是CGI函数了。对应的URLl路径就写一个对应的CGI函数。这样不同的请求,就有不同的处理,这个函数将会返回,你想发送给浏览器的文件的文件名路径。

B.CGI函数介绍
typedef  char *(*tCGIHandler)(int iIndex, int iNumParams, char *pcParam[],                             char *pcValue[]);参数说明:
iIndex :表示在ppcURLs数组中的索引号码, ppcURLs 是个什么玩意呢?
ppcURLs  的类型是这个东东。
typedef struct{    const char *pcCGIName;      //浏览器请求的URL    tCGIHandler pfnCGIHandler;  //定义的CGI函数指针} tCGI;那么这个东东有什么作用呢?,他是用来注册URL路径和对应的函数,void http_set_cgi_handlers(const tCGI *pCGIs, int iNumHandlers),这个函数会对它进行设置。当浏览器发出一个请求URL时,这个URL路径对应的CGI函数就会执行。
lwip已经帮我们做好了,我只修需要注册URL路径和对应的CGI函数就可以了,爽吧。
iNumParams :表示URL中你请求参数和其参数对应值的个数,比如http://192.168.1.16/login.cgi?name=onetree&password=12345,参数是name和password,值是onetree和12345,个数就是2.pcParam :参数的数组,包含所有的参数
pcValue:  对应参数的值的数组
C.CGI应用举例
比如。浏览器,采用get的方式,发了一个请求。http://192.168.1.16/login.cgi?/name=onetree&password=12345
我们的应用程序怎么接收我们的用户名name和密码password呢?

1.注册URL路径和对应的函数
static const tCGI ppcURLs[] ={    { "/login.cgi",      Login_CGIHandler },  };

2.设置请求的URL的路径和CGI函数
#define NUM_CONFIG_CGI_URIS     (sizeof(ppcURLs ) / sizeof(tCGI))
 http_set_cgi_handlers(ppcURLs , NUM_CONFIG_CGI_URIS);

3.编写CGI函数
static char * Login_CGIHandler  ( int iIndex, int iNumParams, char *pcParam[], char *pcValue[] ){
  int index;
  index = FindCGIParameter ( "name", pcParam, iNumParams );
  if(index!=-1){
      name对应的参数值=  pcValue[index];      
  }
   index = FindCGIParameter ( " password", pcParam, iNumParams ); 
  if(index!=-1){
    password对应的参数值 =pcValue[index];  }
 return  "/index.html";  //这里返回你要发给浏览器的文件名路径

}
注意到有个 FindCGIParameter  的函数,这个函数是用来查找你想要的参数。
static int FindCGIParameter(const char *pcToFind, char *pcParam[], int iNumParams){    int iLoop;
    for(iLoop = 0; iLoop < iNumParams; iLoop++)    {        if(strcmp(pcToFind, pcParam[iLoop]) == 0)        {            return(iLoop);        }    }    return(-1);}到此为止,我们就完成了,浏览器和应用程序的交互了。

D.什么是SSI?
SSI是Server Side Include服务器嵌入端的简称。是一种类似于ASP的基于服务器的网页制作技术。
工作原理:LWIP对于.shtml,.ssi,.shtm后缀的文件,会检测文件中<!--#name-->格式的TAG标志。
然后再这个标记后面添加你想要的字符串。并不是替换,不过这个方法在脚本中不行, <!--#name-->
是html文件的注释,但是在 <script>...</script>中就不是注释了,所以在添加js代码的时候必须把整个
JS脚本添加进来。

E.SSI函数简介
typedef int (*tSSIHandler)(int iIndex, char *pcInsert, int iInsertLen)这个函数除了这三个参数外,还有其他三个可选的参数,这里没写出来,具体怎么用交给大家研究啦。
参数说明:
iIndex:在ppcTags数组中的索引号,ppcTags是一个字符串数组,用来标记tag名字的,比如<!--#name-->,“name”就是一个tag名
pcInsert:在tag后面插入的字符串
iInsertLen:在tag后面插入字符串的长度

F.SSI应用举例
1.新建一个index.shtml的文件,在文件中输入一下代码:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><title>welcom</title><meta http-equiv="Content-Type" content="text/html; charset=gb2312" /><meta   http-equiv= "Cache-Control"   content= "no-cache" /> </head><body><form id="login" name="login" method="get" action="login.cgi"><table width="200" border="1" align="center"><tr><td colspan="2" align="center">请按F5进行刷新</td></tr>
<tr><td width="50">内容:</td><td width="134"><label><!--#alientek--></label></td></tr></table></form></body></html>注意到上面有一个 <!--#alientek--> 的TAG,,利用LWIP的SSI的功能可以将我们想插入的字符串插入到后面。

2.编写SSI处理函数
static const char *ppcTags[] =  //TAG数组{    " alientek ",                 };enum ssi_index_s        //索引号{    SSI_INDEX_ALIENTEK_GET = 0, //该表对应ppcTags[]的排序} ;
static int SSIHandler ( int iIndex, char *pcInsert, int iInsertLen ){       switch(iIndex)    {        case  SSI_INDEX_ALIENTEK_GET:
           pcInsert = "alentek";
           iInsertLen = strlen(pcInsert );            break;               default:            strcpy( pcInsert , "" );                }    return  iInsertLen ;  //返回字符串的长度}

 

3.再编写一个返回URL为/index.shtml的文件的CGI函数就可以了。在页面中就可以看到alientek内容了。

最后重点介绍下ajax做异步提交,更新数据:
一、如何将静态页面转换为16进制的c语言数组?
前面说了这么多都没提这个重要的问题,希望大家别见怪,嘿嘿。
这里我谢谢 zhangpisces网友提供的资料。他的资料给了我很大的参考价值,喝水不忘,挖井人。 
步骤:
1.首先将网页源文件编写好,如工程中atk文件夹下的文件。2.将makefsfile工具和atk放在一个文件夹内.3.运行cmd,进入到makefsfile工具的目录。4.使用makefsfile -i  atk  -o fsdata.h -r -h 命令生成一个 fsdata.h文件
注意:在fs.c文件中需要注释掉//#include "fsdata.c"这个,我们不用。

二,添加/response.ssi文件
#define RESPONSE_BUF_SIZE 512   //http响应缓存大小
unsigned char data_response_ssi[RESPONSE_BUF_SIZE+14] ={ /* /response.ssi */ 0x2F, 0x72, 0x65, 0x73, 0x70, 0x6F, 0x6E, 0x73,  0x65, 0x2E, 0x73, 0x73, 0x69, 0x00, };struct fsdata_file file_response_ssi[] ={ { NULL, data_response_ssi, data_response_ssi + 14, sizeof(data_response_ssi) - 14 }};makefsfile 生成的都是ROM文件,如果我们想要改变返回给浏览器的响应内容必须定义一个可以变化的文件。
在CGI函数中我们就可以把想发送的数据写入到这个文件当中,return "/response.ssi"就OK了。
三、编写基于ajax的JS代码
我把战舰原来uip的web例程(不过这个例程并没有用到SSI,是综合例程)改成了ajax异步获取数据,参考JS源码如下:
<script type="text/javascript">var xmlhttp;function loadXMLDoc(url,cfunc){  if (window.XMLHttpRequest)  {     xmlhttp=new XMLHttpRequest();  }  else  {     xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");  }  xmlhttp.onreadystatechange=cfunc;  xmlhttp.open("GET",url,true);  xmlhttp.send();}
function led0(){  loadXMLDoc("/led_red.cgi?red=1&t="+ Math.random(),function()  {    if (xmlhttp.readyState==4 && xmlhttp.status==200)    { document.getElementById("red").src=xmlhttp.responseText;    }  });}
function led1(){  loadXMLDoc("/led_green.cgi?green=1&t="+ Math.random(),function()  {    if (xmlhttp.readyState==4 && xmlhttp.status==200)    {  document.getElementById("green").src=xmlhttp.responseText;    }  });}var text;
function update(){   loadXMLDoc("/orther.cgi?t="+ Math.random(),function()  {    if (xmlhttp.readyState==4 && xmlhttp.status==200)    {  text = xmlhttp.responseText;  text = text.split(";");  document.getElementById("temperature").innerHTML=text[0]+"℃";  document.getElementById("time").innerHTML=text[1];
    }  });
}function init(){ setInterval(update,1000);  }
</script>

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
到现在为止基本上讲完了,第一次发这么长的帖子,各位看官慢慢看。有错误请多多包容哦^_^
附件中还有战舰的UIP补充例程,添加了UDP的服务器和客户端模式,web没怎么搞。
还有LWIP例程。包括了TCP服务器,TCP客户端,UDP服务器,UDP客户端,WEBSEVER。这些功能,配合我们的ALIENTEK的ENC28J60模块就可以
直接在战舰开发板上运行了。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
LWIP例程说明:
KEY_UP:选择实验(TCP服务器,TCP客户端,UDP服务器,UDP客户端,WEBSEVER)KEY_DOWN:发送数据
建议大家先学习UIP,然后再学LWIP可能效果要好些,毕竟UIP还是要简单得多。

<ignore_js_op>

建立连接 .jpg (65.03 KB, 下载次数: 1761)

<ignore_js_op>

发送数据包.jpg (21.27 KB, 下载次数: 1298)

<ignore_js_op>

服务器关闭连接.jpg (31.6 KB, 下载次数: 1103)

<ignore_js_op>

makefsfile.rar

20.68 KB, 下载次数: 4854

<ignore_js_op>

atk.rar

53.25 KB, 下载次数: 4647

<ignore_js_op>

ALIENTEK ENC28J60 UIP.rar

494.66 KB, 下载次数: 7081

<ignore_js_op>

ALIENTEK ENC28J60 LWIP.rar

转载自:http://www.openedv.com/forum.php?mod=viewthread&tid=25178&extra=&page=1

ALIENTEK 战舰ENC28J60 LWIP和UIP补充例程(LWIP WEB有惊喜)的更多相关文章

  1. 学习随笔:Django 补充及常见Web攻击 和 ueditor

    判断用户是否登录 <!-- xxx.html --> {% if request.user.is_authenticated %} django中的request对象详解 填错表格返回上次 ...

  2. 开始逐步补充下相关Web知识,很多年没搞了....

    <script type="text/javascript"> $(function(){ ShowProduct(); $("#ShowUserInfo&q ...

  3. 移植LWIP(ENC28J60)

       上图就是整个移植的基本思路,非常清晰的三个层次.其实想想,本质上就是收发数据,只是LWIP协议通过对数据的封装可以实现网络传输.从图中我们就可以看到这里首先需要ENC28J60的驱动,这个驱动需 ...

  4. ETH—Lwip以太网通信

    第39章     ETH—Lwip以太网通信 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/ ...

  5. 第39章 ETH—Lwip以太网通信

    第39章     ETH—Lwip以太网通信 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/ ...

  6. ENC28J60学习笔记——第1部分

    1前言 嵌入式以太网开发,可以分为两个部分,一个是以太网收发芯片的使用,一个是嵌入式以太网协议栈的实现.以太网收发芯片的使用要比串口收发芯片的使用复杂的多,市面上流通比较广泛的以太网收发芯片种类还不少 ...

  7. 第39章 ETH—Lwip以太网通信—零死角玩转STM32-F429系列

    第39章     ETH—Lwip以太网通信 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/ ...

  8. LWIP互联网资料汇总

    本文主要搜集了下互联网上关于LWIP的资料和教程 欢迎补充 第一部分:移植 LWIP在UCOS上移植 LWIP 在STM32上移植   http://www.docin.com/p-459242028 ...

  9. 基于 LWIP 建立 TCP Server 与主机通信实验

    LWIP 版本:2.0.3 上一篇文章是写如何将 LWIP 移植到板子上,今天晚上记录基于 LWIP 实现与主机的网络通信. 先是打开了原子的实验例程,大概浏览了一遍,觉得 TCP 网络网络通信也就是 ...

随机推荐

  1. JAVA实验报告及第七周总结

    JAVA第六周作业 实验报告五 第一题 1.设计一个类层次,定义一个抽象类--形状,其中包括有求形状的面积的抽象方法. 继承该抽象类定义三角型.矩形.圆. 分别创建一个三角形.矩形.圆存对象,将各类图 ...

  2. XOR Guessing(交互题+思维)Educational Codeforces Round 71 (Rated for Div. 2)

    题意:https://codeforc.es/contest/1207/problem/E 答案guessing(0~2^14-1) 有两次机会,内次必须输出不同的100个数,每次系统会随机挑一个你给 ...

  3. F. 汤圆防漏理论

    ghc很喜欢吃汤圆,但是汤圆很容易被粘(zhān)漏. 根据多年吃汤圆经验,ghc总结出了一套汤圆防漏理论: 互相接触的汤圆容易粘(zhān)在一起,并且接触面积不同,粘(zhān)在一起的粘(niá ...

  4. Mysql-Sqlalchemy-ORM-多外键关联

    创建表结构:orm_many_fk.py from sqlalchemy import Integer, ForeignKey, String, Column,create_engine from s ...

  5. Go语言GOMAXPROCS(调整并发的运行性能)

    在 Go语言程序运行时(runtime)实现了一个小型的任务调度器.这套调度器的工作原理类似于操作系统调度线程,Go 程序调度器可以高效地将 CPU 资源分配给每一个任务.传统逻辑中,开发者需要维护线 ...

  6. X86逆向9:通过关键常量破解

    本章将讲解一下关于关键全局变量的一些内容,关键的全局变量对于软件的破解非常的有用,找到了关键全局变量并改写它同样可以完成完美爆破一个程序,这里我将使用CM小例子来讲解搜索关键变量的一些技巧,最后我们来 ...

  7. 2 - sat 模板(自用)

    2-sat一个变量两种状态符合条件的状态建边找强连通,两两成立1 - n 为第一状态(n + 1) - (n + n) 为第二状态 例题模板 链接一  POJ 3207 Ikki's Story IV ...

  8. B2C电商平台开发心得(asp.net+bootstrap)

    Bootstrap,来自 Twitter,是目前最受欢迎的前端框架.Bootstrap 是基于 html.css.javascript的,专为 web 应用设计,包含了移动设备优先的样式, 其响应式 ...

  9. GTA4 EFLC cheat code

    GTA4 EFLC cheat code 提示警告:您的图像设置接近或超出您的系统推荐资源限制,为了使游戏运行更加流畅推荐你降低你的图像设置. 在游戏目录新建名为 commandline的txt文本文 ...

  10. linux mint ubuntu 安装virtualbox

    安装虚拟机:virtualbox 1.打开终端而且切换到root帐号,然后输入安装命令: apt-get install virtualbox 2.安装推荐的软件包:(必须安装这个包.不然看不到应用程 ...