<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; FAQ</title>
	<atom:link href="https://blog.gmem.cc/tag/faq/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Tue, 21 Apr 2026 10:40:56 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>Framework7知识集锦</title>
		<link>https://blog.gmem.cc/framework7-faq</link>
		<comments>https://blog.gmem.cc/framework7-faq#comments</comments>
		<pubDate>Fri, 19 Feb 2016 08:39:12 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[F7]]></category>
		<category><![CDATA[FAQ]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11067</guid>
		<description><![CDATA[<p>常见问题 注意点 从页面A导航到B，导航后，A的DOM保留；回退后，B的DOM默认移除 页面回调、事件中均可以访问页面数据，这意味着页面已经解析完毕，如果使用Template7模板，此时模板已经转换为目标HTML。因此尝试在页面回调、事件中为Template7模板提供上下文，均是不可取的 在preprocess回调中，不能进行路由，会导致错误 通过data-context指定页面上下文无效 导航到子页面时报错：Uncaught SyntaxError: Unexpected token 原因：F7要求规范化的JSON格式，所有对象属性必须用双引号包围。</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/framework7-faq">Framework7知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">注意点</span></div>
<ol>
<li>从页面A导航到B，导航后，A的DOM保留；回退后，B的DOM默认移除</li>
<li>页面回调、事件中均可以访问页面数据，这意味着页面已经解析完毕，如果使用Template7模板，此时模板已经转换为目标HTML。因此尝试在页面回调、事件中为Template7模板提供上下文，均是不可取的</li>
<li>在preprocess回调中，不能进行路由，会导致错误</li>
</ol>
<div class="blog_h3"><span class="graybg">通过data-context指定页面上下文无效</span></div>
<p>导航到子页面时报错：Uncaught SyntaxError: Unexpected token</p>
<p>原因：F7要求规范化的JSON格式，所有对象属性必须用双引号包围。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/framework7-faq">Framework7知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/framework7-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Tomcat知识集锦</title>
		<link>https://blog.gmem.cc/tomcat-faq</link>
		<comments>https://blog.gmem.cc/tomcat-faq#comments</comments>
		<pubDate>Fri, 17 Oct 2014 09:17:31 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[Tomcat]]></category>
		<category><![CDATA[性能调优]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1944</guid>
		<description><![CDATA[<p>常见问题 零散问题 One or more listeners failed to start 报错信息： Artifact pems-web-manager:war exploded: Error during artifact deployment. See server log for details. <a class="read-more" href="https://blog.gmem.cc/tomcat-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/tomcat-faq">Tomcat知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">One or more listeners failed to start</span></div>
<p>报错信息：</p>
<p>Artifact pems-web-manager:war exploded: Error during artifact deployment. See server log for details.</p>
<p>org.apache.catalina.core.StandardContext.startInternal One or more listeners failed to start. Full details will be found in the appropriate container log file</p>
<p>报错原因：某个Web应用的Servlet监听器配置出现问题（例如监听器类找不到），上面的出错信息不明确，检查web.xml即可。<br /> </p>
<div class="blog_h1"><span class="graybg">Tomcat7生产环境配置</span></div>
<div class="blog_h2"><span class="graybg">环境准备</span></div>
<div class="blog_h3"><span class="graybg">下载软件</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 80px; text-align: center;">软件</td>
<td style="text-align: center;">版本</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Tomcat</td>
<td style="width: 80px;">7.0.56</td>
<td>
<p>如果是64位Windows，可以下载：64-bit Windows zip</p>
<p>下载地址：<a href="http://tomcat.apache.org/download-70.cgi#7.0.56">http://tomcat.apache.org/download-70.cgi#7.0.56</a></p>
</td>
</tr>
<tr>
<td>JRE</td>
<td>7u71</td>
<td>
<p>如果是64位Windows，可以下载：Windows x64的tar.gz版本</p>
<p>下载地址：<a href="http://www.oracle.com/technetwork/cn/java/javase/downloads/jre7-downloads-1880261.html">http://www.oracle.com/technetwork/cn/java/javase/downloads/jre7-downloads-1880261.html</a></p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">安装软件</span></div>
<ol>
<li>解压Tomcat到某个位置，例如：D:\JavaEE\production\Tomcat7，确认Tomcat7\bin目录存在</li>
<li>解压JRE到%TOMCAT_HOME%\jre，确认%TOMCAT_HOME%\jre\bin目录存在</li>
</ol>
<div class="blog_h2"><span class="graybg">Tomcat配置</span></div>
<div class="blog_h3"><span class="graybg">使用%TOMCAT_HOME%\jre作为Java运行环境</span></div>
<ol>
<li>在%TOMCAT_HOME%下建立文件startup.bat，内容类似下面：<br />
<pre class="crayon-plain-tag">@echo off
echo *******************************************************************************
echo Portable Tomcat 7 by Alex Wang
echo.
pushd "%~dp0"
for /f "tokens=1-3 delims=/ " %%a in ('date /t') do (set md=%%a-%%b-%%c)
for /f "tokens=1-3 delims=/:." %%a in ("%TIME%") do (set mt=%%a%%b%%c)
set CUR_TIME=%md%-%mt%

set "JAVA_HOME="
set "CATALINA_HOME=%CD%"
set "JRE_HOME=%CATALINA_HOME%\jre"
set "LOG_DIR=%CATALINA_HOME%\logs"
rem 请根据服务器硬件配置、系统的特性填写下面一行
rem 响应时间优先配置示例：-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:ParallelGCThreads=8 -XX:MaxGCPauseMillis=500
rem 吞吐量优先的配置示例：-XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=8 -Xmn1280M
set JAVA_OPTS=-server -Xms2G -Xmx2G -Xss128K -XX:MaxPermSize=256M
rem 启用错误记录
set JAVA_OPTS=%JAVA_OPTS% -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=%LOG_DIR%\ -XX:ErrorFile=%LOG_DIR%\hs_err_pid%%p.log
rem 启用JMX远程管理和黑匣子
set JAVA_OPTS=%JAVA_OPTS% -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=7777 -Dcom.sun.management.jmxremote -XX:+UnlockCommercialFeatures -XX:+FlightRecorder
rem 启用黑匣子默认记录行为
set JAVA_OPTS=%JAVA_OPTS% -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=%LOG_DIR%\jfr\dump-on-exit-%CUR_TIME%.jfr,maxage=3600s,settings=%JRE_HOME%\lib\jfr\profile.jfc
rem 记录GC详细日志
set JAVA_OPTS=%JAVA_OPTS% -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:%LOG_DIR%\gc.log
rem 启用远程调试
set JAVA_OPTS=%JAVA_OPTS% -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000

echo Setting CATALINA_HOME %CATALINA_HOME%
echo Setting JRE_HOME %JRE_HOME%
echo Setting JAVA_OPTS %JAVA_OPTS%
echo.
echo Launching Tomcat 7 startup.bat...
echo *******************************************************************************
echo.
call bin\startup.bat
popd</pre>
</li>
<li>类似的，编写一个shutdown.bat</li>
</ol>
<div class="blog_h3"><span class="graybg">NT服务配置</span></div>
<p>如果不想使用脚本启动（上一段的方式），可以把Tomcat配置为NT服务，把下面的脚本存放到%TOMCAT_HOME%，传入服务名、显示名、服务描述三个参数即可安装服务，内存配置需要根据机器的性能进行调整：</p>
<pre class="crayon-plain-tag">@echo off
pushd "%~dp0"
setlocal

set SERVICE_NAME=%~1
set DISPLAY_NAME=%~2
set SERVICE_DESC=%~3

set "CATALINA_HOME=%CD%"
set "EXECUTABLE=%CATALINA_HOME%\bin\tomcat7.exe"
set "CATALINA_BASE=%CATALINA_HOME%"
set "JRE_HOME=%CATALINA_HOME%\jre"
set "LOG_DIR=%CATALINA_HOME%\logs"
set "CLASSPATH=%CATALINA_HOME%\bin\bootstrap.jar;%CATALINA_BASE%\bin\tomcat-juli.jar"
rem 请根据服务器硬件配置、系统的特性填写下面一行
rem 响应时间优先配置示例：-XX:MaxPermSize=256M;-XX:+UseConcMarkSweepGC;-XX:+UseParNewGC;-XX:ParallelGCThreads=8;-XX:MaxGCPauseMillis=500;-XX:+UseParNewGC;
rem 吞吐量优先的配置示例：-XX:+UseParallelGC;-XX:+UseParallelOldGC;-XX:ParallelGCThreads=8;-Xmn1280M;
set "JVM_MEM_GC_OPTS=-XX:MaxPermSize=256M;"

rem 启用JMX远程管理和黑匣子
set "JMX_OPTS=-Dcom.sun.management.jmxremote.authenticate=false;-Dcom.sun.management.jmxremote.ssl=false;-Dcom.sun.management.jmxremote.port=7777;-Dcom.sun.management.jmxremote;-XX:+UnlockCommercialFeatures;-XX:+FlightRecorder;"
rem 启用错误记录
set "JVM_ERR_OPTS=-XX:+HeapDumpOnOutOfMemoryError;-XX:HeapDumpPath=%LOG_DIR%\;-XX:ErrorFile=%LOG_DIR%\hs_err_pid%%p.log;"
set "JFR_AUTO_RCD=-XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=%LOG_DIR%\jfr\dump-on-exit-%SERVICE_NAME%.jfr,maxage=3600s,settings=%JRE_HOME%\lib\jfr\profile.jfc;"

.\bin\tomcat7 //IS//%SERVICE_NAME% --Startup=auto ^
--DisplayName="%DISPLAY_NAME%" --Description "%SERVICE_DESC%" ^
--Jvm="%CATALINA_HOME%\jre\bin\server\jvm.dll" ^
--Classpath="%CLASSPATH%" ^
--LogPath "%CATALINA_HOME%\logs\services\%SERVICE_NAME%" --LogLevel=Info --StdOutput auto --StdError auto ^
--StartMode=jvm --StartPath "%CATALINA_HOME%" --StartClass=org.apache.catalina.startup.Bootstrap --StartParams=start ^
--StopMode=jvm --StopPath "%CATALINA_HOME%" --StopClass=org.apache.catalina.startup.Bootstrap --StopParams=stop ^
--JvmMs=2048 --JvmMx=2048 --JvmSs=128 ^
++JvmOptions "%JVM_MEM_GC_OPTS%-Dcatalina.home=%CATALINA_HOME%;-Dcatalina.base=%CATALINA_BASE%;-Djava.endorsed.dirs=%CATALINA_HOME%\endorsed;-Djava.io.tmpdir=%CATALINA_BASE%\temp;-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager;-Djava.util.logging.config.file=%CATALINA_BASE%\conf\logging.properties;%JMX_OPTS%%JVM_ERR_OPTS%%JFR_AUTO_RCD%"

popd
endlocal</pre>
<div class="blog_h3"><span class="graybg">优化Tomcat配置参数（基于Java NIO）</span></div>
<p>参考下面的代码编辑%TOMCAT_HOME%\conf\server.xml</p>
<pre class="crayon-plain-tag">&lt;!-- 注释掉 AJP --&gt;
&lt;!--&lt;Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /&gt; --&gt;
&lt;!-- 使用Java NIO优化性能，注意删除原Connector元素的同名属性，线程数和缓存依据实际需要调整 --&gt;
&lt;Connector port="8080" redirectPort="8443" protocol="org.apache.coyote.http11.Http11NioProtocol" 
           connectionTimeout="20000" 
           URIEncoding="UTF-8" 
           useBodyEncodingForURI="true" 
           maxThreads="1024" 
           minSpareThreads="128"
           socket.appReadBufSize="1024" 
           socket.appWriteBufSize="1024"
/&gt;</pre>
<div class="blog_h2"><span class="graybg">Linux下的脚本示例（CentOS 6.3）</span></div>
<pre class="crayon-plain-tag">#!/bin/bash
PDTDIR=$(dirname $0)
export CUR_TIME=`date "+%Y%m%d%H%M%S"`
export JAVA_HOME=
export CATALINA_HOME=$PDTDIR
export JRE_HOME=$CATALINA_HOME/jre
export LOG_DIR=$CATALINA_HOME/logs

# 请根据服务器硬件配置、系统的特性填写下面一行
# 响应时间优先配置示例：-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:ParallelGCThreads=8 -XX:MaxGCPauseMillis=500
# 吞吐量优先的配置示例：-XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=8 -Xmn1280M
export JAVA_OPTS="-server -Xms2G -Xmx2G -Xss256K -XX:MaxPermSize=256M"
# 启用错误记录
export JAVA_OPTS="$JAVA_OPTS -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$LOG_DIR/ -XX:ErrorFile=$LOG_DIR/hs_err_pid%p.log"
# 启用JMX远程管理和黑匣子
export JAVA_OPTS="$JAVA_OPTS -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.port=7777 -Dcom.sun.management.jmxremote -XX:+UnlockCommercialFeatures -XX:+FlightRecorder"
# 启用黑匣子默认记录行为
export JAVA_OPTS="$JAVA_OPTS -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=$LOG_DIR/jfr/dump-on-exit-$CUR_TIME.jfr,maxage=3600s,settings=$JRE_HOME/lib/jfr/profile.jfc"
#rem 记录GC详细日志
export JAVA_OPTS="$JAVA_OPTS -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:$LOG_DIR/gc.log"

echo
echo "*******************************************************************************"
echo "    Portable Tomcat 7 for Linux  by Alex Wang  (Last updated: May 7th, 2015 )  "
echo "Setting CATALINA_HOME       $CATALINA_HOME"
echo "Setting JRE_HOME            $JRE_HOME"
echo "Setting JAVA_OPTS           $JAVA_OPTS"
echo
echo "Launching Tomcat 7 startup.bat...                                              "
echo "*******************************************************************************"
echo
exec "$PDTDIR"/bin/startup.sh</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/tomcat-faq">Tomcat知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/tomcat-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>NaCl学习笔记</title>
		<link>https://blog.gmem.cc/chrome-native-client-study-note</link>
		<comments>https://blog.gmem.cc/chrome-native-client-study-note#comments</comments>
		<pubDate>Tue, 30 Sep 2014 06:33:48 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[Chrome]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[NaCl]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=3650</guid>
		<description><![CDATA[<p>基础知识 Chrome Chrome是一个多进程（multi-process）架构的浏览器，相比起Firefox之类的单进程浏览器，其提供额外的完全特性。 Chrome包含一个称为“browser”的主进程，其负责运行UI界面、管理标签页（Tabs）和插件进程，具有当前用户所有的访问权限（文件、网络等），并且能够fork出子进程。 标签页被分配到单独的“renderer”进程中，通常每个domain会共享一个进程。展示进程运行在沙盒（Sandbox）中，不能打开文件、网络。展示进程负责渲染HTML布局、处理页面展示。 插件 插件指外部的二进制文件，插件为浏览器添加额外的功能。插件可以随着浏览器一起安装，例如 Adobe Flash、Adobe Reader，也可以是用户自行安装的。一般来说，现有的大部分插件由于需要网络、文件系统的访问权限，而不能在沙盒内运行，因此Chrome提供了进程外插件机制（out of process plugins），在独立进程中以完整访问权限运行插件，并且使用进程间通信（IPC）机制来与browser、renderer进程通信。 Chrome也支持进程内插件（in process plugins），其运行于renderer进程内部，不需要使用IPC机制进行通信。 Netscape Plugin API (NPAPI) NPAPI是一个跨浏览器的插件框架，除了微软使用ActiveX技术以外，其他主要浏览器均支持。Chrome使用进程外插件的方式来支持NPAPI。Chrome 目前已经不再支持NPAPI。 <a class="read-more" href="https://blog.gmem.cc/chrome-native-client-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/chrome-native-client-study-note">NaCl学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">基础知识</span></div>
<div class="blog_h3"><span class="graybg">Chrome</span></div>
<p>Chrome是一个多进程（multi-process）架构的浏览器，相比起Firefox之类的单进程浏览器，其提供额外的完全特性。 Chrome包含一个称为“browser”的主进程，其负责运行UI界面、管理标签页（Tabs）和插件进程，具有当前用户所有的访问权限（文件、网络等），并且能够fork出子进程。</p>
<p>标签页被分配到单独的“renderer”进程中，通常每个domain会共享一个进程。展示进程运行在沙盒（Sandbox）中，不能打开文件、网络。展示进程负责渲染HTML布局、处理页面展示。</p>
<div class="blog_h3"><span class="graybg">插件</span></div>
<p style="text-align: justify;">插件指外部的二进制文件，插件为浏览器添加额外的功能。插件可以随着浏览器一起安装，例如 Adobe Flash、Adobe Reader，也可以是用户自行安装的。一般来说，现有的大部分插件由于需要网络、文件系统的访问权限，而不能在沙盒内运行，因此Chrome提供了进程外插件机制（out of process plugins），在独立进程中以完整访问权限运行插件，并且使用进程间通信（IPC）机制来与browser、renderer进程通信。</p>
<p style="text-align: justify;">Chrome也支持进程内插件（in process plugins），其运行于renderer进程内部，不需要使用IPC机制进行通信。</p>
<div class="blog_h3"><span class="graybg">Netscape Plugin API (NPAPI) </span></div>
<p>NPAPI是一个跨浏览器的插件框架，除了微软使用ActiveX技术以外，其他主要浏览器均支持。Chrome使用进程外插件的方式来支持NPAPI。Chrome 目前已经不再支持NPAPI。</p>
<div class="blog_h3"><span class="graybg">Pepper Plugin API (PPAPI)</span></div>
<p>PPAPI是谷歌提出的开源、跨平台插件框架，其主要目的是解决NPAPI的性能、可移植性问题。PPAPI的初期设计曾考虑尽可能保持与NPAPI的接口风格一致性，但是这一设计原则逐渐被放弃。</p>
<p>PPAPI允许C/C++模块与浏览器交互、以安全可移植方式来访问系统级函数。PPAPI不支持操作系统调用，但是它提供了类似的API。</p>
<p>PPAPI支持以下操作：</p>
<ol>
<li>与JavaScript的双向通信</li>
<li>进行文件I/O操作</li>
<li>播放音频</li>
<li>渲染3D图形</li>
</ol>
<p>Google没有提供自动化安装第三方的PPAPI插件的方式。使用类似下面的命令行参数启动Chrome，可以手工加载PPAPI插件：</p>
<pre class="crayon-plain-tag">chrome --disable-sandbox --register-pepper-plugins="&lt;plugin_dll_path&gt;;mime/type"</pre>
<div class="blog_h2"><span class="graybg">NaCl技术概览</span></div>
<p>NaCl是一种在浏览器中安全执行平台无关的、不受信任代码的开源沙盒技术，它允许计算密集型、交互性的实时Web应用有效的利用机器的物理性能，并同时保证安全性。<span style="background-color: #c0c0c0;">NaCl本质上是一个进程内PPAPI插件</span>。</p>
<p>一个NaCl应用程序包含：JavaScript、HTML、CSS、以及一个NaCl模块。目前NaCl SDK支持的语言主要是C和C++。</p>
<p>NaCl SDK是用于开发NaCl可执行文件（nexe）的工具包。由一系列GNU工具链组成，包括 gcc, binutils、gdb等。</p>
<p>Portable Native Client(PNacl)则是在NaCl的基础上支持硬件架构的无关性，可以在任何支持AOT（ahead-of-time）的平台下运行，必须在Chrome 31+才能支持。Chrome负责把pexe格式的中间代码翻译成客户端本地代码。PNacl可以在<span style="background-color: #c0c0c0;">不依赖Google Web Store的情况下进行部署</span>。在Chrome://flags中开启Native Client标记可以运行任意NaCl。</p>
<p>应当尽可能使用PNacl，而不是NaCl，除非：</p>
<ol>
<li>程序需要平台相关的指令，例如内联汇编</li>
<li>程序使用动态链接，PNacl仅支持与newlib C标准库的naclport静态链接，对glibc的静态链接、以及动态链接尚不支持</li>
<li>程序使用了某些PNaCl的LLVM工具链所不支持的GNU扩展</li>
</ol>
<p>&nbsp;</p>
<div class="blog_h3"><span class="graybg">NaCl工作机制示意图</span></div>
<p><img class="aligncenter  wp-image-3662" src="https://blog.gmem.cc/wp-content/uploads/2014/09/nacl_diagram.png" alt="nacl_diagram" width="564" height="677" /></p>
<p><img class="aligncenter  wp-image-3669" src="https://blog.gmem.cc/wp-content/uploads/2014/09/nacl-pnacl-component-diagram.png" alt="nacl-pnacl-component-diagram" width="526" height="471" /></p>
<div class="blog_h3"><span class="graybg">NaCl工具链</span></div>
<ol>
<li>PNaCl工具链：基于LLVM工具链。使用newlib库，生成单个pexe文件，在运行时，浏览器内置的AOT转换器负责将其转换为本地代码</li>
<li>NaCl工具链：基于GCC，包含x86_newlib、x86_glibc、arm_newlib三个工具链，生成多个架构相关的nexe文件，并打包到一个应用，浏览器负责决定使用哪个nexe</li>
</ol>
<p>不能在一个应用里混合使用多个工具链。下表列出这些工具链使用的C标准库、目录名称，其中platform可能是win或者linux</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">目标架构 </td>
<td style="text-align: center;">C标准库</td>
<td style="text-align: center;">工具链目录 </td>
</tr>
<tr>
<td style="text-align: center;">x86</td>
<td style="text-align: center;">newlib</td>
<td style="text-align: left;">toolchain/&lt;platform&gt;_x86_newlib</td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;">x86</td>
<td style="text-align: center;">glibc</td>
<td>toolchain/&lt;platform&gt;_x86_glibc</td>
</tr>
<tr>
<td style="text-align: center;">ARM</td>
<td style="text-align: center;">newlib</td>
<td>toolchain/&lt;platform&gt;_arm_newlib</td>
</tr>
<tr>
<td style="text-align: center;">PNaCl</td>
<td style="text-align: center;">newlib</td>
<td>toolchain/&lt;platform&gt;_pnacl</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<div class="blog_h3"><span class="graybg">NaCl的安全性</span></div>
<ol>
<li>NaCl沙盒确保代码只能通过安全的、白名单中的API来访问系统资源，并且确保代码不能干扰其它浏览器内外代码的运行</li>
<li>NaCl验证器在运行前静态分析代码，确保只包含允许的代码和数据图式（pattern）</li>
</ol>
<div class="blog_h3"><span class="graybg">基于NaCl的Web应用的结构</span></div>
<p>至少包含HTML代码、Manifest文件和NaCl模块3部分内容。</p>
<p>HTML代码中需要包含embed标签，用于定位manifest文件：</p>
<pre class="crayon-plain-tag">&lt;embed width="300" height="150" type="application/x-pnacl" src="helloworld.nmf" name="mygame"&gt;
&lt;/embed&gt;</pre>
<p>Manifest文件以nmf为扩展名，定义了需要加载的NaCl模块以及选项，形式如下：</p>
<pre class="crayon-plain-tag">{
  "url": "helloworld.pexe"
}</pre>
<p>NaCl模块是编译好的pexe文件，其使用Pepper API作为本地代码与JavaScript、其它浏览器资源的桥梁。</p>
<div class="blog_h2"><span class="graybg">NaCl的劣势以及其替代技术</span></div>
<p>NaCl具有以下缺点：</p>
<ol>
<li>不能直接访问硬件，相比之下WebRTC之类的技术允许访问音视频捕获设备</li>
<li>没有完整的OpenGL特性支持</li>
<li>不允许原始TCP/UDP</li>
<li>其它浏览器厂商对其缺乏兴趣</li>
<li>不支持同步化的JavaScript调用，所有NaCl与JavaScript的交互均是异步的</li>
</ol>
<div class="blog_h3"><span class="graybg">替代技术</span></div>
<p><span style="background-color: #c0c0c0;"><strong>PPAPI (without NaCL) </strong></span></p>
<p>本文上面已经提到过，PPAPI插件无法方便的安装，只能使用命令行参数</p>
<p><span style="background-color: #c0c0c0;"><strong>Google Native Messaging</strong></span></p>
<p>该技术允许Chrome插件与本地程序进行消息交互。</p>
<p>优势：</p>
<ol>
<li>支持运行本地代码，可以进行完整的硬件访问、原生TCP/UDP访问</li>
<li>支持从Chrome启动本地程序</li>
</ol>
<p>劣势：</p>
<ol>
<li>两个单独的安装程序：本地程序的安装、Chrome扩展的安装</li>
<li>难以获取视频数据返回给浏览器，不适合密集的数据传输</li>
</ol>
<div class="blog_h2"><span class="graybg">NaCl SDK安装步骤</span></div>
<div class="blog_h3"><span class="graybg">Windows</span></div>
<ol>
<li>到https://developer.chrome.com/native-client/sdk/download下载SDK</li>
<li>解压到D:\CPP\tools\Chromium\nacl_sdk</li>
<li>运行以下脚本：<br />
<pre class="crayon-plain-tag">cd nacl_sdk
naclsdk update --force</pre>
</li>
<li>安装Python 2.7.x，并添加到PATH环境变量</li>
</ol>
<div class="blog_h3"><span class="graybg">Linux</span></div>
<ol>
<li>下载SDK并解压到~/CPP/tools/Chromium/nacl_sdk</li>
<li>运行脚本：<br />
<pre class="crayon-plain-tag">sudo apt-get install libc6:i386

cd ~/CPP/tools/Chromium/nacl_sdk
./naclsdk update

vim ~/.bashrc
#添加以下内容
export PATH="$PATH":/home/alex/CPP/tools/Chromium/depot_tools
export NACL_SDK_ROOT=/home/alex/Chromium/nacl_sdk/pepper_39</pre>
</li>
<li>如果没有安装Python 2.x，则apt-get安装</li>
</ol>
<div class="blog_h2"><span class="graybg">NaCl HelloWorld</span></div>
<div class="blog_h3"><span class="graybg">开发环境准备</span></div>
<p><strong><span style="background-color: #c0c0c0;">环境变量：</span></strong></p>
<p>NACL_SDK_ROOT=D:\CPP\tools\nacl_sdk\pepper_39</p>
<p>如果安装了Cygwin，需要把Cygwin的bin目录从PATH中排除掉（可以通过Eclipse工程右键，Properties - C/C++ Build Environment设置），否则构建时可能收到类似“/x86_64-nacl-ld: cannot find -lppapi_gles2”的错误</p>
<p><strong><span style="background-color: #c0c0c0;">IDE设置（Eclipse CDT）：</span></strong></p>
<ol>
<li>File - New - Makefile Project with existing Code，工程名称设置为nacl-helloworld，Existing Code Location定位到准备存放代码的目录，Toolchain for Indexer Settings选择Cross GCC</li>
<li>工程点右键 - Properties - C/C++ General - Paths and Symbols - Source Location，删除默认，与Makefile设置保持一致</li>
<li>工程点右键 - Properties - C/C++ General - Paths and Symbols - Output Location，删除默认，添加/project-root/***，***可以是glibc、newlib、pnacl等，根据Makefile中设置的工具链（VALID_TOOLCHAINS）来设置</li>
<li>工程点右键 - Properties - C/C++ General - Paths and Symbols - Includes - GNU C++，添加：${NACL_SDK_ROOT}/include、${NACL_SDK_ROOT}/toolchain/win_***/x86-64_bc-nacl/include，根据Makefile中设置的工具链（VALID_TOOLCHAINS）来设置</li>
<li>工程点右键 - Properties - C/C++ Build，取消勾选“Use default build command”，设置为make.bat</li>
<li>工程点右键 - Make Targets - Create，创建名为serve的构建目标</li>
</ol>
<div class="blog_h3"><span class="graybg">源代码列表</span></div>
<pre class="crayon-plain-tag">@%NACL_SDK_ROOT%\tools\make.exe %*</pre><br />
<pre class="crayon-plain-tag">CONFIG = Debug

VALID_TOOLCHAINS := newlib glibc pnacl win

NACL_SDK_ROOT ?= $(abspath $(CURDIR)/../../..)

include $(NACL_SDK_ROOT)/tools/common.mk

TARGET = hello_world
LIBS = ppapi_cpp ppapi

CFLAGS = -Wall
SOURCES = hello_world.cc

$(foreach src,$(SOURCES),$(eval $(call COMPILE_RULE,$(src),$(CFLAGS))))

ifneq (,$(or $(findstring pnacl,$(TOOLCHAIN)),$(findstring Release,$(CONFIG))))
$(eval $(call LINK_RULE,$(TARGET)_unstripped,$(SOURCES),$(LIBS),$(DEPS)))
$(eval $(call STRIP_RULE,$(TARGET),$(TARGET)_unstripped))
else
$(eval $(call LINK_RULE,$(TARGET),$(SOURCES),$(LIBS),$(DEPS)))
endif

$(eval $(call NMF_RULE,$(TARGET),))</pre><br />
<pre class="crayon-plain-tag">#include &lt;string&gt;

#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/var.h"


/**
 * NaCl实例类
 * 浏览器端的每一个embed标签对应了一个实例
 * 指定embed标签的属性：
 * src="hello_world.nmf"
 * type="application/x-pnacl"
 */
class HelloWorldInstance : public pp::Instance
{
    public:

        explicit HelloWorldInstance( PP_Instance instance ) :
                pp::Instance( instance )
        {
        }
        virtual ~HelloWorldInstance()
        {
        }

        /**
         * 处理浏览器端通过postMessage()调用传入的消息
         * @param var_message 浏览器传入的消息
         */
        virtual void HandleMessage( const pp::Var&amp; var_message )
        {
            // 处理传入消息
            if ( !var_message.is_string() ) return;
            std::string message = var_message.AsString();
            pp::Var var_reply;
            if ( message == "hello" )
            {
                var_reply = pp::Var( "hello from NaCl" );
                PostMessage( var_reply );
            }
        }
};

/**
 * NaCl模块类
 */
class HelloWorldModule : public pp::Module
{
    public:
        HelloWorldModule() :
                pp::Module()
        {
        }
        virtual ~HelloWorldModule()
        {
        }

        /**
         * 在模块加载完成后，浏览器即调用此函数创建实例。
         * @param in 浏览器端的实例对象句柄
         * @return 插件端的实例对象
         */
        virtual pp::Instance* CreateInstance( PP_Instance instance )
        {
            return new HelloWorldInstance( instance );
        }
};

namespace pp
{
    /**
     * 该工厂函数在NaCl模块在加载时被浏览器调用一次，不会多次调用
     * 浏览器维持模块的单例
     *
     */
    Module* CreateModule()
    {
        return new HelloWorldModule();
    }
}</pre><br />
<pre class="crayon-plain-tag">{
  "program": {
    "portable": {
      "pnacl-translate": {
        "url": "hello_world.pexe"
      }
    }
  }
}</pre>
<div> <br />
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;hello World&lt;/title&gt;
&lt;script type="text/javascript"&gt;
    HelloWorldModule = null; // 全局模块对象
    statusText = 'NO-STATUS';

    function moduleDidLoad()
    {
        HelloWorldModule = document.getElementById( 'hello_world' );
        updateStatus( 'SUCCESS' );
        HelloWorldModule.postMessage('hello');
    }

    //处理NaCl模块发送的消息
    function handleMessage( message_event )
    {
        alert( message_event.data );
    }

    function pageDidLoad()
    {
        if ( HelloWorldModule == null )
        {
            updateStatus( 'LOADING...' );
        }
        else
        {
            updateStatus();
        }
    }

    function updateStatus( opt_message )
    {
        if ( opt_message ) statusText = opt_message;
        var statusField = document.getElementById( 'statusField' );
        if ( statusField )
        {
            statusField.innerHTML = statusText;
        }
    }
&lt;/script&gt;
&lt;/head&gt;
&lt;body onload="pageDidLoad()"&gt;

    &lt;div id="listener"&gt;
        &lt;script type="text/javascript"&gt;
            var listener = document.getElementById( 'listener' );
            listener.addEventListener( 'load', moduleDidLoad, true );
            listener.addEventListener( 'message', handleMessage, true );
        &lt;/script&gt;
        &lt;embed id="hello_world" width=0 height=0 src="newlib/Debug/hello_world.nmf" type="application/x-nacl" /&gt;
    &lt;/div&gt;
    &lt;div&gt;
        Status &lt;code id="statusField"&gt;NO-STATUS&lt;/code&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
</div>
<div class="blog_h3"><span class="graybg">构建与运行</span></div>
<ol>
<li>Project - Build Project，会生成hello_world.pexe文件</li>
<li>鼠标选中工程根目录，Shift + F9，选择serve</li>
<li>使用Chrome打开http://localhost:5103/，可以看到弹出消息框，显示来自NaCl模块的消息文本：“hello from NaCl”，运行成功</li>
</ol>
<div class="blog_h3"><span class="graybg">远程调试</span></div>
<ol>
<li>在源代码hello_world.cc的79行，设置一个断点</li>
<li>修改Makefile，以Debug方式编译，并设置工具链把newlib置为默认：<br />
<pre class="crayon-plain-tag">CONFIG = Debug
VALID_TOOLCHAINS := newlib glibc pnacl win</pre>
</li>
<li>Run - Debug Configurations，在C/C++ Remote Application目录下创建新的Debug配置</li>
<li>Main 选项卡 
<ol>
<li>Name设置为nacl-helloworld newlib；Project选择nacl-helloworld</li>
<li>C/C++ Application填写：D:\CPP\projects\eclipse\4.3.2\nacl-helloworld\newlib\Debug\hello_world_x86_64.nexe</li>
<li>底部的链接Select Other，点击后选择GDB(DSF) Manual Remote Debugging Launcher</li>
<li>点选Disable auto build</li>
</ol>
</li>
<li>Debugger选项卡
<ol>
<li>Stop on startup at设置为：Instance_DidCreate，或者取消勾选</li>
<li>GDB Debugger选择：%NACL_SDK_ROOT%\toolchain\win_x86_newlib\bin\x86_64-nacl-gdb.exe</li>
<li>切换到子选项卡Connection，端口设置为4014</li>
</ol>
</li>
<li>Source选项卡，点击Add - Path Mapping，Name设置为pepper_mapping，添加一个映射条目：\cygdrive\s\src\out 对应D:\CPP\tools\Chromium\nacl_sdk，可以在调试无法找到源代码时再行设置</li>
<li>点击Apply保存设置，然后点击Run，Eclipse Debugger开始等待连接到Chrome</li>
<li>在客户端设置以下环境变量：<br />
<pre class="crayon-plain-tag">rem 重定向stderr、stdout
set NACL_EXE_STDERR=F:\Temp\Chrome\nacl_stderr.log
set NACL_EXE_STDOUT=F:\Temp\Chrome\nacl_stdout.log
rem 重定向NaCl内部生成的消息，默认至stderr
set NACLLOG=F:\Temp\Chrome\nacl.log
rem 控制警告、错误信息的显示
set NACL_PLUGIN_DEBUG=1
rem 1-255，越高则信息越详细
set NACL_SRPC_DEBUG=255
set NACLVERBOSITY=255</pre>
</li>
<li>运行make serve启动HTTP服务器</li>
<li>以参数--no-sandbox --enable-nacl-debug启动Chrome，如果使用ChromiumPortable，可以修改ChromiumPortable.ini：<br />
<pre class="crayon-plain-tag">[GoogleChromePortable]    
;--vmodule=ppb*=4 --enable-logging=stderr用于记录NaCl模块对Pepper API的调用
AdditionalParameters=--no-sandbox --enable-nacl-debug --disable-hang-monitor --vmodule=ppb*=4 --enable-logging=stderr</pre>
</li>
<li>Chrome暂停运行，Eclipse自动停止在断点处，调试成功</li>
</ol>
<div class="blog_h2"><span class="graybg">编译NaClPorts</span></div>
<p>NaCl提供了一种跨平台的类POSIX环境，很多Linux下开源项目都可以在此环境下运行，但是需要特殊的编译过程（port to nacl），在Windows下，需要Cygwin才能编译ports。</p>
<div class="blog_h3"><span class="graybg">Linux下NaclPorts编译举例：ffmpeg</span></div>
<ol>
<li>安装需要的工具<br />
<pre class="crayon-plain-tag">sudo apt-get install curl sed git cmake texinfo gettext pkg-config autoconf automake libtool xsltproc
sudo apt-get install libglib2.0-dev</pre>
</li>
<li>安装depot_tools，depot_tools是一个脚本工具包，用于管理代码的签出、review，包含gclient, gcl, git-cl, repo等工具<br />
<pre class="crayon-plain-tag">cd ~/CPP/tools/Chromium
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git

vim ~/.bashrc
#添加
export PATH=`pwd`/depot_tools:"$PATH"</pre>
</li>
<li>签出naclports源代码<br />
<pre class="crayon-plain-tag">cd ~/CPP/tools/Chromium
mkdir naclports
cd naclports
gclient config --name=src  https://chromium.googlesource.com/external/naclports.git
gclient sync</pre>
</li>
<li>进入/home/alex/CPP/tools/Chromium/naclports/src，可以看到若干Ports的源代码，每个子目录通常包含以下文件：
<ol>
<li>pkg_info：port的描述</li>
<li>build.sh：用于构建此port的脚本</li>
<li> nacl.patch：一个可选的补丁文件</li>
</ol>
</li>
<li>构建port的工具位于naclports/bin/naclports，执行下面的脚本即可下载、打补丁、构建、安装目标应用或者库<br />
<pre class="crayon-plain-tag">naclports install &lt;package_dir&gt; </pre></p>
<p>注意NaCl模块可以在四种架构下构建（i686,x86_64, arm, pnacl），每次构建只会使用其中一种架构。某些架构下有多个Toolchain（例如x86有newlib、glibc），可以通过环境变量选择：</p>
<pre class="crayon-plain-tag">NACL_ARCH=i686 TOOLCHAIN=glibc make ffmpeg</pre>
<p>如果要为某个port编译所有架构下所有工具链的版本，可以运行：</p>
<pre class="crayon-plain-tag">./make_all.sh ffmpeg</pre>
<p> 头文件、库被安装到对应工具链的目录下，使用这些库不需要额外的-I、-L参数。源码和编译的输出文件位于out/build/&lt;PACKAGE_NAME&gt;目录下。默认的，所有编译都是以RELEASE方式进行的，可以设置环境变量NACL_DEBUG=1来改变此行为，或者给naclports 传递 --debug参数。naclports会优先尝试直接从Google下载二进制包，如果要强制从源代码编译，可以传递--from-source参数</p>
</li>
<li>使用如下命令构建ffmpeg：<br />
<pre class="crayon-plain-tag">cd ~/CPP/tools/Chromium/naclports/src
NACL_ARCH=pnacl make ffmpeg</pre>
</li>
</ol>
<div class="blog_h2"><span class="graybg">FAQ</span></div>
<div class="blog_h3"><span class="graybg">如何让多个版本的Chrome共存</span></div>
<p>可以下载便携版（Portable）的Chromium：http://sourceforge.net/projects/crportable/</p>
<p>类似的，便携版的Chrome也有，可以到网上搜索并下载。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/chrome-native-client-study-note">NaCl学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/chrome-native-client-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ActiveMQ知识集锦</title>
		<link>https://blog.gmem.cc/activemq-faq</link>
		<comments>https://blog.gmem.cc/activemq-faq#comments</comments>
		<pubDate>Tue, 23 Sep 2014 03:31:53 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ActiveMQ]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[JMS]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=861</guid>
		<description><![CDATA[<p>常见问题 Setting clientID on a used Connection is not allowed 与Spring DMLC集成，进行持久化订阅时，会报此错误，报错的根源是： [crayon-69e8e2fb3a15b716599884/] 可以看到，状态isConnectionInfoSentToBroker变为true后，就不能再设置ClientID，修改此字段值的，只有： [crayon-69e8e2fb3a15e079897517/] 断点跟踪，发现调用栈片断： [crayon-69e8e2fb3a161462168821/] 也就是说，业务代码调用JmsTemplate发送JMS消息，导致isConnectionInfoSentToBroker为true且ClientID被设置。进一步分析发现： ConnectionPool只包含一个实际连接ActiveMQConnection，由多个Session共享 使用的ConnectionFactory：PooledConnectionFactory，仅仅持有一个ConnectionPool，也就是仅仅一个ActiveMQConnection AMQ的ClientId是在Collection级别设置的，只能在连接第一次使用前设置一次，这里由业务代码发起的JmsTemplate调用设置 ConnectionPool自动生成的ClientID，具有随机性，无法用于持久化订阅。因为持久化订阅者的识别方式是ClientID <a class="read-more" href="https://blog.gmem.cc/activemq-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-faq">ActiveMQ知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">Setting clientID on a used Connection is not allowed</span></div>
<p>与Spring DMLC集成，进行持久化订阅时，会报此错误，报错的根源是：</p>
<pre class="crayon-plain-tag">@Override
public void setClientID(String newClientID) throws JMSException {
    checkClosedOrFailed();
    // false
    if (this.clientIDSet) {
        throw new IllegalStateException("The clientID has already been set");
    }
    // true
    if (this.isConnectionInfoSentToBroker) {
        // 不允许在“已经使用”的连接上执行设置clientID的操作
        throw new IllegalStateException("Setting clientID on a used Connection is not allowed");
    }

    this.info.setClientId(newClientID);
    this.userSpecifiedClientID = true;
    ensureConnectionInfoSent();
}</pre>
<p>可以看到，状态isConnectionInfoSentToBroker变为true后，就不能再设置ClientID，修改此字段值的，只有：</p>
<pre class="crayon-plain-tag">protected void ensureConnectionInfoSent() throws JMSException {
    synchronized(this.ensureConnectionInfoSentMutex) {
        // Can we skip sending the ConnectionInfo packet??
        if (isConnectionInfoSentToBroker || closed.get()) {
            return;
        }
        //TODO shouldn't this check be on userSpecifiedClientID rather than the value of clientID?
        if (info.getClientId() == null || info.getClientId().trim().length() == 0) {
            // 此生成器的结果具有随机性
            info.setClientId(clientIdGenerator.generateId());
        }
        syncSendPacket(info.copy());
        // 这里修改了字段值
        this.isConnectionInfoSentToBroker = true;
        ...
    }
}</pre>
<p>断点跟踪，发现调用栈片断：</p>
<pre class="crayon-plain-tag">// ActiveMQConnection.java
void ensureConnectionInfoSent();
Session createSession(boolean transacted, int acknowledgeMode);
// ConnectionPool.java 这是对单个ActiveMQConnection的封装，允许多个Session共享之
Session makeSession(SessionKey key);
ConnectionPool (Connection connection);
// JmsTemplate.java
send(final Destination destination, final MessageCreator messageCreator);
// 业务代码略</pre>
<p>也就是说，业务代码调用JmsTemplate发送JMS消息，导致isConnectionInfoSentToBroker为true且ClientID被设置。进一步分析发现：</p>
<ol>
<li>ConnectionPool只包含一个实际连接ActiveMQConnection，由多个Session共享</li>
<li>使用的ConnectionFactory：PooledConnectionFactory，仅仅持有一个ConnectionPool，也就是仅仅一个ActiveMQConnection</li>
<li>AMQ的ClientId是在Collection级别设置的，只能在连接第一次使用前设置一次，这里由业务代码发起的JmsTemplate调用设置</li>
<li>ConnectionPool自动生成的ClientID，具有随机性，<span style="background-color: #c0c0c0;">无法用于持久化订阅。因为持久化订阅者的识别方式是ClientID + subscription name</span></li>
</ol>
<p>因此，使用AMQ的情况下，要进行持久化订阅，应当为DMLC提供一个可控的ConnectionFactory，比如单独分配ConnectionFactory。</p>
<div class="blog_h3"><span class="graybg">javax.jms.InvalidClientIDException</span></div>
<p>报错信息示例：</p>
<p>[WARN ] [triggerStartAsyncNetworkBridgeCreation: remoteBroker=tcp://Zircon/127.0.1.1:38592@47262, localBroker= vm://BrokerA#19200] 2017-08-08 12:21:21 org.apache.activemq.broker.TransportConnection.processAddConnection(TransportConnection.java:770)<br />Failed to add Connection BrokerA-&gt;BrokerB-32884-1502096377514-3780:1<br />javax.jms.InvalidClientIDException: Broker: BrokerA - Client: NC_BrokerA_BrokerB_inbound_BrokerA already connected from vm://BrokerA#8</p>
<p>这里的情况是，BrokerA 到BrokerB之间的通道对应了一个客户端，其ID已经被占用。原因可能是BrokerB代理意外终止导致。</p>
<p>解决办法：设置<pre class="crayon-plain-tag">TransportConnector</pre>的属性：<pre class="crayon-plain-tag">tc.setAllowLinkStealing( true )</pre></p>
<div class="blog_h3"><span class="graybg">Consumer无法监听到消息</span></div>
<p>发现设置了异步启动<pre class="crayon-plain-tag">broker.setStartAsync( true )</pre>的代理，在构成代理网络上，容易出现偶发性的网络相关错误，并发生代理宕机重启后，某些Consumer不再收到消息的现象，关闭异步启动后收不到消息的现象消失。</p>
<p>可能相关的报错信息：</p>
<p>javax.jms.JMSException: peer (vm://BrokerB#3) stopped.<br />Caused by: org.apache.activemq.transport.TransportDisposedIOException: peer (vm://BrokerA#1) stopped.</p>
<p>javax.jms.IllegalStateException: The Consumer is closed</p>
<div class="blog_h3"><span class="graybg">无法启动：Caused by: java.io.IOException: Invalid location: 5:3807632:</span></div>
<p>java.lang.NegativeArraySizeException<br /> at org.apache.activemq.store.kahadb.disk.journal.DataFileAccessor.readRecord(DataFileAccessor.java:92)</p>
<p>服务非正常重启导致kahadb日志损坏，可以设置属性解决：</p>
<pre class="crayon-plain-tag">&lt;persistenceAdapter&gt;
    &lt;kahaDB directory="D:/amq/datasync/kahadb" checkForCorruptJournalFiles="true" /&gt;
&lt;/persistenceAdapter&gt;</pre>
<div class="blog_h3"><span class="graybg">ActiveMQ代理实例无法连入网络</span></div>
<p>检查以下项目：</p>
<ol>
<li>如果使用多播自动发现，检查有没有使用正确的网卡，参考：<a href="/activemq-network-connetcor-problem-caused-by-multihomed-host">我的另一篇文章</a></li>
<li>保证代理名称的唯一性</li>
</ol>
<div class="blog_h3"><span class="graybg">Linux（CentOS 6.3）下无法收发任何消息</span></div>
<p>现象：不能接收到任何组播（Multicast）消息</p>
<p>原因：可能是因为生产者操作系统内核支持IPv6，但是网络本身、消费者操作系统内核不支持IPv6。</p>
<p>解决：当操作系统内核支持IPv6时，JRE会默认使用IPv6，要改变此行为，可以设置JVM系统属性：</p>
<pre class="crayon-plain-tag">-Djava.net.preferIPv4Stack=true</pre>
<div class="blog_h3"><span class="graybg">端口0是什么意思</span></div>
<p>如下配置：</p>
<pre class="crayon-plain-tag">&lt;transportConnector uri="tcp://localhost:0" discoveryUri="multicast://default"/&gt;</pre>
<p>uri中的端口指定为0，表示由ActiveMQ选择一个可用的端口。由于使用发现机制，限定端口没有必要。 </p>
<div class="blog_h3"><span class="graybg">命名规则</span></div>
<p>代理、队列、网络连接器、链接器的名称，都可以通过API自由设定。</p>
<p>假设代理名称为BrokerA，当：</p>
<ol>
<li>基于双向自动发现的网络连接器进行连接时，代理BrokerA通过NC_BrokerA连接，代理BrokerB通过NC_BrokerB连接，则BrokerB在BrokerA队列上的远程消费者的ClientID命名为：
<ol>
<li>NC_BrokerA_BrokerB_inbound_BrokerA，这是由BrokerA双向网络连接（反向）产生的远程消费者</li>
<li>NC_BrokerB_BrokerB_inbound_BrokerA，这是由BrokerB双向网络连接（正向）产生的远程消费者</li>
</ol>
</li>
<li>基于单向自动发现的网络连接器进行连接时，代理BrokerA通过NC_BrokerA连接，代理BrokerB通过NC_BrokerB连接，则BrokerB在BrokerA队列上的远程消费者的ClientID命名为：
<ol>
<li>NC_BrokerA_BrokerB_inbound_BrokerA，这是由BrokerA单向网络连接产生的远程消费者</li>
</ol>
</li>
<li>基于双向静态网络连接器进行连接，代理BrokerB通过NC_BrokerB连接，则BrokerB在BrokerA队列上的远程消费者的ClientID命名为：
<ol>
<li>NC_BrokerB_BrokerB_inbound_BrokerA</li>
</ol>
</li>
<li>基于单向静态网络连接器进行连接，代理BrokerB通过NC_BrokerB连接，则BrokerB在BrokerA队列上没有消费者</li>
</ol>
<p>单向网络连接，仅能用来发送消息，不能接收网络代理发来的消息。</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/activemq-faq">ActiveMQ知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/activemq-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP知识集锦</title>
		<link>https://blog.gmem.cc/php-faq</link>
		<comments>https://blog.gmem.cc/php-faq#comments</comments>
		<pubDate>Wed, 11 Dec 2013 08:56:07 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[FAQ]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=863</guid>
		<description><![CDATA[<p>性能优化 非代码级优化 通用内核参数调优 参考Linux知识集锦。 调整连接数 [crayon-69e8e2fb3a4c4157669267/] 相应的调整内核参数：[crayon-69e8e2fb3a4c8682900461-i/]  启用Zend Opcache [crayon-69e8e2fb3a4ca449951209/] 启用HugePage Linux的HugePage可以让PHP的TEXT段、内存分页都以巨页的方式存储，减少TLB相关的性能损耗。 先设置内核参数：[crayon-69e8e2fb3a4cc922340958-i/] 然后配置：[crayon-69e8e2fb3a4ce989043530-i/] 使用新版编译器 GCC 4.8以上编译器能够提升5%的性能，原因是开启Global Register对opline/execute_data的支持。 使用PGO编译 所谓剖析引导优化（PGO，Profile Guided Optimization），是通过剖析，给编译器以目标应用程序特点的提示信息，然后编译器进行针对性优化的技术。 <a class="read-more" href="https://blog.gmem.cc/php-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/php-faq">PHP知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">性能优化</span></div>
<div class="blog_h2"><span class="graybg">非代码级优化</span></div>
<div class="blog_h3"><span class="graybg">通用内核参数调优</span></div>
<p>参考<a href="/linux-faq#kernel-params-opt-ref">Linux知识集锦</a>。</p>
<div class="blog_h3"><span class="graybg">调整连接数</span></div>
<pre class="crayon-plain-tag">; 根据QPS调整
listen.backlog = 65536</pre>
<p>相应的调整内核参数：<pre class="crayon-plain-tag">sysctl net.core.somaxconn=65536</pre> </p>
<div class="blog_h3"><span class="graybg">启用Zend Opcache</span></div>
<pre class="crayon-plain-tag">zend_extension=opcache.so
opcache.enable=1
opcache.enable_cli=0
opcache.memory_consumption=512
opcache.interned_strings_buffer=16
opcache.max_accelerated_files=10000
opcache.max_wasted_percentage=5
opcache.validate_timestamps=0
opcache.revalidate_freq=0
opcache.revalidate_path=0
opcache.huge_code_pages=1</pre>
<div class="blog_h3"><span class="graybg">启用HugePage</span></div>
<p>Linux的HugePage可以让PHP的TEXT段、内存分页都以巨页的方式存储，减少TLB相关的性能损耗。</p>
<p>先设置内核参数：<pre class="crayon-plain-tag">sysctl vm.nr_hugepages=128</pre></p>
<p>然后配置：<pre class="crayon-plain-tag">opcache.huge_code_pages=1</pre></p>
<div class="blog_h3"><span class="graybg">使用新版编译器</span></div>
<p>GCC 4.8以上编译器能够提升5%的性能，原因是开启Global Register对opline/execute_data的支持。</p>
<div class="blog_h3"><span class="graybg">使用PGO编译</span></div>
<p>所谓剖析引导优化（PGO，Profile Guided Optimization），是通过剖析，给编译器以目标应用程序特点的提示信息，然后编译器进行针对性优化的技术。</p>
<p>首先，构建能产生Profile的二进制文件：</p>
<pre class="crayon-plain-tag">make prof-gen -j 8</pre>
<p>然后，开始运行PHP-FPM，进行训练：</p>
<pre class="crayon-plain-tag">/php-7.3.1/sapi/fpm/php-fpm -R --fpm-config /usr/local/php/etc/php-fpm.conf</pre>
<p>最后，重新编译并安装：</p>
<pre class="crayon-plain-tag">make prof-clean 
make prof-use -j 8 &amp;&amp; make install</pre>
<div class="blog_h1"><span class="graybg">OPcache</span></div>
<p>OPcache通过将 PHP 脚本<span style="background-color: #c0c0c0;">预编译的字节码存储到共享内存</span>中来提升 PHP 的性能， 存储预编译字节码的好处就是<span style="background-color: #c0c0c0;">省去了每次加载和解析 PHP 脚本的开销</span>。PHP 5.5.0 及后续版本中已经绑定了 OPcache 扩展。</p>
<p>如果OPcache和Xdebug一起使用，必须先加载OPcache。</p>
<div class="blog_h2"><span class="graybg">安装</span></div>
<p>OPcache只能编译为共享扩展。如果你通过--disable-all禁用了默认扩展的构建，则需要--enable-opcache来开启OPcache。</p>
<p>编译之后，可以用下面的指令将该扩展加载到PHP中：</p>
<pre class="crayon-plain-tag">zend_extension=/full/path/to/opcache.so</pre>
<div class="blog_h2"><span class="graybg">配置</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 250px; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>opcache.enable</td>
<td>
<p>默认1，可修改范围PHP_INI_ALL</p>
<p>启用操作码缓存。如果禁用此选项，则不会优化和缓存代码。在运行期使用 ini_set() 函数只能禁用 opcache.enable 设置，不可以启用</p>
</td>
</tr>
<tr>
<td>opcache.enable_cli</td>
<td>
<p>默认0，PHP_INI_SYSTEM</p>
<p>仅针对 CLI 版本的 PHP 启用操作码缓存</p>
</td>
</tr>
<tr>
<td>opcache.memory_consumption</td>
<td>
<p>默认64，PHP_INI_SYSTEM</p>
<p>OPcache 的共享内存大小，单位MB</p>
</td>
</tr>
<tr>
<td>opcache.interned_strings_buffer</td>
<td>
<p>默认4，PHP_INI_SYSTEM</p>
<p>用来存储预留字符串的内存大小，单位MB</p>
</td>
</tr>
<tr>
<td>opcache.max_accelerated_files</td>
<td>
<p>默认2000，PHP_INI_SYSTEM</p>
<p>OPcache 哈希表中可存储的脚本文件数量上限，200-1000000之间</p>
<p>真实的取值是在质数集合 { 223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987 } 中找到的第一个大于等于设置值的质数</p>
</td>
</tr>
<tr>
<td>opcache.max_wasted_percentage</td>
<td>
<p>默认5，PHP_INI_SYSTEM</p>
<p>浪费内存的上限，以百分比计。 如果达到此上限</p>
</td>
</tr>
<tr>
<td>opcache.use_cwd</td>
<td>
<p>默认1，PHP_INI_SYSTEM</p>
<p>哈希表的键是脚本的名字+脚本的工作目录，这样可以避免同名脚本导致键冲突</p>
<p>禁用此选项可以提高性能，但是可能会导致应用崩溃</p>
</td>
</tr>
<tr>
<td>opcache.validate_timestamps</td>
<td>
<p>默认1，PHP_INI_ALL</p>
<p>如果启用，那么 OPcache 会每隔 opcache.revalidate_freq秒检查脚本是否更新</p>
<p>如果禁用，则需要使用 opcache_reset() 或者 opcache_invalidate() 函数来手动重置 OPcache，或者重启Web服务</p>
</td>
</tr>
<tr>
<td>opcache.revalidate_freq</td>
<td>
<p>默认2，PHP_INI_ALL</p>
<p>检查脚本时间戳是否有更新的周期，以秒为单位</p>
</td>
</tr>
<tr>
<td>opcache.save_comments</td>
<td>
<p>默认1，PHP_INI_SYSTEM</p>
<p>如果禁用，脚本文件中的注释内容将不会被包含到操作码缓存文件， 这样可以有效减小优化后的文件体积。 禁用此配置指令可能会导致一些依赖注释或注解的 应用或框架无法正常工作， 比如： Doctrine， Zend Framework 2 以及 PHPUnit</p>
</td>
</tr>
<tr>
<td>opcache.enable_file_override</td>
<td>
<p>默认0，PHP_INI_SYSTEM</p>
<p>如果启用，则在调用函数 file_exists()， is_file() 以及 is_readable() 的时候， 都会检查操作码缓存，无论文件是否已经被缓存。 如果应用中包含检查 PHP 脚本存在性和可读性的功能，这样可以提升性能</p>
</td>
</tr>
<tr>
<td>opcache.optimization_level</td>
<td>
<p>默认0x7FFFBFFF，PHP_INI_SYSTEM</p>
<p>控制优化级别的二进制位掩码</p>
</td>
</tr>
<tr>
<td>opcache.blacklist_filename</td>
<td>
<p>默认""，PHP_INI_SYSTEM</p>
<p>OPcache 黑名单文件位置。 黑名单文件为文本文件，包含了不进行预编译优化的文件名，每行一个文件名。 黑名单中的文件名可以使用通配符，也可以使用前缀。 此文件中以分号开头的行将被视为注释</p>
</td>
</tr>
<tr>
<td>opcache.max_file_size</td>
<td>
<p>默认0，PHP_INI_SYSTEM</p>
<p>以字节为单位的缓存的文件大小上限。设置为 0 表示缓存全部文件</p>
</td>
</tr>
<tr>
<td>opcache.consistency_checks</td>
<td>
<p>默认0，PHP_INI_ALL</p>
<p>如果是非 0 值，OPcache 将会每隔 N 次请求检查缓存校验和。 N 即为此配置指令的设置值</p>
</td>
</tr>
<tr>
<td>opcache.force_restart_timeout</td>
<td>
<p>默认180，PHP_INI_SYSTEM</p>
<p>如果缓存处于非激活状态，等待多少秒之后计划重启。 如果超出了设定时间，则 OPcache 模块将杀除持有缓存锁的进程， 并进行重启</p>
</td>
</tr>
<tr>
<td>opcache.error_log</td>
<td>
<p>默认""，PHP_INI_SYSTEM</p>
<p>OPcache 模块的错误日志文件。 如果留空，则视为 stderr， 错误日志将被送往标准错误输出</p>
</td>
</tr>
<tr>
<td>opcache.log_verbosity_level</td>
<td>
<p>默认1，PHP_INI_SYSTEM</p>
<p>OPcache 模块的日志级别。 默认情况下，仅有致命级别（0）及错误级别（1）的日志会被记录。 其他可用的级别有：警告（2），信息（3）和调试（4）</p>
</td>
</tr>
<tr>
<td>opcache.preferred_memory_model</td>
<td>
<p>默认""，PHP_INI_SYSTEM</p>
<p>通常情况下，自动选择就可以满足需求。可选值包括： mmap，shm, posix 以及 win32</p>
</td>
</tr>
<tr>
<td>opcache.restrict_api</td>
<td>
<p>默认""，PHP_INI_SYSTEM</p>
<p>仅允许路径是以指定字符串开始的 PHP 脚本调用 OPcache API 函数。 默认值为空字符串 ""，表示不做限制</p>
</td>
</tr>
<tr>
<td>opcache.file_update_protection</td>
<td>
<p>默认2，PHP_INI_ALL</p>
<p>如果文件的最后修改时间距现在不足此项配置指令所设定的秒数，那么这个文件不会进入到缓存中。 这是为了防止尚未完全修改完毕的文件进入到缓存。 如果你的应用中不存在部分修改文件的情况，把此项设置为 0 可以提高性能</p>
</td>
</tr>
<tr>
<td>opcache.huge_code_pages</td>
<td>
<p>默认0，PHP_INI_SYSTEM</p>
<p>启用或者禁用将 PHP 代码（文本段）拷贝到 HUGE PAGES 中。 此项配置指令可以提高性能，但是需要在 OS 层面进行对应的配置</p>
</td>
</tr>
<tr>
<td>opcache.lockfile_path</td>
<td>
<p>默认/tmp，PHP_INI_SYSTEM</p>
<p>用来存储共享锁文件的绝对路径</p>
</td>
</tr>
<tr>
<td>opcache.file_cache</td>
<td>
<p>默认NULL，PHP_INI_SYSTEM</p>
<p>置二级缓存目录并启用二级缓存。 启用二级缓存可以在 SHM 内存满了、服务器重启或者重置 SHM 的时候提高性能。 默认值为空字符串 ""，表示禁用基于文件的缓存</p>
</td>
</tr>
<tr>
<td>opcache.file_cache_only</td>
<td>
<p>默认0，PHP_INI_SYSTEM</p>
<p>启用或禁用在共享内存中的 opcode 缓存</p>
</td>
</tr>
<tr>
<td>opcache.file_cache_consistency_checks</td>
<td>
<p>默认1，PHP_INI_SYSTEM</p>
<p>当从文件缓存中加载脚本的时候，是否对文件的校验和进行验证</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">推荐配置</span></div>
<p>官方文档的推荐配置如下：</p>
<pre class="crayon-plain-tag">opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.enable_cli=1</pre>
<div class="blog_h1"><span class="graybg">编程知识</span></div>
<div class="blog_h2"><span class="graybg">常用代码片段</span></div>
<div class="blog_h3"><span class="graybg">解析JSON</span></div>
<p>可以使用json_decode函数：</p>
<pre class="crayon-plain-tag">/**
 * @param $json 带解析的JSON字符串
 * @param $assoc 如果设置为true，返回结果被转换为关联数组
 * @param $depth 最大递归深度
 * @param $options 选项位域，目前仅支持JSON_BIGINT_AS_STRING 
 * @return 与JSON内容对应的PHP类型，如果无法解析返回null
 */
mixed json_decode ( string $json [, bool $assoc = false [, int $depth = 512 [, int $options = 0 ]]] );</pre>
<div class="blog_h3"><span class="graybg">正则式匹配</span></div>
<p>使用函数：</p>
<pre class="crayon-plain-tag">/**
 * 搜索一个匹配正则式的子串
 *
 * @param $pattern 用于匹配的正则式，注意 / / 包围
 * @param $subject 被匹配的字符串
 * @param &amp;$matches 可选，存放匹配结果的数组引用。第一个元素将存放完整匹配的子串，之后的元素对应每个捕获（用括号包围的子正则式）的匹配
 * @param $flags 匹配标记，可选值：
 *               PREG_OFFSET_CAPTURE  把匹配的子串位于字符串的偏移量添加到$matches数组
 * @param $offset 开始搜索的偏移量，单位字节
 * @return 如果不匹配，返回0，否则返回1
 */
int preg_match ( string $pattern , string $subject [, array &amp;$matches [, int $flags = 0 [, int $offset = 0 ]]] );
/**
 * 搜索全部匹配
 */
int preg_match_all ( string $pattern , string $subject 
                     [, array &amp;$matches [, int $flags = PREG_PATTERN_ORDER [, int $offset = 0 ]]] )</pre>
<p>示例：</p>
<pre class="crayon-plain-tag">preg_match('/crayon-[0-9a-f]+-i/',$capture[2]);</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/php-faq">PHP知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/php-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CentOS知识集锦</title>
		<link>https://blog.gmem.cc/centos-faq</link>
		<comments>https://blog.gmem.cc/centos-faq#comments</comments>
		<pubDate>Sun, 17 Nov 2013 07:57:24 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[CentOS]]></category>
		<category><![CDATA[FAQ]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=8489</guid>
		<description><![CDATA[<p>常用命令 软件包管理命令 dnf dnf是yum的下一个重大版本，基于RPM的Linux的包管理器。dnf保证了和yum的CLI接口的大概兼容性。 命令 作用 check-update 检查可更新的软件包 upgrade 更新软件包 [crayon-69e8e2fb3a9db060812485/] install 安装指定软件包 [crayon-69e8e2fb3a9df454770662/] reinstall 重新安装指定软件包 remove 删除指定软件包 search 以关键词搜索软件包 info 查看软件包详情 <a class="read-more" href="https://blog.gmem.cc/centos-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/centos-faq">CentOS知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">常用命令</span></div>
<div class="blog_h2"><span class="graybg">软件包管理命令</span></div>
<div id="rpm">
<div class="blog_h3"><span class="graybg">dnf</span></div>
<p>dnf是yum的下一个重大版本，基于RPM的Linux的包管理器。dnf保证了和yum的CLI接口的大概兼容性。</p>
</div>
<div>
<table border="1" width="100%">
<thead>
<tr>
<td style="width: 20%; text-align: center;">命令</td>
<td style="text-align: center;">作用</td>
</tr>
</thead>
<tbody>
<tr>
<td>check-update</td>
<td>检查可更新的软件包</td>
</tr>
<tr>
<td>upgrade</td>
<td>
<p>更新软件包</p>
<pre class="crayon-plain-tag"># 更新所有软件包
dnf upgrade

# 更新指定软件包
dnf upgrade pkg-name</pre>
</td>
</tr>
<tr>
<td>install</td>
<td>安装指定软件包<br />
<pre class="crayon-plain-tag"># 从指定的软件源安装
dnf –enablerepo=repo-name install pkg-name 

# 安装名为mariadb的模块或组
dnf install @mariadb</pre>
</td>
</tr>
<tr>
<td>reinstall</td>
<td>重新安装指定软件包</td>
</tr>
<tr>
<td>remove</td>
<td>删除指定软件包</td>
</tr>
<tr>
<td>search</td>
<td>以关键词搜索软件包</td>
</tr>
<tr>
<td>info</td>
<td>查看软件包详情</td>
</tr>
<tr>
<td>repoquery</td>
<td>
<p>查看软件包依赖
<pre class="crayon-plain-tag"># 查看软件包依赖
dnf repoquery --requires pkg-name
# 查看满足依赖所需的额外 RPM 包
dnf repoquery --requires --resolve pkg-name</pre>
</td>
</tr>
<tr>
<td>download</td>
<td>下载软件包到当前目录（不安装）</td>
</tr>
<tr>
<td>list installed</td>
<td>列出已安装软件包</td>
</tr>
<tr>
<td>grouplist</td>
<td>列出软件组</td>
</tr>
<tr>
<td>groupinstall</td>
<td>安装指定软件组软件</td>
</tr>
<tr>
<td>groupupdate</td>
<td>更新指定软件组软件</td>
</tr>
<tr>
<td>groupremove</td>
<td>删除指定软件组软件</td>
</tr>
<tr>
<td>repolist</td>
<td>列出已启用的软件安装源</td>
</tr>
<tr>
<td>repolist all</td>
<td>列出所有软件安装源</td>
</tr>
<tr>
<td>downgrade</td>
<td>降级指定软件包版本</td>
</tr>
<tr>
<td>history</td>
<td>查看 DNF 命令历史记录</td>
</tr>
<tr>
<td>config-manager</td>
<td>
<pre class="crayon-plain-tag"># 添加 DNF 软件安装源
dnf config-manager --add-repo example.repo
# 启用 DNF 软件安装源
dnf config-manager --set-enabled example-repo
# 禁用 DNF 软件安装源
dnf config-manager --set-disabled example-repo</pre>
</td>
</tr>
<tr>
<td>autoremove</td>
<td>删除不需要的依赖项软件包</td>
</tr>
<tr>
<td>clean all</td>
<td>删除缓存数据及软件包文件</td>
</tr>
</tbody>
</table>
</div>
<div>
<div class="blog_h3"><span class="graybg">rpm</span></div>
</div>
<p>rpm即红帽子包管理器，是Redhat及其衍生系统使用的软件包管理工具。其常用选项包括：
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">选项分类 </td>
<td style="text-align: center;">选项说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>安装/更新/删除</td>
<td>
<p>-i, --install 安装软件包<br />-F, --freshen=&lt;packagefile&gt;+ 如果软件包已经安装，升级软件包<br />-e, --erase=&lt;package&gt;+ 清除 (卸载) 软件包<br />-U, --upgrade=&lt;packagefile&gt;+ 升级软件包</p>
<p>-h, --hash 软件包安装的时候列出哈希标记<br />--allfiles 安装全部文件，包含配置文件，否则配置文件会被跳过<br />--nodeps 忽略软件包的依赖关系强行安装<br />--oldpackage 更新到软件包的旧版本<br />--percent 安装软件包时打印百分比<br />--replacefiles 忽略软件包之间的冲突的文件<br />--replacepkgs 如果软件包已经有了，重新安装软件包<br />--test 不真正安装，只是判断下是否能安装</p>
</td>
</tr>
</tbody>
</table>
<p>命令应用举例：</p>
<pre class="crayon-plain-tag">#检查软件包是否已经安装
rpm -q pptpd
#安装指定路径上的软件包并显示进度
rpm -ivh ~/jdk.rpm
#不安装，仅测试依赖关系
rpm -ivh --test ~/jdk.rpm
#列出所有已安装的软件包
rpm -qa
#升级软件包
rpm -Uvh jdk.rpm 
#删除软件包
rpm -e jdk.rpm
#从源代码编译并安装软件
rpm　--recompile　vim-4.6.src.rpm

# 显示wget包在系统中安装的文件 
rpm -ql wget
# 显示RPM包中的文件
rpm -qlp wget.rpm

# 显示一个文件所属的软件包
rpm -qf /usr/bin/file</pre>
<div id="yum" class="blog_h3"><span class="graybg">yum</span></div>
<p>yum（Yellow dog Updater, Modified）是一个交互式的、基于RPM的软件包管理工具，类似于apt-get。它能够从指定的服务器自动下载 RPM 包并且安装，可以处理依赖性关系，并且一次安装所有依赖的软件包，支持Redhat-like的操作系统。其命令格式如下：</p>
<pre class="crayon-plain-tag">yum [options] [command] [package ...]</pre>
<p> 其中command为子命令，包括：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;"> 子命令</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>check </td>
<td>检查 RPM 数据库问题</td>
</tr>
<tr>
<td>check-update</td>
<td>检查是否有可用的软件包更新</td>
</tr>
<tr>
<td>update</td>
<td>更新系统中的一个或多个软件包，<pre class="crayon-plain-tag">yum update [pkg0 [,...]]</pre> ，不指定包名则更新所有软件包</td>
</tr>
<tr>
<td>repolist </td>
<td>显示已配置的源</td>
</tr>
<tr>
<td>search</td>
<td>根据关键字搜索软件包吗，<pre class="crayon-plain-tag">yum search keyword</pre> </td>
</tr>
<tr>
<td>clean </td>
<td>删除缓存数据（/var/cache/yum）</td>
</tr>
<tr>
<td>install</td>
<td>向系统中安装一个或多个软件包，<pre class="crayon-plain-tag">yum install pkg0 [,...]</pre> </td>
</tr>
<tr>
<td>reinstall</td>
<td>覆盖安装软件包</td>
</tr>
<tr>
<td>deplist</td>
<td>列出软件包的依赖关系</td>
</tr>
<tr>
<td>downgrade</td>
<td>降级软件包</td>
</tr>
<tr>
<td>erase</td>
<td>从系统中移除一个或多个软件包</td>
</tr>
<tr>
<td>info</td>
<td>显示关于软件包或组的详细信息</td>
</tr>
<tr>
<td>list </td>
<td>
<p>列出一个或一组软件包：</p>
<pre class="crayon-plain-tag"># 显示已安装
yum list installed
# 显示可安装
yum list available</pre>
</td>
</tr>
<tr>
<td>provides</td>
<td>查找提供指定内容的软件包</td>
</tr>
<tr>
<td>repo-pkgs</td>
<td>将一个源当作一个软件包组，可以一次性安装/移除全部软件包</td>
</tr>
</tbody>
</table>
<p>options为选项，常用的包括：
<ol>
<li>-y 假设对所有提示回答“是”</li>
<li>-q 不输出安装信息，常和-y联用</li>
<li>-e 设置错误级别，0-10，0表示仅仅输出致命错误</li>
</ol>
<div class="blog_h3"><span class="graybg">yumdownloader</span></div>
<p>从YUM源下载RPM包，注意，已经安装的RPM包，或者它的依赖，都不会下载。</p>
<p>格式：<pre class="crayon-plain-tag">yumdownloader [options] package1 [package2...]</pre></p>
<p>选项：</p>
<p style="padding-left: 30px;">--destdir DIR 下载到的目标目录，默认当前目录</p>
<p style="padding-left: 30px;">--urls 从指定URL下载，而不是根据包名下载</p>
<p style="padding-left: 30px;">--resolve 下载RPM时，解析它的依赖，并一同下载</p>
<p style="padding-left: 30px;">--source 下载source而非binary</p>
<p style="padding-left: 30px;">--archlist=ARCH1[,ARCH2...] 下载指定体系结构的包</p>
<div class="blog_h3"><span class="graybg">repoquery</span></div>
<p>从YUM存储库查询信息</p>
<p>格式：<pre class="crayon-plain-tag">repoquery [options] &lt;item ...&gt;</pre></p>
<p>选项：</p>
<p style="padding-left: 30px;">-i 显示类似于<pre class="crayon-plain-tag">rpm -qi</pre>的软件包信息<br />-l 列出包中文件<br />-R 列出包的依赖<br />--resolve 解析包的capabilities，如果不指定该选项，列出的依赖是文件，指定后列出的是包名<br />--recursive 递归的查询<br />--location  打印包的可下载位置</p>
<p>示例：</p>
<pre class="crayon-plain-tag"># 解析bind-utils的所有依赖  递归解析包               并下载
repoquery -R --resolve --recursive  bind-utils  |  xargs -r yumdownloader </pre>
<div class="blog_h3"><span class="graybg">rpm2cpio</span></div>
<p>将RPM包转换为CPIO归档文件，输出到标准输出。示例：<pre class="crayon-plain-tag">rpm2cpio wget.rpm| cpio -idm</pre></p>
<div class="blog_h2"><span class="graybg">系统管理命令</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">update-ca-trust</td>
<td>
<p>更新系统的根证书列表：</p>
<pre class="crayon-plain-tag">update-ca-trust enable 

cd /etc/pki/ca-trust/source/anchors/
# 将你的CA证书拷贝到此目录

# 更新证书列表
update-ca-trust extract </pre>
</td>
</tr>
<tr>
<td class="blog_h3">firewalld</td>
<td>
<p>防火墙服务，是CentOS 7的重要特性。优势包括：
<ol>
<li>支持动态更新，不用重启服务</li>
<li>支持防火墙Zone的概念</li>
</ol>
<pre class="crayon-plain-tag"># 启动
systemctl start  firewalld
# 启用
systemctl enable firewalld </pre>
</td>
</tr>
<tr>
<td class="blog_h3">firewall-cmd</td>
<td>
<p>firewalld的命令行接口。
<p><span style="background-color: #c0c0c0;">格式：</span><pre class="crayon-plain-tag">firewall-cmd [OPTIONS...]</pre></p>
<p><span style="background-color: #c0c0c0;">选项：</span></p>
<pre class="crayon-plain-tag">--state                         # 显示firewalld的状态
--reload                        # 不中断连接，重新加载
--complete-reload               # 中断所有连接，重新加载
--runtime-to-permanent          # 将当前防火墙的规则永久保存
--check-config                  # 检查配置正确性


--get-log-denied                # 获取记录被拒绝的日志
--set-log-denied=value          # 设置记录被拒绝的日志 'all','unicast','broadcast','multicast','off'
--get-active-zones              # 查看激活的区域信息
--get-zones                     # 查看可用的区域信息
--get-zone-of-interface=eth1    # 查看指定接口所属区域

--panic-on                      # 拒绝所有包
--panic-off                     # 取消拒绝所有包
--query-panic                   # 查看是否拒绝所有包


--set-default-zone=public       # 设置网络接口默认所属区域

--get-zone-of-interface=eth1                        # 查看eth1所属的区域
--zone=public --add-interface=eth1                  # 将eth1加入到public区域
# 加--permanent表示永久修改
--permanent --zone=public --add-port=443/tcp        # 开放public区域的443 TCP端口
--permanent --zone=public --remove-port=53/udp      # 禁止public区域的53  UDP端口
--zone=public --list-ports                          # 列出public区域开放的端口
   

--get-services                                      # 显示支持的服务列表
--enable service=ssh                                # 允许SSH服务通过
--disable service=ssh                               # 禁止SSH服务通过
--enable service=samba --timeout=600                # 在600秒内允许samba服务通过
--list-services                                     # 列出允许的服务
--permanent --zone=internal --add-service=http      # 将HTTP服务加到internal区域


# 直接打开9000 TCP端口
--direct -add-rule ipv4 filter INPUT 0 -p tcp --dport 9000 -j ACCEPT


# 端口转发
# 将80端口的流量转发至8080
firewall-cmd --add-forward-port=port=80:proto=tcp:toport=8080  
# 将80端口的流量转发至192.168.0.1
firewall-cmd --add-forward-port=port=80:proto=tcp:toaddr=192.168.0.1 
# 将80端口的流量转发至192.168.0.1的8080端口
firewall-cmd --add-forward-port=port=80:proto=tcp:toaddr=192.168.0.1:toport=8080  </pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">CentOS 8</span></div>
<div class="blog_h3"><span class="graybg">升级最新内核</span></div>
<p><pre class="crayon-plain-tag">rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org
dnf install https://www.elrepo.org/elrepo-release-8.0-2.el8.elrepo.noarch.rpm
dnf --enablerepo=elrepo-kernel install kernel-ml</pre>
<p>重启即可。 </p>
<div class="blog_h2"><span class="graybg">零散问题</span></div>
<div class="blog_h3"><span class="graybg">如何切换yum源</span></div>
<pre class="crayon-plain-tag">curl -o /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo </pre>
<div class="blog_h3"><span class="graybg">如何信任CA证书</span></div>
<p>将证书拷贝到目录：/etc/pki/ca-trust/source/anchors/，然后执行：</p>
<pre class="crayon-plain-tag">update-ca-trust extract</pre>
<div class="blog_h3"><span class="graybg">如何使用APT命令</span></div>
<p>以x64的Centos为例</p>
<pre class="crayon-plain-tag">wget http://pkgs.repoforge.org/apt/apt-0.5.15lorg3.94a-5.el5.rf.x86_64.rpm
rpm -Uvh apt-0.5.15lorg3.94a-5.el5.rf.x86_64.rpm
cp /etc/apt/sources.list.d/os.list /etc/apt/sources.list
vi /etc/apt/sources.list</pre>
<div class="blog_h3"><span class="graybg">如何使用update-grub</span></div>
<p>修改Grub启动选项仍然是通过/etc/default/grub文件，但是Ubuntu下update-grub命令的等价物是：</p>
<pre class="crayon-plain-tag">grub2-mkconfig -o /boot/grub2/grub.cfg </pre>
<div class="blog_h3"><span class="graybg">添加常用软件源</span></div>
<pre class="crayon-plain-tag">#添加源Fedora Extra Packages for Enterprise Linux (EPEL) repository
#该源包含了很多CentOS系统没有包含的常用软件
yum install epel-release
#如果从该源安装软件时出现： Cannot retrieve metalink for repository: epel. Please verify its path and try again
#则编辑vi /etc/yum.repos.d/epel.repo
#把所有baseurl前面的注释#去掉
#把所有mirrorlist前面加上注释#</pre>
<div class="blog_h3"><span class="graybg">如何支持Zeroconf</span></div>
<pre class="crayon-plain-tag"># Zeroconf（Multicast DNS）规范的开源实现
# 它可以在没有DNS服务的局域网里发现基于zeroconf协议的设备和服务
yum install avahi
# GNU Name Service Switch (NSS)插件，允许使用*.local风格的域名发现
yum install nss-mdns
# 启动服务
service avahi-daemon start</pre>
<div class="blog_h3"><span class="graybg">如何禁用网络管理器</span></div>
<p>首先，停止网络管理器服务，并禁用之：</p>
<pre class="crayon-plain-tag">systemctl stop NetworkManager.service 
systemctl disable NetworkManager.service</pre>
<p>对于/etc/sysconfig/network-scripts/ifcfg-*，你需要设置：</p>
<pre class="crayon-plain-tag">NM_CONTROLLED=no
ONBOOT=yes</pre>
<p>启动传统的网络服务：</p>
<pre class="crayon-plain-tag"># 确保网络服务启用
chkconfig network on
# 手工启动一下服务
service network start 
# 即使使用传统网络服务，/etc/sysconfig/network-scripts/中的脚本仍然有效</pre>
<div class="blog_h3"><span class="graybg">如何禁用防火墙</span></div>
<pre class="crayon-plain-tag"># 禁用
systemctl disable firewalld
# 停止服务
systemctl stop firewalld</pre>
<div class="blog_h3"><span class="graybg">如何安装类似Ubuntu的build-essential软件包</span></div>
<pre class="crayon-plain-tag">yum groupinstall 'Development Tools'</pre>
<div class="blog_h3"><span class="graybg">无法Ping通localhost</span></div>
<p>报错：ping: unknown host localhost</p>
<p>解决办法：</p>
<ol>
<li>首先查看/etc/hosts，确保存在这样的条目：<pre class="crayon-plain-tag">127.0.0.1 localhost</pre> </li>
<li>然后，确保/etc/nsswitch.conf的访问权限正确：<br />
<pre class="crayon-plain-tag">sudo chmod 644 /etc/nsswitch.conf </pre></p>
<p>并具有合适的配置： </p>
<pre class="crayon-plain-tag"># 应当包含files
hosts:      files dns</pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">如何在CentOS 7上安装MySQL</span></div>
<p><pre class="crayon-plain-tag">wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm
rpm -ivh mysql-community-release-el7-5.noarch.rpm

# 安装MySQL
yum install mysql-server
systemctl start mysqld

# 执行此脚本，修改root密码、移除匿名用户，等等
mysql_secure_installation</pre>
<div class="blog_h3"><span class="graybg">无法使用netstat和ifconfig</span></div>
<pre class="crayon-plain-tag">yum install -y net-tools</pre>
<div class="blog_h3"><span class="graybg">如何修改主机名称</span></div>
<p>在CentOS 7中，下列命令可以用来修改主机名：</p>
<ol>
<li>hostnamectl：修改系统主机名，推荐的方式</li>
<li>nmtui：基于文本用户接口（TUI）修改系统主机名</li>
<li>nmcli：基于NetworkManager的CLI修改主机名</li>
</ol>
<p>主机名可以分为几个类型：</p>
<ol>
<li>静态主机名：由系统管理员分配，例如tokyo.gmem.cc</li>
<li>临时/动态主机名：由DHCP或者mDNS服务器运行时分配</li>
<li>Pretty host name：由系统管理员或者最终用户分配的UTF-8兼容的、自由格式的主机名</li>
</ol>
<p>查看主机名信息：</p>
<pre class="crayon-plain-tag">hostnamectl status
#  Static hostname: localhost.localdomain
#         Icon name: computer-vm
#           Chassis: vm
#        Machine ID: a179e3668a70483691a57509c0044cc4
#           Boot ID: 1b264d45e8db4fc6a8655f6451de32e9
#    Virtualization: qemu
#  Operating System: CentOS Linux 7 (Core)
#       CPE OS Name: cpe:/o:centos:centos:7
#            Kernel: Linux 3.10.0-327.28.3.el7.x86_64
#      Architecture: x86-64</pre>
<p>设置主机名：</p>
<pre class="crayon-plain-tag">hostnamectl set-hostname cos7-0
hostnamectl set-hostname "CentOS 7 base" --pretty
hostnamectl set-hostname cos7-0 --static
hostnamectl set-hostname cos7-0 --transient</pre>
<div class="blog_h3"><span class="graybg">如何修改Terminal字体</span></div>
<p>通过控制台登录到文字界面时，可以使用下面的命令设置字体：</p>
<pre class="crayon-plain-tag"># 下载新字体
yum install terminus-fonts-console

# 设置字体
setfont ter-116n.psf.gz

# 持久化设置
echo setfont ter-116n.psf.gz &gt;&gt; /etc/rc.local</pre>
<div class="blog_h3"><span class="graybg">如何设置提示符</span></div>
<pre class="crayon-plain-tag">PS1="\e[1;32m\u@\h\e[39m:\e[1;34m\w\e[39m\\$ \e[0;39m"</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/centos-faq">CentOS知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/centos-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SpringMVC知识集锦</title>
		<link>https://blog.gmem.cc/springmvc-faq</link>
		<comments>https://blog.gmem.cc/springmvc-faq#comments</comments>
		<pubDate>Sun, 09 Sep 2012 03:53:28 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[ExtJS]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[MVC]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1224</guid>
		<description><![CDATA[<p>基于仿冒的SpringMVC测试用例示意 [crayon-69e8e2fb3b14f796994514/] 常用问题 强制产生JSON格式的结果 配置[crayon-69e8e2fb3b154932386975-i/]  SpringMVC 设置 /* 过滤全部，导致JSP无法显示的问题 [crayon-69e8e2fb3b156862475883/] 找不到控制器类的问题：No mapping found for HTTP request with URI 注意URL拦截的前置部分，如/admin，不要再控制器的映射路径中体现，例如地址/admin/user在控制器中直接配： /user [crayon-69e8e2fb3b158776607767/] 使用Ajax配合@ResponseBody时出现乱码 <a class="read-more" href="https://blog.gmem.cc/springmvc-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/springmvc-faq">SpringMVC知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">基于仿冒的SpringMVC测试用例示意</span></div>
<pre class="crayon-plain-tag">package cc.gmem.demo.ctrl;

import static org.junit.Assert.*;

import javax.inject.Inject;

import net.greenmemory.commons.lang.StringUtils;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.LoggerFactory;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import com.fasterxml.jackson.databind.ObjectMapper;

@RunWith ( SpringJUnit4ClassRunner.class )
@ContextConfiguration ( locations = { "/spring-classgen-disabled.xml", "/spring-mvc.xml" } )
@TransactionConfiguration ( transactionManager = "txManager", defaultRollback = true )
public class SpringMVCControllerTest
{
    private static final org.slf4j.Logger LOGGER       = LoggerFactory.getLogger( SpringMVCControllerTest.class );

    @Inject
    private RequestMappingHandlerAdapter  handlerAdapter;

    @Inject
    private RequestMappingHandlerMapping  handlerMapping;

    @Inject
    private SessionFactory                sf;

    @Inject
    private ObjectMapper                  om;

    private MockHttpServletRequest        request;

    private MockHttpServletResponse       response;

    @Before
    @Transactional
    public void setUp()
    {
        request = new MockHttpServletRequest();
        response = new MockHttpServletResponse();

        Session sess = sf.getCurrentSession();
    }

    @Test
    @Transactional
    public void testController() throws Exception
    {
        request.setRequestURI( "/uri/" ); //设置请求的URI
        Object handler = handlerMapping.getHandler( request ).getHandler(); //获取Handler对象（就是控制器类）
        handlerAdapter.handle( request, response, handler ); //转发给Handler处理，并获取响应
        String contentType = response.getContentType();
        String json = response.getContentAsString();
        assertTrue( StringUtils.containsIgnoreCase( contentType, "json" ) );
        assertTrue( StringUtils.containsIgnoreCase( contentType, "utf-8" ) );
    }
}</pre>
<div class="blog_h2"><span class="graybg">常用问题</span></div>
<div class="blog_h3"><span class="graybg">强制产生JSON格式的结果</span></div>
<p>配置<pre class="crayon-plain-tag">@RequestMapping( produces = "application/json" )</pre> </p>
<div>
<div class="blog_h3"><span class="graybg">SpringMVC 设置 /* 过滤全部，导致JSP无法显示的问题</span></div>
</div>
<pre class="crayon-plain-tag">&lt;!--下面的URL模式会匹配所有url：路径型的和后缀型的url(包括/login,*.jsp,*.js和*.html等)--&gt;
&lt;url-pattern&gt;/*&lt;/url-pattern&gt;
&lt;!--解决：下面会匹配到/login这样的路径型url，不会匹配到模式为*.jsp这样的后缀型url--&gt;
&lt;url-pattern&gt;/&lt;/url-pattern&gt;</pre>
<div>
<div class="blog_h3"><span class="graybg">找不到控制器类的问题：No mapping found for HTTP request with URI</span></div>
</div>
<div>
<ol>
<li>注意URL拦截的前置部分，如/admin，不要再控制器的映射路径中体现，例如地址/admin/user在控制器中直接配： /user<br />
<pre class="crayon-plain-tag">&lt;url-pattern&gt;/admin/*&lt;/url-pattern&gt;</pre>
</li>
</ol>
</div>
<div>
<div class="blog_h3"><span class="graybg">使用Ajax配合@ResponseBody时出现乱码</span></div>
</div>
<p>要注意JavaScript组件的编码问题，例如使用ExtJS的时候可以这样设置：</p>
<pre class="crayon-plain-tag">Ext.Ajax.defaultHeaders = {
   'Accept' : 'application/json',
   'Accept-Charset' : 'UTF-8'
};</pre>
<div>
<div class="blog_h3"><span class="graybg">SpringMVC重复扫描Spring配置中的包导致错误</span></div>
</div>
<p>可能报错：<br /> org.hibernate.HibernateException: No Session found for current thread<br /> SpringMVC作为子ApplicationContext时，不要扫描应用层的任何类，只扫描MVC控制器。可以把控制器全部放在ctrl包里面，并设置Spring扫描规则：</p>
<pre class="crayon-plain-tag">&lt;context:component-scan base-package="cc.gmem.demo.ctrl"&gt;
    &lt;context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration" /&gt;
&lt;/context:component-scan&gt;</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/springmvc-faq">SpringMVC知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/springmvc-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Hibernate知识集锦</title>
		<link>https://blog.gmem.cc/hibernate-faq</link>
		<comments>https://blog.gmem.cc/hibernate-faq#comments</comments>
		<pubDate>Wed, 11 Apr 2012 04:28:04 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[Hibernate]]></category>
		<category><![CDATA[HQL]]></category>
		<category><![CDATA[XRM]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1233</guid>
		<description><![CDATA[<p>重要概念 get和load 在Hibernate 3.6.x中： get会返回实际的对象实例，如果不存在则返回null，session.get(Superclass.class,id)与session.get(Concreteclass,id)的返回值完全一致。 load则返回一个生成的代理（类名以类似_$$_javassist_*结尾），即使给定ID的对象不存在也可以得到代理实例，只有第一次访问其非ID属性时才会从数据库寻找实例，找不到抛出异常。代理的父类就是session.load(Class.class,id)中指定的类，如果在多态映射时不指定具体类型，则不能cast为实际类型。 指定ManyToOne关的FetchType为Lazy时，可能导致关联实体使用类似于load的方式加载（调用SessionImplementor.internalLoad方法），除非指定为其指定注解：@NotFound ( action = NotFoundAction.IGNORE )，相关的Hibernate代码： [crayon-69e8e2fb3b461901078972/] [crayon-69e8e2fb3b464120618692/] 因此使用多态的ManyToOne时，要确保关联对象的类型准确，要么禁止延迟加载，要么启用延迟加载的同时打开 NotFoundAction.IGNORE 实体的状态 状态 说明 New (Transient) 新创建的对象，并且从来没有和Hibernate会话（持久化上下文，persistence <a class="read-more" href="https://blog.gmem.cc/hibernate-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/hibernate-faq">Hibernate知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2">重要概念</div>
<div class="blog_h3"><span class="graybg">get和load</span></div>
<p>在Hibernate 3.6.x中：</p>
<p>get会返回实际的对象实例，如果不存在则返回null，session.get(Superclass.class,id)与session.get(Concreteclass,id)的返回值完全一致。</p>
<p>load则返回一个生成的代理（类名以类似_$$_javassist_*结尾），即使给定ID的对象不存在也可以得到代理实例，只有第一次访问其非ID属性时才会从数据库寻找实例，找不到抛出异常。代理的父类就是session.load(Class.class,id)中指定的类，如果在<span style="background-color: #c0c0c0;">多态映射时不指定具体类型，则不能cast为实际类型</span>。</p>
<p>指定ManyToOne关的FetchType为Lazy时，可能导致关联实体使用类似于load的方式加载（调用SessionImplementor.internalLoad方法），除非指定为其指定注解：@NotFound ( action = NotFoundAction.IGNORE )，相关的Hibernate代码：</p>
<pre class="crayon-plain-tag">protected final Object resolveIdentifier(Serializable id, SessionImplementor session) throws HibernateException {
	boolean isProxyUnwrapEnabled = unwrapProxy &amp;&amp;
			session.getFactory()
					.getEntityPersister( getAssociatedEntityName() )
					.isInstrumented( session.getEntityMode() );
        //EntityType是ManyToOneType的父类，后者的isNullable() == ignoreNotFound，对应@NotFound注解
	Object proxyOrEntity = session.internalLoad(
			getAssociatedEntityName(),
			id,
			eager,
			isNullable() &amp;&amp; !isProxyUnwrapEnabled
	);

	if ( proxyOrEntity instanceof HibernateProxy ) {
		( ( HibernateProxy ) proxyOrEntity ).getHibernateLazyInitializer()
				.setUnwrap( isProxyUnwrapEnabled );
	}

	return proxyOrEntity;
}</pre><br />
<pre class="crayon-plain-tag">public Object internalLoad(String entityName, Serializable id, boolean eager, boolean nullable) throws HibernateException {
		// todo : remove
		LoadEventListener.LoadType type = nullable
                                //如果设置NotFoundAction.IGNORE，则走该分支，不创建代理
				? LoadEventListener.INTERNAL_LOAD_NULLABLE
				: eager
						? LoadEventListener.INTERNAL_LOAD_EAGER //不创建代理
						: LoadEventListener.INTERNAL_LOAD_LAZY; //只有这个会进行代理的创建
		LoadEvent event = new LoadEvent(id, entityName, true, this);
		fireLoad(event, type);
		if ( !nullable ) {
			UnresolvableObjectException.throwIfNull( event.getResult(), id, entityName );
		}
		return event.getResult();
	}</pre>
<p>因此使用多态的ManyToOne时，要确保关联对象的类型准确，要么禁止延迟加载，要么启用延迟加载的同时打开 NotFoundAction.IGNORE</p>
<div class="blog_h3"><span class="graybg">实体的状态</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">状态</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>New (Transient)</td>
<td>
<p>新创建的对象，并且从来没有和Hibernate会话（持久化上下文，persistence context）关联过，没有和数据库表的一行关联</p>
<p>要将瞬时对象变为持久化对象，你需要明确的调用持久化API，例如persist</p>
</td>
</tr>
<tr>
<td>Persistent (Managed)</td>
<td>已经关联到数据库表的一行，并且被当前Hibernate管理。对这类对象的<span style="background-color: #c0c0c0;">任何修改都被自动检测到，并自动在flush期间入库</span></td>
</tr>
<tr>
<td>Detached</td>
<td>
<p>如果当前Hibernate会话关闭，则先前Persistent对象变为Detached，之后对实体的更新不会被跟踪因而不会自动入库</p>
<p>要把Detached对象重新关联到Hibernate会话，你可以：</p>
<ol>
<li>Reattaching，通过session.update()方法。注意：在一个会话中，对于每个数据库行，只能有一个JVM对象与之关联</li>
<li>Merging，拷贝Detached对象的字段到一个Managed对象中，如果当前会话中对应Persistent尚不存在则从数据库中抓取。在此操作后，Detached对象的持久化状态不变</li>
</ol>
<p>对于配置了自动生成ID的实体类型，如果手工赋予了ID，会被看作是Detached，即使它是new出来的没有入库过的对象。对这种对象调用session.persist()会导致错误：detached entity passed to persist</p>
</td>
</tr>
<tr>
<td>Removed</td>
<td>
<p>实体对象被从数据库中删除，实际删除动作在flush时发生</p>
<p>JPA要求仅仅Managed对象才可以被删除，但是Hibernate允许调用session.delete删除Detached对象</p>
</td>
</tr>
</tbody>
</table>
<p>实体状态转换示意图：</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2012/04/hibernate-entity-state-trans.png"><img class="alignnone size-full wp-image-15172" src="https://blog.gmem.cc/wp-content/uploads/2012/04/hibernate-entity-state-trans.png" alt="hibernate-entity-state-trans" width="100%" /></a></p>
<div class="blog_h3"><span class="graybg">持久化API</span></div>
<p>本节以如下实体来描述常用持久化API的行为：</p>
<pre class="crayon-plain-tag">@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;

    private String name;
 
}</pre>
<p>注意下表中的API都不会立即导致SQL操作，SQL操作仅仅在flush时执行。</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">session.</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>persist</td>
<td>用于将瞬时（new）对象转换为受管状态，例如：<br />
<pre class="crayon-plain-tag">Person person = new Person();
person.setName("Alex");
session.persist(person);</pre></p>
<p>注意该方法的返回值为void，它是直接把入参对象纳入受管状态，而非进行对象状态的拷贝。它的语义严格遵守JSR-220规范：</p>
<ol>
<li>导致瞬时对象变为受管状态，并且对cascade=PERSIST|ALL的关联对象级联此操作</li>
<li>如果对象已经处于受管状态，调用此API无效果，但是级联操作仍然被执行</li>
<li>如果对象处于Detached状态，在此API调用后或者提交/flush时抛出异常</li>
<li>不保证id立即生成 —— <span style="background-color: #c0c0c0;">调用此方法后id可以为null</span>，无论id的生成策略。规范允许在提交/flush时生成id</li>
</ol>
</td>
</tr>
<tr>
<td>save</td>
<td>
<p>该方法是Hibernate原始的持久化接口，不遵守JPA规范。其行为类似于persist但是实现细节不同</p>
<p>该方法执行后，<span style="background-color: #c0c0c0;">立即返回为瞬时对象分配的id</span></p>
<p>如果对Detached对象执行调用save()，<span style="background-color: #c0c0c0;">会生成一个新的id</span></p>
</td>
</tr>
<tr>
<td>update</td>
<td>
<p>该方法是Hibernate原始的持久化接口：</p>
<ol>
<li>在入参对象上进行操作，返回值是void。将入参对象从detached转换为persistent</li>
<li>如果你传入transient对象，抛出异常</li>
</ol>
</td>
</tr>
<tr>
<td>merge</td>
<td>
<p>逻辑如下：</p>
<ol>
<li>根据id从数据库中加载实体</li>
<li>从入参对象拷贝字段到加载的实体</li>
<li>返回加载的实体，该对象与入参是两个对象</li>
</ol>
<p>JSR-220的语义被遵守：</p>
<ol>
<li>如果实体是detached，则拷贝字段到加载的受管实体上</li>
<li>如果实体是transient，则拷贝字段到新建的受管实体上</li>
<li>级联关联的cascade=MERGE|ALL对象</li>
<li>如果实体目前已经是受管的，不拷贝字段，但是级联操作仍然进行</li>
</ol>
</td>
</tr>
<tr>
<td>saveOrUpdate</td>
<td>
<p>Hibernate的特殊API，JPA中没有对应的接口</p>
<p>行为类似于update，可以被用来reattaching一个实例，不管它是transient还是detached</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">Hibernate3使用XRM（EntityMode=dom4j），修改query.list()的dom元素后报错</span></div>
<p>报错信息：<br /> org.springframework.orm.hibernate3.HibernateSystemException: identifier of an instance of *** was altered from 248178 to null; nested exception is org.hibernate.HibernateException: identifier of an instance of wnetJhInterface was altered from 248178 to null<br /> at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:690)<br /> 解决：查询完成后，调用sess.clear();</p>
<div class="blog_h3"><span class="graybg">Hibernate HQL is null问题</span></div>
<p>HQL应该写成：prop is null。不能用参数： prop is :p 并把值传进去</p>
<div class="blog_h3"><span class="graybg">Hibernate setParameter()报错</span></div>
<p>报错信息：Position beyond number of declared ordinal parameters. Remember that ordinal parameters are 1-based! Position: 2<br /> 报错原因：位置参数第一个设置为0，和JDBC不一样，JDBC的索引以1开始</p>
<div class="blog_h3"><span class="graybg">Hibernate setParameter()传递对象给位置参数?报错</span></div>
<p>报错信息：Expected positional parameter count: ?, actual parameters ...<br /> 报错原因：未知，可以使用命名参数（named parameters）解决</p>
<div class="blog_h3"><span class="graybg">如何为HQL命名参数传递多个值(IN子句)</span></div>
<pre class="crayon-plain-tag">String hql = "update user where id in (:id)";
sess = getSessionFactory().getCurrentSession();
Session msess = sess.getSession( EntityMode.MAP );
Query q = msess.createQuery( hql.toString() );
List&lt;Object&gt; params = new ArrayList&lt;Object&gt;();
for ( Map&lt;String, ?&gt; d : dataSet.getAllData() )
{
    params.add( d.get( "id") );
}
q.setParameterList( "id", params );</pre>
<div class="blog_h3"><span class="graybg">Hibernate序列化对象时出错</span></div>
<p>报错信息：org.hibernate.type.SerializationException: could not serialize</p>
<p>报错原因：某些时候Hibernate需要使用Java串行化机制来序列化对象，因此，必须在注解@Transient的同时，为字段添加transient关键字，这样Java串行化机制才能忽略该字段。</p>
<pre class="crayon-plain-tag">@Inject
@Transient
@JsonIgnore
private  transient ServiceHelper     service;</pre>
<div class="blog_h3"><span class="graybg">HQL报错：org.hibernate.QueryException: could not resolve property</span></div>
<ol>
<li>非实体属性不能存在于HQL的SELECT或者WHERE子句中</li>
<li>对于任意多态映射（@Any），即使关联的所有类型的实体均有属性A（通过@MappedSuperclass指定），A亦不能出现在SELECT或者WHERE子句中</li>
</ol>
<div class="blog_h3"><span class="graybg">@AnyMetaDef的@MetaValue的targetEntity不精确到具体子类时，无法保存元数据列的问题</span></div>
<p>在Hibernate 3.6.x中，使用任意多态映射时，如果其中某个关联目标是一个实体类层次，例如：</p>
<pre class="crayon-plain-tag">@AnyMetaDef (
    name = "adminMetaDef",
    idType = "integer",
    metaType = "integer",
    metaValues = {
        @MetaValue ( value = "1", targetEntity = Child.class ),
        //如果Org是一个抽象实体类，其下面有若干个具体实体子类，则在该配置下，任何子实体均无法关联上
        @MetaValue ( value = "2", targetEntity = Org.class )
    } 
)
@Any ( metaDef = "adminMetaDef", metaColumn = @Column ( name = "ADMIN_TYPE") )
@JoinColumn ( name = "ADMIN_ID" )
private Admin mainAdmin;</pre>
<p>则必须把所有子类的@MetaValue全部定义出来，否则在入库时元数据列ADMIN_TYPE保存为null，导致后续无法读取出mainAdmin属性。下面的补丁解决此问题：</p>
<pre class="crayon-plain-tag">public void nullSafeSet( PreparedStatement st, Object value, int index, SessionImplementor session )
        throws HibernateException, SQLException
{
    baseType.nullSafeSet( st, value == null ? null : getKey( value ), index, session );
}
private Object getSuperclass( Object clsName )
{
    try
    {
        Class&lt;?&gt; c = Class.forName( clsName.toString() );
        return c.getSuperclass().getName();
    }
    catch ( Exception e )
    {
        return null;
    }
}

private Object getKey( Object clsName )
{
    Object key = this.keys.get( clsName );
    if ( key == null )
    {
        return getKey( getSuperclass( clsName ) );
    }
    return key;
}</pre>
<p>应用上述补丁之后，加载对象后，发现mainAdmin属性的类型与其真实类型不对应，其类型是@metaValue中声明的Org类，下面的补丁解决此问题：</p>
<pre class="crayon-plain-tag">//修改此文件的130、210、216行，改为调用下面的方法：

private Object internalLoad( String entityName, Serializable id, SessionImplementor session )
{
    //这边第三个参数改为true，即禁止了延迟加载，避免了代理对象类型不正确的问题
    return session.internalLoad( entityName, id, true, false );
}
//上面的方法返回的对象不是代理，和原代码语义可能存在不一致（本人未验证）</pre>
<div class="blog_h2"><span class="graybg">配置示例</span></div>
<div class="blog_h3"><span class="graybg">Spring配置：Hibernate4</span></div>
<pre class="crayon-plain-tag">&lt;bean id="sessionFactory" p:dataSource-ref="dataSource" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean"&gt;
        &lt;property name="packagesToScan"&gt;
            &lt;list&gt;
                &lt;value&gt;cc.gmem.mgr.*.model&lt;/value&gt;
            &lt;/list&gt;
        &lt;/property&gt;
        &lt;property name="hibernateProperties"&gt;
            &lt;value&gt;
                hibernate.dialect=org.hibernate.dialect.Oracle10gDialect
                hibernate.jdbc.batch_size=20
                hibernate.show_sql=false
                hibernate.format_sql=false
                hibernate.generate_statistics=true
                hibernate.transaction.factory_class=org.hibernate.transaction.JDBCTransactionFactory
                hibernate.current_session_context_class=org.springframework.orm.hibernate4.SpringSessionContext
            &lt;/value&gt;
        &lt;/property&gt;
    &lt;/bean&gt;
    &lt;bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"
        p:sessionFactory-ref="sessionFactory" /&gt;</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/hibernate-faq">Hibernate知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/hibernate-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ubuntu知识集锦</title>
		<link>https://blog.gmem.cc/ubuntu-faq</link>
		<comments>https://blog.gmem.cc/ubuntu-faq#comments</comments>
		<pubDate>Sun, 23 Oct 2011 05:17:06 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Linux]]></category>
		<category><![CDATA[FAQ]]></category>
		<category><![CDATA[Ubuntu]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=1389</guid>
		<description><![CDATA[<p>基本知识 名词术语 PPA：Personal Package Archives，可以上传source，作为apt存储库 APT：Advanced Packaging Tool，Debian GNU/Linux distribution及其变体上进行软件安装/更新/删除的工具，它简化了类linux系统的软件管理，可以进行二进制的安装或者源码编译的安装。最初是设计为pdkg的前端（管理debian的deb包），后来通过 apt-rpm组件支持RPM包 DPKG：dpkg是Debian包管理系统的核心，apt-get、aptitude本质上是调用dpkg 进程基本知识 进程优先级：数字越小越高，[+20,-20] 进程状态 D：不可中断的睡眠（例如I/O导致，处于该状态的进程，只有内核才能将其唤醒） R：运行中 S：可中断的睡眠，正在等待信号 T：终结的进程 Z：僵尸进程，终止了，但是需要清理kill（通常看不到，僵尸很快被清理） 串口通信知识 何为串行接口 <a class="read-more" href="https://blog.gmem.cc/ubuntu-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/ubuntu-faq">Ubuntu知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基本知识</span></div>
<div class="blog_h2"><span class="graybg">名词术语</span></div>
<ol>
<li>PPA：Personal Package Archives，可以上传source，作为apt存储库</li>
<li>APT：Advanced Packaging Tool，Debian GNU/Linux distribution及其变体上进行软件安装/更新/删除的工具，它简化了类linux系统的软件管理，可以进行二进制的安装或者源码编译的安装。最初是设计为pdkg的前端（管理debian的deb包），后来通过 apt-rpm组件支持RPM包</li>
<li>DPKG：dpkg是Debian包管理系统的核心，apt-get、aptitude本质上是调用dpkg</li>
</ol>
<div class="blog_h2"><span class="graybg">进程基本知识</span></div>
<p><strong>进程优先级</strong>：数字越小越高，[+20,-20]<br /> 进程状态<br /> D：不可中断的睡眠（例如I/O导致，处于该状态的进程，只有内核才能将其唤醒）<br /> R：运行中<br /> S：可中断的睡眠，正在等待信号<br /> T：终结的进程<br /> Z：僵尸进程，终止了，但是需要清理kill（通常看不到，僵尸很快被清理）</p>
<div class="blog_h2"><span class="graybg">串口通信知识</span></div>
<div class="blog_h3"><span class="graybg">何为串行接口</span></div>
<p>串行端口（serial port）可以对应计算机的一个<span style="background-color: #c0c0c0;">物理接口</span>：DB9串行接口、DB25串行接口、RJ-45串行接口；或者对应计算中的内置设备：调制解调器、PC卡、USB-串口转换器、扩展坞。</p>
<p>通常计算机包含一个UART（通用异步收发传输器）芯片，但可能没有物理接口（笔记本通常在扩展坞上才有DB-9口）。</p>
<div class="blog_h2"><span class="graybg">常用快捷键</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">快捷键</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Ctrl + L</td>
<td>在文件管理器（Nautilus）中显示完整路径</td>
</tr>
<tr>
<td>Ctrl + Alt + 方向</td>
<td>切换工作区</td>
</tr>
<tr>
<td>Ctrl + Shift + Alt + 方向</td>
<td>把当前窗口移动到目标工作区</td>
</tr>
<tr>
<td>Ctrl + Alt + L</td>
<td>锁屏，可以改为Super + L</td>
</tr>
<tr>
<td>Alt + F2</td>
<td>搜索</td>
</tr>
<tr>
<td>Ctrl + Alt + F1</td>
<td>进入文字终端界面，类似的可以组合F2-F6</td>
</tr>
<tr>
<td>Ctrl + Alt + F7</td>
<td>切换到GUI终端</td>
</tr>
</tbody>
</table>
<div class="blog_h1">常用配置文件</div>
<div class="blog_h2"><span class="graybg">APT配置文件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">配置文件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>/etc/apt/sources.list</td>
<td>
<p>用来获取软件包的源列表，此配置文件用于支持任意数量的软件源。该文件的每一行对应一个源，最优先的源列在最前面</p>
<p>每一行以“类型”开头，类型包括deb、deb-src等。deb类型典型情况下引用一个两级的Debian archive：distribution/component。其中distribution通常是一个archive名称，例如stable、testing或者是trusty、jessie之类的codename；而component通常可以是main、contrib、non-free等</p>
<p>deb-src类型的格式与deb一样，用于指定源码位置</p>
<p>内容示例：</p>
<pre class="crayon-plain-tag"># deb、deb-src的格式：
# deb [ options ] uri suite [component1] [component2] [...]

# 通过HTTP访问归档，使用trusty/main、trusty/restricted区域
deb http://mirrors.aliyun.com/ubuntu/ trusty main restricted

# 通过本地文件系统访问归档，使用stable/main、stable/contrib、stable/non-free
deb file:/home/jason/debian stable main contrib non-free</pre>
</td>
</tr>
<tr>
<td>/etc/apt/sources.list.d/</td>
<td>额外的源片段</td>
</tr>
<tr>
<td>/etc/apt/apt.conf<br />/etc/apt/apt.conf.d</td>
<td>APT工具包所有命令所共享的主配置文件</td>
</tr>
<tr>
<td>/etc/apt/preferences<br />/etc/apt/preferences.d/</td>
<td>
<p>APT参数配置，用于控制什么版本的包被选择并安装
<p>当源列表中包含某个包的多个distribution时（例如testing、stable），那么一个包的<span style="background-color: #c0c0c0;">多个版本</span>可以被安装，APT会为每个版本分配一个优先级。在保证依赖约束的前提下，apt-get会尝试安装优先级最高的版本。通过该配置文件，我们可以覆盖APT给包授予的默认优先级</p>
<p>当源列表中包含多个源时，某个软件包的<span style="background-color: #c0c0c0;">同一版本</span>可以具有多个实例（来源），这种情况下，apt-get会<span style="background-color: #c0c0c0;">选取源列表中第一个包含目标包</span>的源，该配置文件不会影响此行为</p>
<p>如果此配置文件中没有指定，某个版本的<span style="background-color: #c0c0c0;">默认</span>优先级等于其<span style="background-color: #c0c0c0;">所属的distribution的优先级</span></p>
<p>你可以在apt.conf或者apt-get命令行指定某个distribution为target release，使之获得高优先级，例如：</p>
<pre class="crayon-plain-tag">apt-get install -t testing some-package</pre>
<p>如果target release已经设置，APT使用如下算法来设置包的<span style="background-color: #c0c0c0;">版本对应的优先级</span>：</p>
<ol>
<li>优先级1： 来自那些在Release files中标注了NotAutomatic: yes、没标注ButAutomaticUpgrades: yes的archives（例如Debian<br /> experimental archive）中的版本</li>
<li>优先级100：已经安装的版本；来自那些在Release files中标注了NotAutomatic: yes、ButAutomaticUpgrades: yes的archives（例如Debian backports archive）中的版本</li>
<li>优先级500：未安装的、且不属于target release的版本</li>
<li>优先级990：未安装的、且属于target release的版本</li>
</ol>
<p>如果target release未设置，APT为所有已安装版本授予优先级100、所有未安装版本授予优先级500</p>
</td>
</tr>
<tr>
<td>/var/cache/apt/archives/</td>
<td>获取的软件包的存储位置</td>
</tr>
<tr>
<td>/var/cache/apt/archives/partial/</td>
<td>正在传输中的软件包文件片段</td>
</tr>
<tr>
<td>/var/lib/apt/lists/partial/</td>
<td>传输中的，存储sources.list中每一个包资源的状态信息</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常用命令</span></div>
<div class="blog_h2"><span class="graybg">dpkg</span></div>
<p>dpkg是安装、构建、管理和移除Debian软件包的工具，更加友好易用的dpkg前端工具是aptitude。该命令的语法如下：</p>
<pre class="crayon-plain-tag">dpkg [option...] action</pre>
<div class="blog_h3"><span class="graybg">常用动作</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">动作</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>安装(-i)</td>
<td>
<p>-i, --install package-file...</p>
<p>安装指定的软件包。如果指定选项-R，则package-file必须是一个目录。安装过程包含以下步骤：</p>
<ol>
<li>抽取新包的控制文件</li>
<li>如果软件包的另外一个版本已经安装在当前系统上，则旧版本的prerm脚本被执行</li>
<li>运行软件包的preinst脚本</li>
<li>解包新的文件，同时备份旧文件</li>
<li>如果软件包的另外一个版本已经安装在当前系统上，则旧版本的postrm脚本被执行</li>
<li>配置软件包</li>
</ol>
</td>
</tr>
<tr>
<td>解包(--unpack)</td>
<td>
<p>--unpack package-file...</p>
<p>解包，但不配置软件包。如果指定选项-R，则package-file必须是一个目录</p>
</td>
</tr>
<tr>
<td>配置(--configure)</td>
<td>
<p>--configure package...|-a|--pending</p>
<p>配置已经解包、尚未配置的软件包。如果参数packge缺失而-a或--pending存在，则所有解包且尚未配置的软件包都被配置</p>
<p>要重新配置已安装的软件包，可以使用dpkg-reconfigure命令</p>
<p>配置过程包括以下步骤：</p>
<ol>
<li>解包配置文件，同时备份旧的配置文件</li>
<li>运行软件包提供的postinst脚本</li>
</ol>
</td>
</tr>
<tr>
<td>移除(-r)和清理(-P)</td>
<td>
<p>-r, --remove, -P, --purge package...|-a|--pending</p>
<p>移除/清理指定的软件包，-r移除除了配置文件以外的所有内容，-P则移除全部内容。移除过程包括以下步骤：</p>
<ol>
<li>运行prerm脚本</li>
<li>移除安装的文件</li>
<li>运行postrm脚本</li>
</ol>
</td>
</tr>
<tr>
<td>验证(-V)</td>
<td>
<p>-V, --verify [package-name...]</p>
<p>通过比较安装路径下的文件和数据库中元信息，验证软件包的完整性，如果不指定package-name则验证所有包的完整性</p>
</td>
</tr>
<tr>
<td>列出软件包(-l)</td>
<td>
<p>-l, --list package-name-pattern...</p>
<p>作为dpkg-query的前端。查询匹配模式的软件包</p>
</td>
</tr>
<tr>
<td>--set-selections</td>
<td>
<p>设置软件包管理的选区，从标准输入读取数据。数据包括两列：软件包名称、安装状态</p>
<p>安装状态可以取值：</p>
<ol>
<li>install  在本次软件包管理事务中，安装此软件包</li>
<li>hold   维持既有状态</li>
<li>deinstall  删除软件包</li>
<li>purge  删除软件包，并清理其遗留的配置文件</li>
</ol>
</td>
</tr>
<tr>
<td>--get-selections</td>
<td>
<p>--get-selections [package-name-pattern...]</p>
<p>列出选区中匹配模式的软件包，如果不指定package-name-pattern则非已安装的软件不被显示。输出两列：软件包名称、安装状态</p>
<pre class="crayon-plain-tag"># 列出选区中名称以ip开头的软件包
sudo dpkg --get-selections  ip* </pre>
</td>
</tr>
<tr>
<td>列出安装的文件(-l)</td>
<td>
<p>-L, --listfiles package-name...
<p>作为dpkg-query的前端。列出指定软件包在当前系统上安装的文件</p>
</td>
</tr>
<tr>
<td>状态(-s)</td>
<td>
<p>-s, --status package-name...</p>
<p>作为dpkg-query的前端。报告指定软件包的状态。可用的状态包括：</p>
<ol>
<li>not-installed 软件包没有安装到当前系统</li>
<li>config-files 仅软件包的配置文件存在于当前系统</li>
<li>half-installed 软件包的安装已经启动，但尚未完成</li>
<li>unpacked 软件包已经解包，尚未配置</li>
<li>half-configured 软件包的配置已经启动，但尚未完成</li>
<li>triggers-awaited 等待触发器处理</li>
<li>triggers-pending 已经被触发</li>
<li>installed 软件包已经安装到当前系统</li>
</ol>
</td>
</tr>
<tr>
<td>搜索文件(-S)</td>
<td>
<p>-S, --search filename-search-pattern...</p>
<p>作为dpkg-query的前端。从已安装的软件包中搜索文件</p>
<p><span style="background-color: #c0c0c0;">示例：</span></p>
<pre class="crayon-plain-tag"># 显示文件所属的软件包
dpkg -S /path/to/file</pre>
</td>
</tr>
<tr>
<td>信息(-p)</td>
<td>
<p>--print-avail package-name...
<p>作为dpkg-query的前端。显示软件包的详细信息</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">常用选项</span></div>
<ol>
<li>--admindir=dir 设置管理目录，默认/var/lib/dpkg，该目录中包含了软件包状态信息的记录文件</li>
<li>--instdir=dir 设置默认安装目录</li>
<li>-E, --skip-same-version 如果相同版本的软件包已经安装，则不执行安装</li>
<li>--no-debsig 不去尝试校验包的数字签名</li>
<li>--log=filename 记录安装日志到指定文件，默认/var/log/dpkg.log</li>
</ol>
<div class="blog_h2"><span class="graybg">apt-get</span></div>
<p>APT包的管理工具，是aptitude等工具的后端，dpkg的前端。命令格式：</p>
<pre class="crayon-plain-tag">apt-get 
  [-asqdyfmubV] [-o=config_string] [-c=config_file] [-t=target_release] [-a=architecture] 
  { 
    update | upgrade | dselect-upgrade | dist-upgrade |
    install pkg [{=pkg_version_number | /target_release}]...  | 
    remove pkg...  | purge pkg...  | 
    source pkg [{=pkg_version_number | /target_release}]...  |
    build-dep pkg [{=pkg_version_number | /target_release}]...  | 
    download pkg [{=pkg_version_number | /target_release}]...  | 
    check | clean | autoclean | autoremove | {-v | --version} | {-h | --help}
  }</pre>
<div class="blog_h3"><span class="graybg">安装特定版本</span></div>
<p>注意指定目标软件包时，可以在<span style="background-color: #c0c0c0;">包名后面</span>附加<pre class="crayon-plain-tag">=version</pre> 或者<pre class="crayon-plain-tag">/dist</pre> ，从而安装特定版本/从特定源安装：</p>
<pre class="crayon-plain-tag">sudo apt-cache policy  libssl1.0.0
# libssl1.0.0:
#   Installed: 1.0.1f-1ubuntu2.18
#   Candidate: 1.0.1f-1ubuntu2.18
#   Version table:
#*** 1.0.1f-1ubuntu2.18 0
#        100 /var/lib/dpkg/status
#    1.0.1f-1ubuntu2 0
#        500 http://mirrors.aliyun.com/ubuntu/ trusty/main amd64 Packages

# 从上面的输出可以看到，已经安装了1.0.1f-1ubuntu2.18 0版本
# 下面的命令可以强制改为从trusty安装，即1.0.1f-1ubuntu2 0版本：
sudo apt-get openssl/trusty
# 和上面的效果一样：
sudo apt-get install libssl1.0.0=1.0.1f-1ubuntu2</pre>
<div class="blog_h3"><span class="graybg">关于多架构</span></div>
<p>如果你同时安装某个软件包的多种架构（MultiArch），例如libssl1.0.0:i386和libssl1.0.0:amd64，则每个架构的版本必须完全一致。</p>
<div id="apt-get" class="blog_h3"><span class="graybg">子命令说明</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>install</td>
<td>
<p>下载软件包并安装/升级，目标软件包所直接/间接依赖的软件包也会被一并下载和安装。该命令首先下载并保存文件到本地仓库，因此卸载后再安装不需要重新下载</p>
<p>该命令可以后接多个需要安装的软件包，软件包<span style="background-color: #c0c0c0;">不指定全限定</span>的文件名（例如apt-utils而不是apt-utils_1.0.1ubuntu2.10_amd64.deb），配置文件/etc/apt/sources.list用于定位软件包。</p>
<p>如果某个软件包后面附上一个减号（-），那么软件包将被删除（如果已安装在本系统的话）</p>
<p>如果要安装特定的软件包版本，则使用<pre class="crayon-plain-tag">pkgname=version</pre> 的形式指定软件包，该特性可以用于软件包的降级。</p>
<p>通配符和正则式可以用来指定包名称</p>
</td>
</tr>
<tr>
<td>remove</td>
<td>卸载软件包。配置文件被保留</td>
</tr>
<tr>
<td>purge</td>
<td>
<p>卸载软件包。配置文件也被清除，示例：</p>
<pre class="crayon-plain-tag">sudo apt-get purge libmapnik* mapnik-* python-mapnik #可使用通配符</pre>
</td>
</tr>
<tr>
<td>autoremove</td>
<td>卸载系统中没有用到的软件包</td>
</tr>
<tr>
<td>clean</td>
<td>删除所有下载到本地仓库中的软件包文件，除了</td>
</tr>
<tr>
<td>autoclean</td>
<td>与clean类似，但是只删除哪些不太可能再次被使用的、旧版本的软件包</td>
</tr>
<tr>
<td>source</td>
<td>下载源代码</td>
</tr>
<tr>
<td>build-dep</td>
<td>
<p>安装构建某个软件包必须的所有依赖，示例：
<pre class="crayon-plain-tag">sudo apt-get build-dep gedit</pre>
</td>
</tr>
<tr>
<td>download</td>
<td>下载指定的二进制包到当前目录下</td>
</tr>
<tr>
<td>update</td>
<td>从软件源同步所有软件包的索引信息</td>
</tr>
<tr>
<td>upgrade</td>
<td>升级/etc/apt/sources.list中列出的、当前系统已经安装的全部软件包到最新版本</td>
</tr>
<tr>
<td>check</td>
<td>诊断工具，更新软件包缓存、检查损坏的包依赖关系</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">常用选项</span></div>
<table class=" full-width">
<thead>
<tr>
<td style="width: 35%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>--no-install-recommends</td>
<td>不把“推荐的（recommended）”的包作为依赖处理，不下载或安装</td>
</tr>
<tr>
<td>--install-suggests</td>
<td>把“建议的（suggested）”的包作为依赖处理，下载并安装</td>
</tr>
<tr>
<td>-d, --download-only</td>
<td>仅下载软件包，不解压或安装</td>
</tr>
<tr>
<td>--no-download</td>
<td>不去下载软件包，最好和-m联用来强制APT仅使用已经下载到本地仓库的.deb包</td>
</tr>
<tr>
<td>-f, --fix-broken</td>
<td>
<p>尝试修复损坏（冲突）的依赖（APT不允许系统中存在损坏的包依赖关系）。该选项和install/remove子命令联用时，为了推导出可能的解决方案，APT可能忽略任何软件包
<p>有时候该选项不能解决问题，必须手工处理，这意味着你需要使用类似<pre class="crayon-plain-tag">dkpg --remove</pre>  的命令强制删除某些导致依赖破坏的软件包</p>
</td>
</tr>
<tr>
<td>-m, --ignore-missing, --fix-missing</td>
<td>忽略缺失（或者下载后完整性检查失败）的包并继续后续的处理，如果缺失的包是通过命令行指定的（selected），则简单的忽略之</td>
</tr>
<tr>
<td>-q, --quiet</td>
<td>安静模式，不显示进度信息，可以指定值-q=N，N&lt;=2</td>
</tr>
<tr>
<td>-s, --simulate, --just-print, <br />--dry-run, --recon, --no-act</td>
<td>模拟安装，以查看对系统的影响</td>
</tr>
<tr>
<td>-y, --yes, --assume-yes</td>
<td>对所有提问回答“是”</td>
</tr>
<tr>
<td>-u, --show-upgraded</td>
<td>打印所有将被升级的软件包</td>
</tr>
<tr>
<td>-a, --host-architecture</td>
<td>控制<pre class="crayon-plain-tag">apt-get source --compile</pre> 下载与指定体系结构匹配的源码。默认情况下与build-architecture即当前机器的体系结构一致</td>
</tr>
<tr>
<td>-b, --compile, --build</td>
<td>在下载完毕后，编译源代码包</td>
</tr>
<tr>
<td>--no-upgrade</td>
<td>和install子命令联用时，不对既有包进行升级</td>
</tr>
<tr>
<td>--only-upgrade</td>
<td>和install子命令联用时，只进行升级，而不是安装新的软件包</td>
</tr>
<tr>
<td>--force-yes</td>
<td>强制对提示回答“是”，可能损坏系统</td>
</tr>
<tr>
<td>--print-uris</td>
<td>打印下载文件的URL等信息，而不是获取文件并安装</td>
</tr>
<tr>
<td>--no-remove</td>
<td>如果某些软件包需要被移除，立即退出</td>
</tr>
<tr>
<td>--auto-remove</td>
<td>自动移除不需要的依赖，和 autoremove子命令效果相同</td>
</tr>
<tr>
<td>--allow-unauthenticated</td>
<td>允许未验证的包</td>
</tr>
<tr>
<td>--show-progress </td>
<td>显示用户友好的进度信息</td>
</tr>
<tr>
<td>--target-release, --default-release<br />-t=target_release</td>
<td>控制策略引擎的默认输入，为指定的release指定990的优先级</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">apt-cache</span></div>
<p>该命令用于查询APT缓存，它可以对APT包缓存进行一系列的操作。改命令不会改变系统，但可以生成一系列有价值的信息。</p>
<div class="blog_h3"><span class="graybg" style="font-weight: bold;">子命令说明</span></div>
<table class=" full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>gencaches</td>
<td>生成APT包缓存，此命令会被所有需要缓存的命令隐含调用（当缓存不存在或者过期时）</td>
</tr>
<tr>
<td>showpkg pkg...</td>
<td>
<p>显示列出的包的信息，信息包括：</p>
<ol>
<li>可用的版本</li>
<li>反向依赖（reverse dependencies），反向依赖即依赖于pkg...的包</li>
<li>每个版本的正向依赖（forward dependencies），正向依赖即pkg...所依赖的包，正向依赖必须被满足（satisfied）</li>
</ol>
<p>例如<pre class="crayon-plain-tag">apt-cache showpkg libssl-dev</pre> 的输出如下：</p>
<p><pre class="crayon-plain-tag">Package: libssl-dev
Versions: 
1.0.1f-1ubuntu2  ...
Reverse Depends: 
  libssl-dev:i386,libssl-dev 1.0.1f-1ubuntu2
  libssl-dev:i386,libssl-dev 1.0.1f-1ubuntu2
  proftpd-dev,libssl-dev
  ...
Dependencies: 
# 1.0.1f-1ubuntu2版本的libssl-dev依赖于以下包，括号内是其版本
# 只有这些依赖包都被安装，libssl-dev才可以安装
1.0.1f-1ubuntu2 - libssl1.0.0 (5 1.0.1f-1ubuntu2) 
                  zlib1g-dev (0 (null)) libssl-doc (0 (null)) 
                  libssl-dev:i386 (3 1.0.1f-1ubuntu2) 
                  libssl-dev:i386 (6 1.0.1f-1ubuntu2) 
Provides: 
1.0.1f-1ubuntu2 - 
Reverse Provides:</pre>
</td>
</tr>
<tr>
<td>stat</td>
<td>显示缓存的统计信息 </td>
</tr>
<tr>
<td>showsrc pkg...</td>
<td>显示包对应的源码包的信息 </td>
</tr>
<tr>
<td>dump</td>
<td>显示缓存中所有包的简短列表，主要用于调试 </td>
</tr>
<tr>
<td>umet</td>
<td>打印缓存中所有为满足依赖（unmet dependencies）的概要信息 </td>
</tr>
<tr>
<td>show pkg... </td>
<td>提供类似于<pre class="crayon-plain-tag">dpkg --print-avail</pre> 的功能</td>
</tr>
<tr>
<td>search regex... </td>
<td>在所有可用包上进行全文的正则式检索，打印包名称、描述<br />--full，打印类似于show子命令的信息<br />--names-only，仅打印包名</td>
</tr>
<tr>
<td>depends pkg... </td>
<td>打印包的依赖列表，以及其它所有可能满足依赖的包</td>
</tr>
<tr>
<td>rdepends pkg...</td>
<td>显示包的反向依赖</td>
</tr>
<tr>
<td>pkgnames [prefix]</td>
<td>打印匹配的包名称</td>
</tr>
<tr>
<td>policy [pkg...]</td>
<td>
<p>用于调试和配置文件有关的问题，如果不加参数，它会打印所有源的优先级，否则打印目标包和优先级相关的详细信息</p>
<p>例如<pre class="crayon-plain-tag">apt-cache policy libssl1.0.0</pre> 的输出如下：</p>
<pre class="crayon-plain-tag">libssl1.0.0:
  # 已安装的版本
  Installed: 1.0.1f-1ubuntu2.18
  # 候选版本，不指定版本时，默认安装此版本
  Candidate: 1.0.1f-1ubuntu2.18
# 版本表格，格式：
#  *** &lt;some-version&gt; &lt;minimum-priority-to-consider&gt;
#     &lt;priority-of-this-instance&gt; &lt;repository1&gt;
#     &lt;priority-of-this-instance&gt; &lt;repository2&gt;
#  *** &lt;some-other-version&gt; &lt;minimum-priority-to-consider&gt;
#     &lt;priority-of-this-instance&gt; &lt;repository3&gt;
#     &lt;priority-of-this-instance&gt; &lt;repository4&gt;#
#
  Version table:
   *** 1.0.1f-1ubuntu2.18 0   # ***表示已安装的版本
        # 不设置target release时，已安装版本的优先级为100
        100 /var/lib/dpkg/status
     1.0.1f-1ubuntu2 0
        # 不设置target release时，未安装版本的优先级为500
        500 http://mirrors.aliyun.com/ubuntu/ trusty/main amd64 Packages</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">其它apt命令</span></div>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 150px; text-align: center;">命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>add-apt-repository</td>
<td>
<p>添加PPA存储库，示例：
<p><pre class="crayon-plain-tag"># 添加存储库
add-apt-repository ppa:mapnik/v2.2.0
# 删除存储库，但不会删除来自此仓库的软件包
add-apt-repository ppa:mapnik/v2.2.0 --remove</pre>
</td>
</tr>
<tr>
<td>ppa-purge</td>
<td>
<p>禁用一个PPA并回退为官方软件包（如果可能的话）。例如你添加了xorg-edgers PPA并且安装Nvidia驱动，当对此PPA执行ppa-purge命令时，不单单会禁用PPA，而且会把Nvidia驱动回复到Ubuntu官方仓库的版本<br />该命令需要安装：<pre class="crayon-plain-tag">sudo apt-get install ppa-purge</pre> 。用法示例：</p>
<pre class="crayon-plain-tag">sudo ppa-purge ppa:otto-kesselgulasch/gimp</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">aptitude</span></div>
<p>该命令是包管理器的高层接口，命令格式如下：
<pre class="crayon-plain-tag"># &lt;options&gt;...表示命令选项
# {}中包含了命令的动作（子命令）
# &lt;packages&gt;... 指定目标包的列表
aptitude [&lt;options&gt;...] {autoclean | clean | forget-new | keep-all | update}

aptitude [&lt;options&gt;...] {full-upgrade | safe-upgrade} [&lt;packages&gt;...]

aptitude [&lt;options&gt;...] {
                            build-dep | build-depends | changelog | download | 
                            forbid-version | hold | install | markauto | purge | 
                            reinstall | remove | show | unhold | unmarkauto | versions
                        } &lt;packages&gt;...
                        
aptitude extract-cache-subset &lt;output-directory&gt; &lt;packages&gt;...

aptitude [&lt;options&gt;...] search &lt;patterns&gt;...

aptitude [&lt;options&gt;...] {add-user-tag | remove-user-tag} &lt;tag&gt; &lt;packages&gt;...

aptitude [&lt;options&gt;...] {why | why-not} [&lt;patterns&gt;...] &lt;package&gt;

aptitude [-S &lt;fname&gt;] [--autoclean-on-startup | --clean-on-startup | -i | -u]

aptitude help</pre>
<div class="blog_h3"><span class="graybg">子命令</span></div>
<p>命令行中第一个不带短横线（-）的参数被作为需要执行的动作看待。</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>install</td>
<td>
<p>安装一个或者多个软件包。后面跟着需要安装的软件包的列表，如果某个包名后接~、?字符，则被视为搜索模式，匹配的包都会被安装</p>
<p>如果要选择特定的包版本，使用格式<pre class="crayon-plain-tag">pkgname=version</pre> ；如果要从特定的归档中选择包，使用格式<pre class="crayon-plain-tag">pkgname/archive</pre> ，例如<pre class="crayon-plain-tag">aptitude install apt/experimental</pre> </p>
<p>命令行中指定的包，并不一定必须安装，可以在包名后添加一个指示器，说明需要对包执行的动作：</p>
<ol>
<li>pkgname+ 安装</li>
<li>pkgname+M 安装，并标记其为自动（作为依赖而）安装，如果没有其它包依赖它，该包会被立即自动移除</li>
<li>pkgname- 移除</li>
<li>pkgname_ 移除并清理相关配置文件</li>
<li>pkgname= 设置包为保持状态，取消所有安装、升级、移除动作，并防止该包在未来被自动升级</li>
<li>pkgname: 类似上面，但是不阻止未来的包升级</li>
<li>pkgname&amp;M 标记为已经自动安装</li>
<li>pkgname&amp;m 标记为已经手动安装</li>
</ol>
<p>如果install子命令后面不跟随任何包名，那么所有预定、未决的安装操作会被执行。比如你发起<pre class="crayon-plain-tag">aptitude install foo</pre> 命令后立即终止命令执行，那么后面必须手工<pre class="crayon-plain-tag">aptitude remove foo</pre> 才能取消安装</p>
<p>如果出现依赖冲突，aptitude会提示可选的解决方案，你可以快速的删除冲突依赖包，而不是一个个手工删除</p>
</td>
</tr>
<tr>
<td>remove</td>
<td rowspan="6">
<p>这些命令和intall类似，但是仅对命令指定的包执行相应的动作</p>
<p>hold和keep的区别：前者时包在未来的safe-upgrade/full-upgrade命令中被忽略，keep仅仅取消任何准备对包进行动作</p>
<p>unhold执行和hold相反的操作</p>
<p>举例：</p>
<p><pre class="crayon-plain-tag">#移除所有名字中包含ndeity的包
aptitude remove '~ndeity'</pre>
</td>
</tr>
<tr>
<td>purge</td>
</tr>
<tr>
<td>hold</td>
</tr>
<tr>
<td>unhold</td>
</tr>
<tr>
<td>keep</td>
</tr>
<tr>
<td>reinstall</td>
</tr>
<tr>
<td>markauto</td>
<td rowspan="2">标记包为自动/手工安装</td>
</tr>
<tr>
<td>unmarkauto</td>
</tr>
<tr>
<td>build-depends</td>
<td rowspan="2">满足一个包的构建时依赖（build-dependencies）</td>
</tr>
<tr>
<td>build-dep</td>
</tr>
<tr>
<td>forbid-version</td>
<td>禁止某个包升级到特定的版本</td>
</tr>
<tr>
<td>update</td>
<td>升级软件包列表信息，等价于<pre class="crayon-plain-tag">apt-get update</pre> </td>
</tr>
<tr>
<td>safe-upgrade</td>
<td>
<p>升级已安装软件包到它们的最新版本。已安装的包不会被移除，除非它们已经不被使用</p>
<p>如果不指定包名，则尝试升级所有软件包</p>
<p>有时候为了升级某个软件包，需要移除另外一个包，这种情况下必须使用full-upgrade</p>
</td>
</tr>
<tr>
<td>full-upgrade</td>
<td>类似上面，更加激进的进行升级</td>
</tr>
<tr>
<td>keep-all</td>
<td>取消针对所有包的、已计划的（scheduled）动作</td>
</tr>
<tr>
<td>search</td>
<td>
<p>搜索匹配模式的软件包，匹配命令行中任意模式的软件包被打印</p>
<pre class="crayon-plain-tag">#搜索名称中包含edit的任何“新”包
aptitude search '~N' edit</pre>
</td>
</tr>
<tr>
<td>show</td>
<td>
<p>显示软件包的详细信息
<p><pre class="crayon-plain-tag">aptitude show ibus-pinyin</pre>
</td>
</tr>
<tr>
<td>versions</td>
<td>显示指定软件包的版本信息</td>
</tr>
<tr>
<td>why</td>
<td rowspan="2">解释特定软件包需要被安装、移除的原因</td>
</tr>
<tr>
<td>why-not</td>
</tr>
<tr>
<td>clean</td>
<td>删除所有先前下载到本地仓库的.deb文件</td>
</tr>
<tr>
<td>autoclean</td>
<td>与clean类似，但是只删除哪些不太可能再次被使用的、旧版本的软件包</td>
</tr>
<tr>
<td>changelog</td>
<td>下载并显示指定源码/二进制包的Debian changelog</td>
</tr>
<tr>
<td>download</td>
<td>下载软件包到当前目录</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">常用选项</span></div>
<ol>
<li>--allow-untrusted 允许不加提示的安装来自不受信源的软件</li>
<li>-D, --show-deps 对于那些会安装/移除软件包的子命令，该选项显示自动安装/移除的简要说明</li>
<li>-d, --download-only 仅仅下载软件包，而不安装/移除。软件包默认存放在<pre class="crayon-plain-tag">/var/cache/apt/archives</pre> </li>
<li>-s, --simulate 模拟安装过程</li>
<li>--schedule-only 对于修改包状态的那些命令，该选项会计划操作在未来进行，本次命令不会立即执行</li>
<li>-V, --show-versions 显示被安装软件包的版本</li>
<li>-v, --verbose 导致show等子命令显示更多信息</li>
<li>-y, --assume-yes 对所有提示回答“是</li>
</ol>
<div class="blog_h2"><span class="graybg">checkinstall</span></div>
<p>跟踪软件的安装过程，并依此创建一个Deb包：</p>
<pre class="crayon-plain-tag"># 监控 make install，生成Deb包
sudo checkinstall -D --fstrans=no make install</pre>
<div class="blog_h2"><span class="graybg"><a id="ufw"></a>ufw</span></div>
<p>所谓UFW（Uncomplicated Firewall，非复杂的防火墙），是iptables的前端，使用它可以降低iptables的使用复杂度。</p>
<div class="blog_h3"><span class="graybg">安装</span></div>
<p>要安装、启/禁UFW，参考下面的命令：</p>
<pre class="crayon-plain-tag">sudo apt-get install ufw
# 查看ufw的状态，inactive表示被禁用
sudo ufw status
# （在系统启动时）启用或者禁用
sudo ufw enable
sudo ufw disable
# 重新加载配置文件
sudo ufw reload</pre>
<p>查看UFW中的规则：</p>
<pre class="crayon-plain-tag">sudo ufw status verbose
# 下面的是默认规则的：禁止入站连接，允许出站连接，禁用路由功能
# Status: active
# Logging: on (low)
# Default: deny (incoming), allow (outgoing), disabled (routed)

# 在规则签名显示序号
sudo ufw status numbered</pre>
<p>设置日志级别：</p>
<pre class="crayon-plain-tag"># 启用日志，日志记录在 /var/log/ufw.log
ufw logging on
# 禁用日志
ufw logging off
# 设置日志级别，默认low，可用值： off low medium high full
ufw logging low</pre>
<div class="blog_h3"><span class="graybg">配置文件</span></div>
<p>你可以修改/etc/default/ufw文件，来定制ufw的默认行为</p>
<div class="blog_h3"><span class="graybg">规则设置命令</span></div>
<p>ufw的大部分命令格式都支持<pre class="crayon-plain-tag">--dry-run</pre> 选项，表示仅仅显示命令执行的效果，但是不改变现有系统。</p>
<p>设置默认策略：</p>
<pre class="crayon-plain-tag"># 默认 允许|禁止|回绝（提出提示） 入站|出站|路由 数据包
ufw  default allow|deny|reject [incoming|outgoing|routed]</pre>
<p>插入/删除一条规则：</p>
<pre class="crayon-plain-tag"># delete 执行删除操作  insert 插入规则到NUM行
# allow|deny|reject|limit 允许|禁止|回绝|限制
# in|out 针对入站|出站流量
# log|log-all 按规则设置日志记录方式，log记录新的连接，log-all记录所有包
# PORT 端口号或者名称
# PROTOCOL 限定针对的协议：tcp、udp等
ufw [delete] [insert NUM] allow|deny|reject|limit [in|out] [log|log-all] PORT[/PROTOCOL]

# rule 针对入站/出站数据包，route 针对路由数据包
# on INTERFACE 限定入站/出站的网络接口
# proto PROTOCOL 限定协议类型
# from ADDRESS 限定IP包来源地址
# to ADDRESS 限定IP包目的地址
ufw [rule|route] [delete] [insert NUM] allow|deny|reject|limit 
    [in|out [on INTERFACE]] [log|log-all] [proto PROTOCOL] [from ADDRESS [port PORT]] [to ADDRESS [port PORT]]

# 删除第NUM个规则
ufw delete NUM </pre>
<p>重置规则到安装时的状态：</p>
<pre class="crayon-plain-tag">ufw reset</pre>
<p>规则设置举例：</p>
<pre class="crayon-plain-tag"># 允许访问本机80端口
sudo ufw allow 80
# 禁止访问本机80端口（如果没有改变默认规则）
sudo ufw delete allow 80
# 允许来自特定IP的任何数据包（针对任何本机端口）
sudo ufw allow from 192.168.1.1
# 禁止外部访问本机smtp服务
sudo ufw deny smtp

# 禁止10.0.0.1/8子网访问192.168.0.1的SSH服务
sudo ufw deny proto tcp from 10.0.0.0/8 to 192.168.0.1 port 22

# 允许任何局域网主机的访问
sudo ufw allow from 10.0.0.0/8
sudo ufw allow from 172.16.0.0/12
sudo ufw allow from 192.168.0.0/16</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">16.04相关</span></div>
<div class="blog_h3"><span class="graybg">无法自动连接到隐藏Wifi</span></div>
<p>这是个已知的问题，临时解决方案：</p>
<pre class="crayon-plain-tag">nmcli c up id 'dangdang-ydgw'</pre>
<div class="blog_h2"><span class="graybg">MATE相关</span></div>
<div class="blog_h3"><span class="graybg">如何重置面板</span></div>
<pre class="crayon-plain-tag">mate-panel --reset</pre>
<div class="blog_h2"><span class="graybg">安装NVIDIA官方驱动</span></div>
<p>按Ctrl + Alt + F1，进入文本界面，执行</p>
<pre class="crayon-plain-tag"># 停止此服务
sudo service lightdm stop
# 安装驱动
sudo ./NVIDIA-Linux-x86_64-361.28.run

# 删除驱动
sudo /usr/bin/nvidia-uninstall
# 或者
sudo apt-get install autoremove --purge nvidia*</pre>
<div class="blog_h2"><span class="graybg">修改grub和Splash分辨率</span></div>
<p>执行<pre class="crayon-plain-tag">sudo gedit /etc/default/grub</pre> 编辑配置文件：</p>
<pre class="crayon-plain-tag"># 让等待时间缩短一些
GRUB_TIMEOUT=3

# 设置为显示器标准分辨率
GRUB_GFXMODE=1920x1080
# 添加下面一行
GRUB_GFXPAYLOAD_LINUX=keep</pre>
<p>保存后，执行<pre class="crayon-plain-tag">sudo update-grub</pre> 更新grub。</p>
<p>执行<pre class="crayon-plain-tag">sudo gedit /etc/initramfs-tools/conf.d/splash</pre> 编辑配置文件：</p>
<pre class="crayon-plain-tag">echo FRAMEBUFFER=y &gt; /dev/null</pre>
<p>保存后，执行<pre class="crayon-plain-tag">sudo update-initramfs -u</pre> 。重启后生效。</p>
<div class="blog_h2"><span class="graybg">记住显示器亮度</span></div>
<pre class="crayon-plain-tag">sudo apt-get install xbacklight
#设置亮度，可以指定0-100
xbacklight -set 80</pre>
<div class="blog_h2"><span class="graybg">一致性网络设备命名</span></div>
<p>所谓一致性网络设备命名（consistent network device naming），是Linux下以太网设备命名的约定规则，用来替换老旧的eth*命名风格。后者存在的问题是，由于内核发现网卡的顺序可能发生变化，这导致以太网设备的名称序号也随着改变。</p>
<p>某些操作系统版本默认启用了一致性网络设备命名，要改变此行为，可以：</p>
<ol>
<li>修改文件vim /etc/default/grub，设置内核参数：<pre class="crayon-plain-tag">GRUB_CMDLINE_LINUX="net.ifnames=0 biosdevname=0"</pre> </li>
<li>执行<pre class="crayon-plain-tag">update-grub</pre> 并重启</li>
</ol>
<p>重启后，以太网卡的命名风格变成传统的eth0*</p>
<div class="blog_h2"><span class="graybg">网络管理器提示：device not managed</span></div>
<pre class="crayon-plain-tag">sudo gedit /etc/NetworkManager/NetworkManager.conf
#修改如下一行
#[ifupdown]
#managed=true   默认值为false

sudo service network-manager restart</pre>
<div class="blog_h2"><span class="graybg">如何查看正在使用的DNS</span></div>
<pre class="crayon-plain-tag">nmcli dev list iface wlan1 | grep IP4
# 或者
nm-tool | grep DNS </pre>
<div class="blog_h2"><span class="graybg">启动显示waiting an additional 60 seconds for network configuration</span></div>
<p>除了启动时耗时外，还会出现通知区域的网络图标消失的问题。原因可能是因为<pre class="crayon-plain-tag">/etc/network/interfaces</pre> 配置了多于1个的<pre class="crayon-plain-tag">gateway</pre> 导致</p>
<div class="blog_h2"><span class="graybg">安装中文输入法</span></div>
<ol>
<li>打开System Settings - Text Entry，添加中文输入法ibus-pinyin</li>
<li>打开终端，输入ibus-setup，Show property panel 设置为 Do not show</li>
<li>nautilus /usr/share/ibus-pinyin/icons/ 该目录为ibus-pinyin的图标文件，可以进行美化</li>
<li>如果输入混乱，可以执行：ibus-daemon -drx并重启</li>
</ol>
<div class="blog_h2"><span class="graybg">如何安装一系列字体</span></div>
<pre class="crayon-plain-tag"># 修改新字体所在目录的文件模式
sudo chmod -R 777  /usr/share/fonts/windows
cd /usr/share/fonts/windows
# 创建元数据目录
sudo mkfontscale
sudo mkfontdir
# 刷新字体缓存
sudo fc-cache -fv </pre>
<div class="blog_h2"><span class="graybg">修正安装中文支持后字体变为楷体的问题</span></div>
<p>移除系统添加的字体即可解决。</p>
<pre class="crayon-plain-tag">sudo apt-get remove fonts-arphic-ukai fonts-arphic-uming</pre>
<div class="blog_h2"><span class="graybg">在VMware下，如何切换Console和GUI</span></div>
<p>进入字符界面 Ctrl + Alt + Space，Space松开，按F1<br /> 进入图形界面 Ctrl + Alt + Space，Space松开，按F7</p>
<div class="blog_h2"><span class="graybg">安装VMwareTools</span></div>
<p>首先在VMware菜单中选择安装VMware Tools，然后复制挂载光驱中的压缩包，执行perl文件。</p>
<pre class="crayon-plain-tag">cp /media/***/VMwareTools-9.6.1-1378637.tar.gz  ~/
cd ~
tar xzvf VMwareTools-9.6.1-1378637.tar.gz 
rm VMwareTools-9.6.1-1378637.tar.gz 
cd vmware-tools-distrib/
./vmware-install.pl
#遇到提示一律回车，完成后重启</pre>
<div class="blog_h2"><span class="graybg"><a id="add-modeline"></a>自定义显示模式</span><output name="Virtual1"></output></div>
<div class="blog_h3"><span class="graybg">通过命令行添加</span></div>
<pre class="crayon-plain-tag"># 以1600x900为例
# 查询当前显示信息，得知活动显示器为Virtual1
xrandr -q
# 拷贝下面命令的输出
cvt 1600 900 60
# 参考上述命令的输出，新增显示模式
xrandr --newmode "1600x900"  1837.25  1600 1760 1936 2272  900 903 908 1348 -hsync +vsync
# 为显示器Virtual1添加新模式
xrandr --addmode Virtual1 1600x900
# 立即以新模式输出
xrandr --output Virtual1 --mode 1600x900</pre>
<div class="blog_h3"><span class="graybg">持久化显示模式设置（方法一）</span></div>
<pre class="crayon-plain-tag">xrandr --newmode "1600x900" 1837.25 1600 1760 1936 2272 900 903 908 1348 -hsync +vsync
xrandr --addmode Virtual1 1600x900
xrandr --output Virtual1 --mode 1600x900</pre>
<div class="blog_h3"><span class="graybg">持久化显示模式设置（方法二） </span></div>
<p>新建或修改文件：</p>
<pre class="crayon-plain-tag">xrandr --newmode "1600x900" 1837.25 1600 1760 1936 2272 900 903 908 1348 -hsync +vsync
xrandr --addmode Virtual1 1600x900
xrandr --output Virtual1 --mode 1600x900</pre>
<p>执行命令：<pre class="crayon-plain-tag">chmod +x /etc/lightdm/lightdm-setup</pre> </p>
<p>新建或修改lightdm（Ubuntu下的显示管理器）的配置文件：</p>
<pre class="crayon-plain-tag"># 添加下面的内容
display-setup-script=/etc/lightdm/lightdm-setup</pre>
<p>该方法在Ubuntu MATE 16.04下测试通过。</p>
<div class="blog_h2"><span class="graybg">解决包依赖问题</span></div>
<p>有时在安装软件包时会遇到unmet dependencies提示，要解决此类问题，可以参考：</p>
<pre class="crayon-plain-tag">#移除并清理指定软件包的配置文件，打开所有强制选项
#可以用该命令删除系统中既有的不匹配的依赖包版本
sudo dpkg --purge --force-all pkgname

#尝试自动纠正系统中损坏的依赖关系（broken dependencies），该命令可以不指定pkgname
sudo apt-get -f install

#查看某个软件包的可用版本
sudo apt-cache showpkg pkgname

#安装指定版本的软件包
#该命令可以强制覆盖已安装版本
sudo aptitude install pkgname=pkgversion</pre>
<p> 通过以上四个命令，然后在系统提示unmet dependencies时步步跟踪，即可解决依赖关系问题，具体例子如下：</p>
<pre class="crayon-plain-tag">apt-get install pkg-a
...
The following packages have unmet dependencies:
libqt4-dev: Depends: pkg-b but it is not going to be installed #继续往下跟踪 
            Depends: pkg-c but it is not going to be installed 
...
apt-get install pkg-b 
...
The following packages have unmet dependencies: 
 pkg-b : Depends: pkg-d(= 2.1.0) but 3.5.0 is to be installed  #这里就是问题所在，系统将要自动安装一个比较新的版本
 Depends: pkg-c but it is not going to be installed 
 Depends: pkg-e but it is not going to be installed 
#强制安装一个低级的版本
aptitude install pkg-d=2.1.0</pre>
<div class="blog_h2"><span class="graybg">sudo免密码</span></div>
<p>执行<pre class="crayon-plain-tag">sudo visudo</pre> 修改文件：</p>
<pre class="crayon-plain-tag"># alex免密码
alex ALL=(ALL) NOPASSWD: ALL
# admin组的成员免密码
%admin ALL=(ALL) NOPASSWD: ALL</pre>
<div class="blog_h2"><span class="graybg">ssh免密码</span></div>
<p>客户端的公钥需要放到服务器的<pre class="crayon-plain-tag">~/.ssh/authorized_keys</pre>中，需要注意文件模式：</p>
<pre class="crayon-plain-tag"># 服务器端
sudo chmod 700 ~/.ssh
sudo chmod 600 ~/.ssh/authorized_keys</pre>
<p>仍然无法免密码登陆，修改/etc/ssh/sshd_config并重启SSH服务：</p>
<pre class="crayon-plain-tag"># 此项的默认值为yes
StrictModes no</pre>
<div class="blog_h2"><span class="graybg">挂载NTFS相关选项</span></div>
<p>打开Disks，可以添加挂载选项：</p>
<p>hide_hid_files，隐藏NTFS的隐藏文件<br />windows_names，仅允许Windows下合法的文件名<br />show_sys_files，显示系统文件<br />hide_dot_files，隐藏点号开头的文件</p>
<div class="blog_h2"><span class="graybg">无法进入Grub</span></div>
<p>现象：开机后，BIOS通过后就自动重启。</p>
<p>原因：在我的机器上，此问题是因为错误的使用分区工具，导致引导扇区出错导致。</p>
<div class="blog_h3"><span class="graybg">修复Grub</span></div>
<p>解决上述问题的方法是：</p>
<ol>
<li>使用Ubuntu启动U盘进入系统</li>
<li>执行：<br />
<pre class="crayon-plain-tag">sudo apt-add-repository ppa:yannubuntu/boot-repair
sudo apt-get update
sudo apt-get install -y boot-repair
boot-repair</pre>
</li>
</ol>
<p>执行boot-repair会弹出一个图形界面，根据提示进行操作。</p>
<div class="blog_h3"><span class="graybg">修复Windows</span></div>
<p>在多硬盘（每硬盘对应一个OS，相互独立）的机器上，执行上述操作后，可能出现Windows系统无法启动的情况。这是因为boot-repair把修改了所有硬盘的启动扇区导致。可以使用下面的命令证实这一点：</p>
<pre class="crayon-plain-tag">sudo dd bs=512 count=1 if=/dev/sda  2&gt;/dev/null | strings
# ZRr=
# `|f	
# \|f1
# GRUB           # GRUB
# Geom
# Hard Disk
# Read
#  Error </pre>
<p>解决办法，使用工具恢复Windows所在磁盘的主引导记录：</p>
<pre class="crayon-plain-tag">sudo apt-get install syslinux

# /dev/sda替换为Windows所在的磁盘
sudo dd if=/usr/lib/syslinux/mbr.bin of=/dev/sda

sudo dd bs=512 count=1 if=/dev/sda  2&gt;/dev/null | strings
# RPf1
# Missing operating system.
# f`f1
# |fRfP
# Ht[y9Y[
# Multiple active partitions.
# Operating system load error.</pre>
<div class="blog_h2"><span class="graybg">进入恢复模式</span></div>
<p>开机后按住Shift键，进入GRUB启动菜单，依次选择：<br /> Advanced options for Ubuntu<br /> Ubuntu, With Linux 3.13.0-32-generic (recovery mode)<br /> 等待一会后，选择倒数第二项：</p>
<p><a href="/wp-content/uploads/2011/10/ubuntu-recover-mode-1.jpg"><img class="aligncenter" src="https://blog.gmem.cc/wp-content/uploads/2011/10/ubuntu-recover-mode-1.jpg" alt="ubuntu-recover-mode-1" width="558" height="286" /></a><br /> 在shell中输入：</p>
<pre class="crayon-plain-tag">#挂载读写文件系统
mount -o remount,rw /</pre>
<p>即可以root权限进行维护。</p>
<div class="blog_h2"><span class="graybg">以root身份通过GUI浏览文件</span></div>
<pre class="crayon-plain-tag">sudo nautilus&amp;</pre>
<div class="blog_h2"><span class="graybg">禁止Chrome自动升级</span></div>
<pre class="crayon-plain-tag">cd /etc/apt/sources.list.d
sudo mv google-chrome.list google-chrome.list.save</pre>
<div class="blog_h2"><span class="graybg">设置时区</span></div>
<pre class="crayon-plain-tag">#运行以下命令，根据提示进行设置
dpkg-reconfigure tzdata</pre>
<div class="blog_h2"><span class="graybg">彻底清空命令历史</span></div>
<pre class="crayon-plain-tag">cat /dev/null &gt; ~/.bash_history &amp;&amp; history -c &amp;&amp; exit</pre>
<div class="blog_h2"><span class="graybg">新窗口屏幕居中</span></div>
<pre class="crayon-plain-tag">#安装软件：
sudo apt-get install compizconfig-settings-manager</pre>
<p>完毕后，打开 CompizConfig Settings Manager ，定位到Window Management - Place Window，设置Placement Mode为Centered</p>
<div class="blog_h2"><span class="graybg">安装.deb包</span></div>
<pre class="crayon-plain-tag">sudo dpkg -i path/name.deb</pre>
<div class="blog_h2"><span class="graybg">修改主机名称</span></div>
<p>临时修改主机名称：</p>
<pre class="crayon-plain-tag">sudo hostname new-name</pre>
<p>永久修改主机名称：编辑文件/etc/hostname<br /> 编辑：/etc/hosts 修改 127.0.1.1对应的域名</p>
<div class="blog_h2"><span class="graybg">支持Zeroconf</span></div>
<p>执行下面的命令安装Avahi即可，Avahi是Zeroconf规范的开源实现：</p>
<pre class="crayon-plain-tag">sudo apt-get install avahi-daemon avahi-dnsconfd avahi-discover avahi-utils libnss-mdns
sudo reboot</pre>
<div class="blog_h2"><span class="graybg">刷空DNS缓存</span></div>
<pre class="crayon-plain-tag">#安装DNS缓存守护服务 Name Service Cache Daemon
apt-get install nscd
#重新启动
service nscd restart</pre>
<div class="blog_h2"><span class="graybg">su: Authentication failure</span></div>
<pre class="crayon-plain-tag">#需要重设密码：
sudo passwd root</pre>
<div class="blog_h2"><span class="graybg">清空ROOT的回收站</span></div>
<pre class="crayon-plain-tag">sudo apt-get install trash-cli 
sudo trash-empty</pre>
<div class="blog_h2"><span class="graybg">PPTP客户端配置</span></div>
<p>需要根据PPTP服务器的设定进行配置，如果连接失败，可以查看/var/log/syslog，下图是一个配置示例：</p>
<p><img class="aligncenter size-full wp-image-5976" src="https://blog.gmem.cc/wp-content/uploads/2011/10/pptp.png" alt="pptp" width="411" height="613" /></p>
<div class="blog_h2"><span class="graybg">L2TP客户端支持</span></div>
<pre class="crayon-plain-tag">#安装集成到Ubunt网络管理器的L2TP支持
sudo apt-add-repository ppa:seriy-pr/network-manager-l2tp
sudo apt-get update
sudo apt-get install network-manager-l2tp-gnome</pre>
<p>L2TP客户端的设置，参考PPTP，注意IPsec Settings的Pre-shared Key要填写预共享密钥。</p>
<div class="blog_h2"><span class="graybg">VPN频繁断开</span></div>
<p><span class="graybg">现象：日志显示short read (-1): Message too long</span></p>
<p>原因：ppp接口的MTU1400，eth为1500，而traceroute发现网络最大MTU为1382</p>
<p>解决：<pre class="crayon-plain-tag">sudo ifconfig eth0 mtu 1380</pre></p>
<div class="blog_h2"><span class="graybg">修改应用程序图标</span></div>
<p>大部分应用程序的桌面图标定义在<pre class="crayon-plain-tag">/usr/share/applications/*.desktop</pre> 文件中。当前用户个人的应用程序定义</p>
<p>在<pre class="crayon-plain-tag">~/.local/share/applications/*.desktop</pre> 文件中，编辑这些文件，修改Icon字段，即可改变图标</p>
<div class="blog_h2"><span class="graybg">隐藏工作区切换图标</span></div>
<p>安装dconf-editor，在com.canonical.unity.launcher.favorites字段中，删除unity://expo-icon</p>
<div class="blog_h2"><span class="graybg">无线网卡作为Hotspot</span></div>
<ol>
<li>网络管理器 ⇨ Edit Connections，弹出的窗口中点击Add</li>
<li>添加一个Wi-Fi类型的连接，填写好字段。注意IPv4 Settings中，Method选择Shared to other computers保存</li>
<li>打开文件<pre class="crayon-plain-tag">/etc/NetworkManager/system-connections/新创建的连接名称</pre> ，修改<pre class="crayon-plain-tag">mode=ap</pre> </li>
<li>网络管理器 ⇨ Connect to hidden Wi-Fi network，选择新创建的连接，点击Connect</li>
<li>其他机器现在可以连接到热点 了</li>
</ol>
<div class="blog_h2"><span class="graybg"><a id="infinality"></a>使用Infinality进行字体渲染</span></div>
<pre class="crayon-plain-tag">sudo add-apt-repository ppa:no1wantdthisname/ppa
sudo apt-get update
sudo apt-get install fontconfig-infinality

# 可以选择4（osx）
sudo bash /etc/fonts/infinality/infctl.sh setstyle</pre>
<div class="blog_h2"><span class="graybg">双系统共享蓝牙</span></div>
<p>同一台机器安装Ubuntu 20.04、Windows 10双系统，分别与蓝牙设备配对后，只有最后配对的系统才能访问鼠标，切换系统后需要重新配对。解决此问题的步骤如下：</p>
<ol>
<li>Windows执行配对</li>
<li>Ubuntu执行配对，然后打开配置文件 /var/lib/bluetooth/蓝牙适配器MAC/目标蓝牙设备MAC，获取密钥：<br />
<pre class="crayon-plain-tag">; ...
[LinkKey]
; 记住这个密钥
Key=9E438CF7CED4F8D80E6FB46B29468575
Type=4
PINLength=0</pre>
</li>
<li>切换到Windows系统，以系统管理员身份打开cmd.exe，执行<pre class="crayon-plain-tag">psexec -s -i regedit</pre> 打开注册表</li>
<li>定位到注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\BTHPORT\Parameters\Keys\，修改对应设备的安全码为linkkeys中的安全码，例如：<br />
<pre class="crayon-plain-tag">rem 修改 f065dd9b9784的二进制值
rem 可以退格键删除所有内容，然后依次输入16进制数9E 43 8C F7 CE D4 F8 D8 0E 6F B4 6B 29 46 85 75</pre>
</li>
<li>在Windows下尝试连接蓝牙</li>
</ol>
<div class="blog_h2"><span class="graybg">恢复acpi-cpufreq</span></div>
<p>要禁用Ubuntu 16.04的intel_pstate，恢复acpi-cpufreq，可以：</p>
<pre class="crayon-plain-tag">sudo apt-get install acpi-support acpid acpi</pre>
<p>然后编辑/etc/default/grub：</p>
<pre class="crayon-plain-tag">GRUB_CMDLINE_LINUX_DEFAULT="... intel_pstate=disable"</pre>
<p>执行<pre class="crayon-plain-tag">sudo update-grub</pre>并重启即可。 </p>
<div class="blog_h2"><span class="graybg">使用蓝牙4.0</span></div>
<p>最近买了阿米洛的蓝牙机械键盘，结果Ubuntu不能识别。到网上搜索一下，发现需要升级Bluez到5.x版本，并且必须使用Blueman代替Gnome的蓝牙管理器。</p>
<p>执行如下命令安装：</p>
<pre class="crayon-plain-tag">#安装Bluez 5和Blueman
sudo add-apt-repository ppa:vidplace7/bluez5
sudo apt-add-repository ppa:blueman/ppa
sudo apt-get update
sudo apt-get install blueman
sudo apt-get install bluez

# 上面命令执行后会挂起，强制终止
sudo killall apt-get
sudo rm /var/lib/dpkg/lock
# 重新配置一下
sudo dpkg --configure -a</pre>
<p>完毕后，重启，即可使用Blueman连接蓝牙键盘了。存在两个问题需要解决：</p>
<ol>
<li>每次开机后，蓝牙适配器总是关闭的。Blueman没有提供相关的设置项，需要修改Bluez的配置文件：<br />
<pre class="crayon-plain-tag">AutoEnable=true</pre>
</li>
<li>无法正常关机，卡死在Splash屏上。出现这个现象的原因，在我机器上是bluetoothd无法正常终结，可以写一个关机脚本强制终结：<br />
<pre class="crayon-plain-tag">sudo /usr/bin/pkill -9 bluetoothd</pre>
</li>
</ol>
<div class="blog_h2"><span class="graybg">ibus-pinyin和Eclipse快捷键冲突</span></div>
<p>目前（1.5.0）版本的ibus-pinyin没有提供设置项，可以<a href="https://github.com/phuang/ibus-pinyin">修改源码</a>：</p>
<pre class="crayon-plain-tag">/* Toggle simp/trad Chinese Mode when hotkey Ctrl + Shift + F pressed */
if (keyval == IBUS_F &amp;&amp; scmshm_test (modifiers, (IBUS_SHIFT_MASK | IBUS_CONTROL_MASK))) {
    //m_props.toggleModeSimp ();
    m_prev_pressed_key = IBUS_F;
    return FALSE; //去除此快捷键的处理代码
}</pre>
<p>修改后，重新构建ibus-pinyin并安装：</p>
<pre class="crayon-plain-tag">git clone git://github.com/ibus/ibus-pinyin.git ibus-pinyin
cd ibus-pinyin
#You need to install gnome-common from the GNOME CVS
sudo apt-get -y install gnome-common
#***Error***: You must have glib-gettext &gt;= 2.2.0 installed
sudo apt-get -y install libglib2.0-dev
#No package 'ibus-1.0' found
sudo aptitude install libibus-1.0-dev #强制安装
#No package 'pyzy-1.0' found
sudo apt-get -y install libpyzy-dev
#No package 'sqlite3' found
sudo apt-get -y install libsqlite3-dev

./autogen.sh --prefix=/usr
#我机器上构建english.db出错，因此这里拷贝现有的文件绕过这一target
cp /usr/share/ibus-pinyin/db/english.db data/db/english/
make 
make install</pre>
<div class="blog_h2"><span class="graybg">ibus-pinyin输文本丢失</span></div>
<p>ibus-pinyin还有一个严重问题，在Chrome/Firefox中，经常自动删除已经输入的文本。可以改用ibus-libpinyin。下面是安装步骤：</p>
<pre class="crayon-plain-tag">git clone https://github.com/libpinyin/libpinyin.git
cd libpinyin
sudo apt-add-repository ppa:bitcoin/bitcoin
sudo apt-get update
sudo apt-get install libdb4.8++-dev
./autogen.sh --prefix=/usr
make &amp;&amp; sudo make install

cd ..
git clone https://github.com/libpinyin/ibus-libpinyin.git
cd ibus-libpinyin
./autogen.sh --prefix=/usr
make &amp;&amp; sudo make install</pre>
<p>ibus-libpinyin同样存在Ctrl+Shift+F冲突问题，可以修改源码：</p>
<pre class="crayon-plain-tag">//从140行左右开始，删除下面的内容：
/* Toggle full/half Letter Mode */
    if (PinyinConfig::instance (). letterSwitch () == accel) {
        m_props.toggleModeFull ();
        m_prev_pressed_key = keyval;
        return TRUE;
    }

    /* Toggle full/half Punct Mode */
    if (PinyinConfig::instance (). punctSwitch () == accel) {
        m_props.toggleModeFullPunct ();
        m_prev_pressed_key = keyval;
        return TRUE;
    }

    /* Toggle simp/trad Chinese Mode */
    if (PinyinConfig::instance ().tradSwitch () == accel) {
        m_props.toggleModeSimp ();
        m_prev_pressed_key = keyval;
        return TRUE;
    }</pre>
<div class="blog_h2"><span class="graybg">挂载内存磁盘</span></div>
<pre class="crayon-plain-tag"># 创建挂载目录
mkdir /mnt/ramdisk
# 通过命令挂载
mount -t tmpfs -o size=4096M tmpfs /mnt/ramdisk</pre>
<p>也可以修改fstab，在启动时自动挂载：</p>
<pre class="crayon-plain-tag">tmpfs       /mnt/ramdisk tmpfs   nodev,nosuid,noexec,nodiratime,size=4096M   0 0</pre>
<div class="blog_h2"><span class="graybg">改变交换文件大小</span></div>
<pre class="crayon-plain-tag"># 关闭交换文件功能
sudo swapoff -a
# 修改交换文件大小
sudo dd if=/dev/zero of=/swapfile bs=1M count=1024
# 重新启用交换文件功能
sudo mkswap /swapfile
sudo swapon /swapfile</pre>
<div class="blog_h2"><span class="graybg">使用单独交换分区</span></div>
<p>如果你正在使用交换文件，可以创建一个交换分区，然后：</p>
<pre class="crayon-plain-tag"># 获取交换分区的设备名
cat /etc/fstab | grep swap
# swap was on /dev/sdc5 during installation

sudo swapoff -a
sudo mkswap /dev/sdc5
sudo swapon /dev/sdc5</pre>
<div class="blog_h2"><span class="graybg">交换分区不生效</span></div>
<p>在fstab中配置好交换分区后，应该就能生效，如果不生效，可能是UUID填写错误：</p>
<pre class="crayon-plain-tag">UUID=e000bd2d-8ddd-4124-8e45-50ec2e086cca none            swap    sw              0       0</pre>
<p>可以使用<pre class="crayon-plain-tag">sudo blkid</pre> 查到交换分区真实的UUID，修改fstab文件，然后重启即可。 </p>
<div class="blog_h2"><span class="graybg">配置反转SSH隧道</span></div>
<p>利用反转SSH隧道（Reverse SSH Tunnel）技术，可以让你从互联网来访问隐藏在防火墙后面的（内网）机器。</p>
<p>下面用一个实际的例子来说明如何配置反转SSH隧道。在这个例子中，外网机器vpn.gmem.cc，登陆用户为root，位于内网机器不具有公共地址，登陆用户为pi。两台机器均使用公钥验证。</p>
<div class="blog_h3"><span class="graybg">外网机器配置</span></div>
<pre class="crayon-plain-tag">AllowTcpForwarding yes 
TCPKeepAlive yes</pre>
<div class="blog_h3"><span class="graybg">内网机器配置</span></div>
<pre class="crayon-plain-tag">#!/bin/sh
while true; do
    sudo -u pi ssh -nNT -R localhost:62222:localhost:22 -i /home/pi/Documents/puTTY/gmem.key root@vpn.gmem.cc
    sleep 300
done</pre>
<p>可以修改/ect/rc.local，开机执行上述脚本。</p>
<div class="blog_h3"><span class="graybg">反向连接到内网</span></div>
<p>在外网机器执行：<pre class="crayon-plain-tag">ssh -p 62222 -i ~/documents/puTTY/gmem.key pi@localhost</pre> </p>
<div class="blog_h3"><span class="graybg">更新字体缓存</span></div>
<pre class="crayon-plain-tag">fc-cache -f -v</pre>
<div class="blog_h2"><span class="graybg">保存iptables设置</span></div>
<pre class="crayon-plain-tag">sudo apt-get install iptables-persistent

# 保存当前设置
sudo /etc/init.d/iptables-persistent save 
# 加载已保存的设置
sudo /etc/init.d/iptables-persistent reload</pre>
<p>或者：</p>
<pre class="crayon-plain-tag"># 保存设置到任意文件
sudo iptables-save &gt; /etc/network/iptables.rules
# 从先前保存的文件中加载
pre-up iptables-restore &lt; /etc/network/iptables.rules</pre>
<p>你可以在网络启动的时候自动加载iptables设置：</p>
<pre class="crayon-plain-tag">iface eth0 inet static
        ....
        pre-up /etc/init.d/iptables-persistent reload</pre>
<div class="blog_h2"><span class="graybg">SSH无法通过root登陆</span></div>
<p>报错：Permission denied, please try again.</p>
<p>查看/var/log/auth.log，提示：Failed password for root from 192.168.0.89 port 39275 ssh2</p>
<p>解决办法：修改配置文件</p>
<pre class="crayon-plain-tag"># 把下面这一行
PermitRootLogin without-password
# 改为：
PermitRootLogin yes</pre>
<p>然后重启SSH服务： <pre class="crayon-plain-tag">sudo service ssh restart</pre> </p>
<div class="blog_h2"><span class="graybg">从登陆列表隐藏用户</span></div>
<p>最简单的办法，是把用户的UID设置为小于500的数字：</p>
<pre class="crayon-plain-tag">sudo usermod -u 125 kurento
sudo groupmod -g 133 kurento</pre>
<div class="blog_h2"><span class="graybg">配置网桥</span></div>
<p><a href="/network-faq#bridging">网桥</a>能够把两个<span style="background-color: #c0c0c0;">以太网卡</span>（例如eth0、eth1）桥接在一起，当两个以太网被桥接到一起后，两个网络合并为单个更大的网络。</p>
<p>要在Ubuntu下配置网桥，可以执行以下步骤：</p>
<div class="blog_h3"><span class="graybg">安装bridge-utils</span></div>
<pre class="crayon-plain-tag">sudo apt-get update
sudo apt-get install bridge-utils</pre>
<p>安装此软件后，你就可以在/etc/network/interfaces中，像配置普通网卡一样，配置网桥了。</p>
<div class="blog_h3"><span class="graybg">自动创建网桥</span></div>
<p>你可以通过配置文件，在系统启动后自动创建网桥：</p>
<pre class="crayon-plain-tag">auto br0
iface br0 inet dhcp
  # 该网桥链接的以太网
  bridge_ports eth0 eth1
  bridge_stp off
  bridge_fd 0
  # 系统等待以太网port启动的最大时间，0表示不等待
  bridge_maxwait 0</pre>
<p>你也可以为网桥指定静态IP：</p>
<pre class="crayon-plain-tag">auto br0
iface br0 inet static
  address 192.168.0.122
  netmask 255.255.255.0
  gateway 192.168.0.1
  dns-nameservers 192.168.0.2
  bridge_ports eth0 eth1
  bridge_stp off
  bridge_fd 0
  bridge_maxwait 0</pre>
<p>使用DHCP时，分配给网桥会被分配一个IP地址，该地址和被桥接的两个网卡之一所在网段相同。</p>
<div class="blog_h2"><span class="graybg">Grub2设置默认启动项</span></div>
<p>执行下面的命令，找到你需要的启动项：</p>
<pre class="crayon-plain-tag">grep menuentry /boot/grub/grub.cfg

# 输出内容
# ...
# menuentry 'Windows 7 (loader) (on /dev/sda1)' --class windows  ...</pre>
<p>修改配置文件：</p>
<pre class="crayon-plain-tag">GRUB_DEFAULT="Windows 7 (loader) (on /dev/sda1)"</pre>
<p>运行下面的命令，重启后即生效：</p>
<pre class="crayon-plain-tag">sudo update-grub </pre>
<div class="blog_h2"><span class="graybg">Grub错误：unknown filesystem</span></div>
<p>我在克隆分区镜像到另外一台机器上时，出现此问题。原因是两台机器的分区结构不一样。grub中的记录已经无效。</p>
<p>解决办法：</p>
<pre class="crayon-plain-tag"># 列出可用分区
ls
# 输出：(hd0) (hd0,msdos3)   (hd0,msdos5)    (hd0,msdos8)

# 查看当前启动路径设置
set

# 逐个检查可用分区，根据目录结构判断是否Ubuntu系统盘
ls (hd0, msdos8)

# 设置正确的启动路径
set root=hd0,msdos8
set prefix=(hd0,msdos8)/boot/grub

# 启动
insmod normal
normal

# 进入系统后，执行
sudo update-grub
# 把Grub安装到sda
sudo grub-install /dev/sda</pre>
<div class="blog_h2"><span class="graybg">禁用mnemonics</span></div>
<p>Ubuntu下的Alt + &lt;KEY&gt;激活顶部菜单的功能有时候很烦人，可以按照如下方法禁用掉：</p>
<ol>
<li>对于基于GTK 2的应用，例如GIMP，可以在~/.gtkrc-2.0中添加：<br />
<pre class="crayon-plain-tag">gtk-enable-mnemonics = 0</pre>
</li>
<li>对于基于GTK3的应用，可以修改/etc/gtk-3.0/settings.ini</li>
</ol>
<div class="blog_h2"><span class="graybg">终端窗口行宽度太短</span></div>
<pre class="crayon-plain-tag">export TERM=xterm-256color </pre>
<div class="blog_h2"><span class="graybg">自动激活新窗口</span></div>
<p>有时候，程序自动打开的窗口可能不会自动激活（置于最前面）。要让新打开的窗口自动激活，可以执行：</p>
<pre class="crayon-plain-tag">dconf write /org/compiz/profiles/unity/plugins/core/focus-prevention-level 0 </pre>
<div class="blog_h2"><span class="graybg">安装Java8</span></div>
<p>14.10以下的版本，自带源不包括JDK1.8，可以自行添加源并安装：</p>
<pre class="crayon-plain-tag">sudo add-apt-repository ppa:openjdk-r/ppa
sudo apt-get update 
sudo apt-get install openjdk-8-jdk </pre>
<div class="blog_h2"><span class="graybg">如何查看保存的WiFi密码</span></div>
<p>所有密码存放在/etc/NetworkManager/system-connections目录下。</p>
<div class="blog_h2"><span class="graybg">如何启用巨型帧</span></div>
<p>尝试在当前节点、目标节点上设置MTU：</p>
<pre class="crayon-plain-tag">ip link set eth0 mtu 9000</pre>
<p>尝试发送巨型帧（Jumbo Frame）：</p>
<pre class="crayon-plain-tag">ping -M do -s 8972 10.0.3.1</pre>
<p>如果当前节点忘记设置MTU，你会遇到错误：ping: sendto: Message too long</p>
<p>如果当前节点设置了MTU，但是目标节点、两者之间的交换机没有设置，则会遇到错误：Request timeout for icmp_seq 0</p>
<p>如果一切正常，你会看到：8192 bytes from  ... </p>
<div class="blog_h2"><span class="graybg">假坏扇区修复</span></div>
<p>首先执行命令：<pre class="crayon-plain-tag">dmesg | grep print_req</pre>，获取出错扇区范围。</p>
<p>然后，扫描出错扇区附近位置：</p>
<pre class="crayon-plain-tag">#                          END      START
badblocks -b 512 /dev/sda  76000000 70000000</pre>
<p>将出错扇区覆盖一次：</p>
<pre class="crayon-plain-tag">badblocks -b 512 -w /dev/sdb 2899976 2899968</pre>
<p>通常会扫描出连续8个扇区，原因是文件系统目前常使用4K扇区。 </p>
<div class="blog_h2"><span class="graybg">升级内核</span></div>
<div class="blog_h3"><span class="graybg">桌面系统如何升级</span></div>
<p>自己去下载内核并安装，很容易出现驱动问题、包不兼容问题。正确的方法是，使用官方原始的souces.list，安装：</p>
<pre class="crayon-plain-tag">sudo apt install --install-recommends linux-image-generic-hwe-16.04 xserver-xorg-hwe-16.04 
sudo apt install linux-headers-generic-hwe-16.04
sudo ubuntu-drivers autoinstall</pre>
<div class="blog_h3"><span class="graybg">到4.15</span></div>
<pre class="crayon-plain-tag">wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.15.18/linux-headers-4.15.18-041518-generic_4.15.18-041518.201804190330_amd64.deb
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.15.18/linux-headers-4.15.18-041518_4.15.18-041518.201804190330_all.deb
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.15.18/linux-image-4.15.18-041518-generic_4.15.18-041518.201804190330_amd64.deb

sudo dpkg -i *.deb
sudo update-grub


# 或者
apt search 4.15.0-34   # 检索可用的内核版本
apt install linux-image-4.15.0-34-generic linux-headers-4.15.0-34-generic linux-modules-4.15.0-34-generic \
            linux-modules-extra-4.15.0-34-generic linux-tools-4.15.0-34-generic</pre>
<div class="blog_h3"><span class="graybg">到4.4</span></div>
<pre class="crayon-plain-tag">wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.4-wily/linux-image-4.4.0-040400-generic_4.4.0-040400_amd64.deb
wget http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.4-wily/linux-headers-4.4.0-040400-generic_4.4.0-040400_amd64.deb
http://kernel.ubuntu.com/~kernel-ppa/mainline/v4.4-wily/linux-headers-4.4.0-040400_4.4.0-040400.201601101930_all.deb
sudo dpkg -i *.deb</pre>
<p>执行下面的命令移除4.4内核：</p>
<pre class="crayon-plain-tag">sudo apt-get remove linux-headers-4.4.0-* linux-image-4.4.0-*
sudo update-grub </pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/ubuntu-faq">Ubuntu知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/ubuntu-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Android知识集锦</title>
		<link>https://blog.gmem.cc/android-faq</link>
		<comments>https://blog.gmem.cc/android-faq#comments</comments>
		<pubDate>Sun, 03 Jul 2011 03:17:21 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[FAQ]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=15011</guid>
		<description><![CDATA[<p>零散知识 基本概念 术语 说明 smali 可以指： dalvik虚拟机（安卓的JVM实现）的汇编器（编译为dex格式），对应的baksmali则是反汇编器 —— 把dex字节码转换为smali格式 一种文件格式，dex反汇编后的一种语言 dex字节码无法阅读，smali为其提供了较好的可读性 dpi与像素密度 APK包的res目录下，会有一些drawable开头的子目录，用来存放针对不同像素密度屏幕的资源文件： 目录后缀 像素密度范围 机型举例 mdpi 120dpi~160dpi   hdpi 160dpi~240dpi   <a class="read-more" href="https://blog.gmem.cc/android-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/android-faq">Android知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">零散知识</span></div>
<div class="blog_h2"><span class="graybg">基本概念</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">术语</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>smali</td>
<td>
<p>可以指：</p>
<ol>
<li>dalvik虚拟机（安卓的JVM实现）的汇编器（编译为dex格式），对应的baksmali则是反汇编器 —— 把dex字节码转换为smali格式</li>
<li>一种文件格式，dex反汇编后的一种语言</li>
</ol>
<p>dex字节码无法阅读，smali为其提供了较好的可读性</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">dpi与像素密度</span></div>
<p>APK包的res目录下，会有一些drawable开头的子目录，用来存放针对不同像素密度屏幕的资源文件：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">目录后缀</td>
<td style="text-align: center;">像素密度范围</td>
<td style="text-align: center;">机型举例</td>
</tr>
</thead>
<tbody>
<tr>
<td>mdpi</td>
<td>120dpi~160dpi</td>
<td> </td>
</tr>
<tr>
<td>hdpi</td>
<td>160dpi~240dpi</td>
<td> </td>
</tr>
<tr>
<td>xhdpi</td>
<td>240dpi~320dpi</td>
<td> </td>
</tr>
<tr>
<td>xxhdpi</td>
<td>320dpi~480dpi</td>
<td>Sony Xperia Z5</td>
</tr>
<tr>
<td>xxxhdpi</td>
<td>480dpi~640dpi</td>
<td> </td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">签名后的APK安装报错：INSTALL_PARSE_FAILED_NO_CERTIFICATES</span></div>
<p>可能原因是，APK被重复签名多次。删除掉META-INF目录下的签名文件、签名块文件后，重新签名即可。</p>
<div class="blog_h1"><span class="graybg">常用开发命令</span></div>
<div class="blog_h2"><span class="graybg"><a id="apktool"></a>apktool</span></div>
<p>APK包反向工程工具。</p>
<div class="blog_h3"><span class="graybg">子命令说明</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>if</td>
<td>
<p>命令格式：<pre class="crayon-plain-tag">apktool if|install-framework [options] &lt;framework.apk&gt;</pre> </p>
<p>安装框架文件到目录中，选项：<br />-p,--frame-path &lt;dir&gt;  安装到的目标目录<br />-t,--tag &lt;tag&gt;  使用tag来标记框架</p>
</td>
</tr>
<tr>
<td>d</td>
<td>
<p>命令格式：<pre class="crayon-plain-tag">apktool [-q OR -v] d[ecode] [options] &lt;file_apk&gt;</pre></p>
<p>解码file_apk，生成以下目录或者文件：<br />apktool.yml  工具私有元数据<br />AndroidManifest.xml  解码后的清单文件<br />original  原始数据，二进制的清单文件、数据签名文件等<br />res  资源文件，包括图片和解码后的XML<br />smali  dex反汇编后的代码，类似于汇编语言/JVM Opcodes</p>
<p>可用选项：<br /> --api &lt;API&gt; 生成的文件使用的API级别，数字<br /> -b,--no-debug-info 不解码调试用信息，例如行号<br /> -f,--force  如果目标目录存在，先删除之<br /> -k,--keep-broken-res 如果出现错误而某些资源被drop时可以使用，但是构建前你需要手工处理<br /> -o,--output &lt;dir&gt;  输出目录，默认 apk.out<br /> -p,--frame-path &lt;dir&gt; 使用dir中的框架文件<br /> -r,--no-res 不解码资源文件<br /> -s,--no-src 不解码源文件<br /> -t,--frame-tag &lt;tag&gt; 使用被标记未tag的框架</p>
</td>
</tr>
<tr>
<td>b</td>
<td>
<p>命令格式：<pre class="crayon-plain-tag">apktool [-q|--quiet OR -v|--verbose] b[uild] [options] &lt;app_path&gt;</pre></p>
<p>对解码后的目录进行重新打包，可用选项：<br />-a,--aapt &lt;loc&gt; 使用位于loc的资产打包工具<br /> -c,--copy-original 复制原始的AndroidManifest.xml 和 META-INF目录<br /> -d,--debug 设置android:debuggable为true<br /> -f,--force-all 跳过变更检测并构建所有文件<br /> -o,--output &lt;dir&gt; 输出apk的未知，默认dist/name.apk<br /> -p,--frame-path &lt;dir&gt; 使用位于dir的框架文件</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg"><a id="dex2jar"></a>dex2jar</span></div>
<p>进行.dex格式、.smali格式、.class格式之间的转换。</p>
<div class="blog_h3"><span class="graybg">子命令说明</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>d2j-dex2jar</td>
<td>
<p>转换.dex为.jar文件</p>
<p>选项：<br />-d,--debug-info 转译调试信息<br /> -e,--exception-file &lt;file&gt; 转换过程中异常信息的保存位置$current_dir/[file-name]-error.zip<br /> -f,--force 强制覆盖目标jar文件<br /> -n,--not-handle-exception 不处理dex2jar抛出的任何异常<br /> -o,--output &lt;out-jar-file&gt; 输出jar文件名称，默认 $current_dir/[file-name]-dex2jar.jar<br /> -os,--optmize-synchronized optmize-synchronized</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">用法示例</span></div>
<pre class="crayon-plain-tag"># 转换apk中的.dex为Java的class文件，这些class文件打包在apk_to_decompile-dex2jar.jar中
d2j-dex2jar -f ~/path/to/apk_to_decompile.apk </pre>
<div class="blog_h2"><span class="graybg"><a id="adb"></a>adb（Android Debug Bridge）</span></div>
<p>命令格式：<pre class="crayon-plain-tag">adb [子命令] [选项]</pre> </p>
<div class="blog_h3"><span class="graybg">命令选项</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-a</td>
<td>使adb在所有网络接口上监听连接</td>
</tr>
<tr>
<td>-d</td>
<td>使命令仅针对USB连接的设备有效</td>
</tr>
<tr>
<td>-e</td>
<td>使命令仅针对运行中的模拟器有效</td>
</tr>
<tr>
<td>-s &lt;specific device&gt;</td>
<td>使命令仅针对与特定设备或模拟器，通过serial number或 qualifier指定设备</td>
</tr>
<tr>
<td>-H</td>
<td>adb服务器的主机名，默认localhost</td>
</tr>
<tr>
<td>-P</td>
<td>adb服务器的监听端口，默认5037</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">子命令说明</span></div>
<p>列出设备、连接/断开设备：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>devices [-l]</td>
<td>列出所有连接的设备，-l会显示设备的qualifier</td>
</tr>
<tr>
<td>connect &lt;host&gt;[:&lt;port&gt;]</td>
<td>通过TCP/IP连接到某个设备，默认5555端口</td>
</tr>
<tr>
<td>disconnect [&lt;host&gt;[:&lt;port&gt;]]</td>
<td>断开TCP/IP连接，不指定主机和端口，则端口所有通过TCP/IP连接的设备</td>
</tr>
</tbody>
</table>
<p>针对设备的子命令：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 35%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>adb push [-p] &lt;localpath&gt; &lt;remotepath&gt;</td>
<td>拷贝目录或者文件到设备，-p显示进度</td>
</tr>
<tr>
<td>adb pull [-p] [-a] &lt;remote&gt; [&lt;local&gt;]</td>
<td>拷贝目录或者文件到本地，-a拷贝时间戳和UNIX文件mode</td>
</tr>
<tr>
<td>adb sync [ &lt;directory&gt; ]</td>
<td>当本地文件变化时，拷贝目录到设备</td>
</tr>
<tr>
<td>adb shell</td>
<td>交互式的运行Linux命令，示例：<br />
<pre class="crayon-plain-tag"># 列出已安装的包
adb shell pm list packages
# 得到指定包的APK路径
adb shell pm path com.huawei.mw
# 拷贝出APK包
adb pull /data/app/com.huawei.mw-1/base.apk</pre>
</td>
</tr>
<tr>
<td>adb shell &lt;command&gt;</td>
<td>运行Linux命令</td>
</tr>
<tr>
<td>adb emu &lt;command&gt;</td>
<td>运行模拟器控制台命令</td>
</tr>
<tr>
<td>adb logcat [ &lt;filter-spec&gt; ]</td>
<td>显示设备日志</td>
</tr>
<tr>
<td>adb install [-lrtsdg] &lt;file&gt;</td>
<td>
<p>安装APK到设备，-r替换已经存在的应用</p>
<p>报错：<br />INSTALL_FAILED_ALREADY_EXISTS  已经存在，使用-r<br />INSTALL_FAILED_PERMISSION_MODEL_DOWNGRADE  删掉重装</p>
</td>
</tr>
<tr>
<td>adb uninstall [-k] &lt;package&gt;</td>
<td>从设备上移除软件包，-k表示保留应用的数据和缓存</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg"><a id="keytool"></a>keytool</span></div>
<p>JRE自带密钥数据库管理工具。用于加密密钥对、X.509证书链、受信任证书的管理。该工具支持基于DES算法的密码管理。</p>
<p>一个密钥数据库（keystore）可以包含多个密钥条目，以别名（alias）进行唯一区分。</p>
<p>命令格式：<pre class="crayon-plain-tag">keytool [commands]</pre></p>
<div class="blog_h3"><span class="graybg">子命令说明</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 18%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-gencert</td>
<td>
<p>可用选项：</p>
<pre class="crayon-plain-tag">{-rfc} {-infile infile} {-outfile outfile} {-alias alias} {-sigalg sigalg}
{-dname dname} {-startdate startdate {-ext ext}* {-validity valDays}
[-keypass keypass] {-keystore keystore} [-storepass storepass]
{-storetype storetype} {-providername provider_name}
{-providerClass provider_class_name {-providerArg provider_arg}}
{-v} {-protected} {-Jjavaoption} </pre>
<p>生成一个证书，作为证书请求的响应。该命令从infile（不指定该选项则使用stdin）读取证书请求，使用alias这个私钥进行签名并输出X.509格式的证书到outfile（不指定则使用stdout）</p>
<p>当指定-rfc选项时，输出格式是基于Base64的PEM，否则使用二进制的DER</p>
<p>当指定dname选项时，该选项作为输出证书的subject字段，不指定则使用请求中该字段的值</p>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>-genkeypair</td>
<td>
<p>可用选项：</p>
<pre class="crayon-plain-tag">{-alias alias} {-keyalg keyalg} {-keysize keysize} {-sigalg sigalg}
[-dname dname] [-keypass keypass] {-startdate value} {-ext ext}*
{-validity valDays} {-storetype storetype} {-keystore keystore}
[-storepass storepass]
{-providerClass provider_class_name {-providerArg provider_arg}}
{-v} {-protected} {-Jjavaoption}</pre>
<p>别名-genkey，用于生成密钥对（公钥/私钥），并包装公钥为X.509 v3自签名格式的证书，该证书作为单元素证书链存储。这个自签名证书 +  私钥作为keystore中的一个条目保存，以alias唯一识别</p>
<p>-keyalg 指定生成密钥对的算法，默认DSA，可选RSA<br />-keysize 指定密钥长度，使用RSA算法时默认2048；使用1024算法时默认1024 <br />-sigalg  指定签名自签名证书时使用的算法，该算法必须和keyalg算法兼容<br />-dname  关联到alias值的X509可识别名称（Distinguished Name），并且用作自签名证书的issuer、subject这两个字段<br />-keypass  用于保护私钥的密码<br />-startdate  自签名证书有效期开始点</p>
<p>某些选项如果不提供，会交互式的提示</p>
</td>
</tr>
<tr>
<td>-genseckey</td>
<td>生存密钥并存储到keystore中</td>
</tr>
<tr>
<td>-importcert</td>
<td>
<p>可用选项：</p>
<pre class="crayon-plain-tag">{-alias alias} {-file cert_file} [-keypass keypass] {-noprompt} {-trustcacerts}
{-storetype storetype} {-keystore keystore} [-storepass storepass]
{-providerName provider_name}
{-providerClass provider_class_name {-providerArg provider_arg}}
{-v} {-protected} {-Jjavaoption}</pre>
<p>从cert_file文件中读取证书或者证书链，并且存储到keystore中，以alias进行识别</p>
<p>支持导入X509 v1 v2 v3格式的证书，以及这些证书形成的PKCS#7格式的证书链。数据必须是二进制的或者是BASE64编码的。对于BASE64格式，证书必须以-----BEGIN开头-----END结束</p>
<p>之所以需要导入证书，可能出于以下目的：</p>
<ol>
<li>添加被导入证书到受信任证书列表</li>
<li>添加受信任CA签发的证书</li>
</ol>
</td>
</tr>
<tr>
<td>-importpassword</td>
<td>导入一个密码</td>
</tr>
<tr>
<td>-importkeystore</td>
<td>从其它keystore导入内容</td>
</tr>
<tr>
<td>-certreq</td>
<td>制作证书请求</td>
</tr>
<tr>
<td>-exportcert</td>
<td>导出证书</td>
</tr>
<tr>
<td>-list</td>
<td>列出alias对应条目的信息，打印到标准输出</td>
</tr>
<tr>
<td>-printcert</td>
<td>
<p>从文件、SSL服务器、被签名JAR等地方读取证书信息</p>
</td>
</tr>
<tr>
<td>-printcertreq</td>
<td>打印证书请求</td>
</tr>
<tr>
<td>-printcrl</td>
<td>读取证书吊销列表 </td>
</tr>
<tr>
<td>-storepasswd</td>
<td>修改keystore的密码</td>
</tr>
<tr>
<td>-keypasswd</td>
<td>修改某个条目的密码</td>
</tr>
<tr>
<td>-delete</td>
<td>从keystore中删除某个条目</td>
</tr>
<tr>
<td>-changealias </td>
<td>修改alias</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">用法示例</span></div>
<pre class="crayon-plain-tag"># 生成一个密钥对，并添加到数据库中
keytool -genkeypair -v -keystore ~/Android/Keystores/alexwong.keystore 
    -alias development -keyalg RSA -keysize 2048 -validity 36500</pre>
<div class="blog_h2"><span class="graybg"><a id="jarsigner"></a>jarsigner</span></div>
<p>对JAR文件进行签名，或者执行签名校验 —— 签名是否有效、签名后的JAR是否完整。</p>
<p>被签名后的JAR文件，仅仅是在META-INF目录中多了两个额外的文件：</p>
<ol>
<li>以.SF为扩展名的签名文件</li>
<li>以.DSA、.RSA或者.EC为扩展名的签名块文件</li>
</ol>
<p>命令格式：</p>
<pre class="crayon-plain-tag"># 进行签名
jarsigner [ options ] jar-file alias
# 校验签名
jarsigner -verify [ options ] jar-file [alias ...]</pre>
<div class="blog_h3"><span class="graybg">命令示例</span></div>
<pre class="crayon-plain-tag"># 签名一个APK（JAR）文件
jarsigner -keystore ~/Android/Keystores/alexwong.keystore -storepass simple
    -keypass simple -signedjar android-signed.apk android.apk  development</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/android-faq">Android知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/android-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
