HTTP实际上是基于TCP的应用层协议,它在更高的层次封装了TCP的使用细节,是网络请求操作更为易用. TCP连接是因特网上基于流的可靠连接,它为HTTP提供了一条可靠的比特传输管道. 从TCP连接一端填入的字节会从另一端以原有的顺序,正确地传递出来,如下图所示.

Client客户端Client客户端Web服务端Web服务端数据在网络中传输

TCP的数据是通过名为IP分组(或IP数据报)的小数据块来发送的. 这样的话,如下图的HTTP协议所示,HTTP就是”HTTP over TCP over IP”这个”协议栈”中的最顶层了.

HTTP要传送一条报文时,会以流的形式将报文数据的内容通过一条打开的TCP连接按序传输. TCP收到数据流之后,会将数据流分割成被称作段的小数据块,并将段封装在IP分组中,通过因特网进行传输. 所有这些工作都是由TCP/IP软件来处理的,程序员什么都看不到.

下面我们就模拟一个简单的Web服务器来深度了解一下HTTP的报文格式以及HTTP协议与TCP协议之间的协作原理.

一个HTTP请求就是一个典型的C/S模式,服务端在监听某个端口,客户端向服务端的端口发起请求. 服务端解析请求,并且向客户端返回结果. 下面我们就先看看这个简单的Web服务端.

代码如下:

public class SimpleHttpServer extends Thread {

    public static void main(String[] args) {
new SimpleHttpServer().start();
}
// 服务端Socket
ServerSocket mSocket = null; public SimpleHttpServer() {
try {
mSocket = new ServerSocket(SocketTool.PORT);
} catch (IOException e) {
e.printStackTrace();
}
if (mSocket == null) {
throw new RuntimeException("服务器Socket初始化失败");
}
} @Override
public void run() {
try {
while (true) {
// 无限循环,进入等待连接状态
System.out.println("等待连接中");
// 一旦接收到连接请求,构建一个线程来处理
new DeliverThread(mSocket.accept()).start();
} } catch (IOException e) {
e.printStackTrace();
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

SimpleHttpServer继承自Thread类,在构造函数中我们会创建一个监听10086端口的服务端Socket,并且覆写Thread的run函数,在该函数中开启无限循环,在该循环中调用ServerSocket的accept()函数等待客户端的连接,该函数会阻塞,知道有客户端进行连接,接收连接之后会构造一个线程来处理该请求. 也就是说,SimpleHttpServer本身是一个子线程,它在后台等待客户端的连接,一旦接收到连接又会创建一个线程处理该请求,避免阻塞SimpleHttpServer线程.

现在我们一步一步来分析连接处理线程DeliverThread的代码:

static class DeliverThread extends Thread {
Socket mClientSocket;
// 输入流
BufferedReader mInputStream;
// 输出流
PrintStream mOutputStream;
// 请求方法,GET、POST等
String httpMethod;
// 子路径
String subPath;
// 分隔符
String boundary;
// 请求参数
Map<String, String> mParams = new HashMap<String, String>();
// 请求headers
Map<String, String> mHeaders = new HashMap<String, String>();
// 是否已经解析完Header
boolean isParseHeader = false; public DeliverThread(Socket socket) {
mClientSocket = socket;
} @Override
public void run() {
try {
// 获取输入流
mInputStream = new BufferedReader(new InputStreamReader(
mClientSocket.getInputStream()));
// 获取输出流
mOutputStream = new PrintStream(mClientSocket.getOutputStream());
// 解析请求
parseRequest();
// 返回Response
handleResponse();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭流和Socket
IoUtils.closeQuickly(mInputStream);
IoUtils.closeQuickly(mOutputStream);
IoUtils.closeSocket(mClientSocket);
}
}
//代码省略
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

DeliverThread也继承自Thread,在run函数中主要封装了如下步骤:

  1. 获取客户端Socket的输入,输出流用于读写数据;
  2. 解析请求参数;
  3. 处理,返回请求结果;
  4. 关闭输入,输出流,客户端Socket.

上文我们说过TCP的数据操作是基于流的,因此得到客户端Socket连接之后,我们首先获取到它的输入,输出流. 其中我们可以从输入流中获取该请求的数据,而通过输出流就可以将结果返回给该客户端. 得到流之后我们首先解析该请求,根据它请求的路径,header,参数等作出处理,最后将处理结果通过输出流返回给客户端. 最终关闭流和Socket.

在分析HTTP请求解析的代码之前,我们再来回顾一下HTTP请求的报文格式,如下图所示

下面我们看一下解析请求的具体实现,即parseRequest函数:

private void parseRequest() {
String line;
try {
int lineNum = 0;
// 从输入流读取客户端发送过来的数据
while ((line = mInputStream.readLine()) != null) { //第一行为请求行
if (lineNum == 0) {
parseRequestLine(line);
}
// 判断是否是数据的结束行
if (isEnd(line)) {
break;
}
// 解析header参数
if (lineNum != 0 && !isParseHeader) {
parseHeaders(line);
}
// 解析请求参数
if (isParseHeader) {
parseRequestParams(line);
}
lineNum++;
}
} catch (IOException e) {
e.printStackTrace();
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

在parseRequest函数中,我们按照数据的分步进行解析. 首先解析第一行的请求行数据,即当lineNum为0时调用parseRequestLine函数进行解析. 该函数的实现如下:

// 解析请求行
private void parseRequestLine(String lineOne) {
String[] tempStrings = lineOne.split(" ");
httpMethod = tempStrings[0];
subPath = tempStrings[1];
System.out.println("请求行,请求方式 : " + tempStrings[0] + ", 子路径 : " + tempStrings[1]
+ ",HTTP版本 : " + tempStrings[2]);
System.out.println();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在上文的格式分析中我么你说过,请求行由3部分组成,即请求方式,请求子路径,协议版本,它们之间通过空格来进行分割. 因此,在parseRequestLine中我们用空格分隔请求行字符串,得到的结果就是这3个值.

请求行后面紧跟着请求Header,因此,我们的下一步就是解析Header区域. 对应的函数为parseHeaders,代码如下:

// 解析header,参数为每个header的字符串
private void parseHeaders(String headerLine) {
// header区域的结束符
if (headerLine.equals("")) {
isParseHeader = true;
System.out.println("-----------> header解析完成\n");
return;
} else if (headerLine.contains("boundary")) {
boundary = parseSecondField(headerLine);
System.out.println("分隔符 : " + boundary);
} else {
// 解析普通header参数
parseHeaderParam(headerLine);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

每个header为一个独立行,格式为参数名: 参数值,还有一种情况是参数名1: 参数值2;参数名2: 参数值2. 例如下面两个header:

Content-Length: 1234
Content-Type: multipart/form-data; boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp
  • 1
  • 2
  • 1
  • 2

第一个header参数名为Content-Length,值为1234. 第二个header在同一行内有两个数据,分别为值为multipart/form-data的Content-Type,以及值为OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp的boundary. header与请求参数之间通过一个空行分隔,因此,我们检测到header数据为空时则认为是header参数的结束行.

当一个header行数据中含有boundary字段时,则调用parseSecondField函数解析,该函数实现如下:

// 解析header中的第二个参数
private String parseSecondField(String line) {
String[] headerArray = line.split(";");
parseHeaderParam(headerArray[0]);
if (headerArray.length > 1) {
return headerArray[1].split("=")[1];
}
return "";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

因为boundary参数在header格式的第二个参数的位置上,因此,这里通过分号进行分割,获取数组第二个位置的数据,也就是boundary=OCqxMF6-JxtxoMDHmoG5W5eY9MGRsTBp,然后再进行解析.

普通的header则是参数名: 参数值的格式,我们通过parseHeaderParam函数进行解析,代码如下:

// 解析单个header
private void parseHeaderParam(String headerLine) {
String[] keyvalue = headerLine.split(":");
mHeaders.put(keyvalue[0].trim(), keyvalue[1].trim());
System.out.println("header参数名 : " + keyvalue[0].trim() + ", 参数值 : "
+ keyvalue[1].trim());
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

解析完header之后我们就开始解析请求参数了. 对于POST和PUT请求来说,它们的每个参数格式都是固定的,格式如下:

--boundary值
header-1: value-1
...
header-n: value-n
空行
参数值
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

由于在我们的例子中每个请求参数只有一个header字段,因此, 我们的每个参数的格式简化为:

--boundary
Content-Disposition: form-data; name="参数名"
空行
参数值
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

根据上述格式,我们再来看解析函数:

// 解析请求参数
private void parseRequestParams(String paramLine) throws IOException {
if (paramLine.equals("--" + boundary)) {
// 读取Content-Disposition行
String ContentDisposition = mInputStream.readLine();
// 解析参数名
String paramName = parseSecondField(ContentDisposition);
// 读取参数header与参数值之间的空行
mInputStream.readLine();
// 读取参数值
String paramValue = mInputStream.readLine();
mParams.put(paramName, paramValue);
System.out.println("参数名 : " + paramName + ", 参数值 : " + paramValue);
}
} // 是否是结束行
private boolean isEnd(String line) {
return line.equals("--" + boundary + "--");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

至此,整个请求的各个部分均已解析完成. 后面要做的就是根据用户的请求返回结果. 在这里我们直接返回了一个固定的Response. 代码如下:

// 返回结果
private void handleResponse() {
//模拟处理耗时
sleep();
//向输出流写数据
mOutputStream.println("HTTP/1.1 200 OK");
mOutputStream.println("Content-Type: application/json");
mOutputStream.println();
mOutputStream.println("{\"stCode\":\"success\"}");
} private void sleep() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在handleResponse函数中,通过Socket的输出流向客户端写入数据. 写入的数据也遵循了响应报文的基本格式,格式如下:

响应行
header区域
空行
响应数据
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

向客户端写完数据后,我们就会关闭输入,输出流以及Socket,至此,整个请求,响应流程完毕.

服务端逻辑分析完成之后我们再来看看客户端的实现. 从上述的分析以及平时的开发经验我们知道,客户端要做的就是主动向服务器发起HTTP请求,它们之间的通信通道就是TCP/IP,因此,也是基于Socket实现. 下面我们就模拟一个Http POST请求,代码如下:

public class HttpPost {
public String url;
// 请求参数
private Map<String, String> mParamsMap = new HashMap<String, String>();
private static final int PORT = 10086;
//客户端Socket
Socket mSocket; public HttpPost(String url) {
this.url = url;
} public void addParam(String key, String value) {
mParamsMap.put(key, value);
} public void execute() {
try {
// 创建Socket连接
mSocket = new Socket(this.url, PORT);
PrintStream outputStream = new PrintStream(mSocket.getOutputStream());
BufferedReader inputStream = new BufferedReader(new InputStreamReader(
mSocket.getInputStream()));
final String boundary = "my_boundary_123";
// 写入header
writeHeader(boundary, outputStream);
// 写入参数
writeParams(boundary, outputStream);
// 等待返回数据
waitResponse(inputStream);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (mSocket != null) {
try {
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/代码省略
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

HttpPost构造函数中传入请求的URL地址,然后用户可以调用addParam函数添加普通的文本参数,当用户设置好参数之后就可以通过execute函数执行该请求. 在execute函数中客户端首先创建Socket连接,目标地址就是用户执行的URL以及端口. 连接成功之后客户端就可以获取到输入,输出流,通过输出流客户端可以向服务端发送数据,通过输入流则可以获取服务端返回的数据. 之后我们一次写入header,请求参数,最后等待Response的返回.

在该示例中,我们将header固定作出如下设置,代码如下:

private void writeHeader(String boundary, PrintStream outputStream) {
outputStream.println("POST /api/login/ HTTP/1.1");
outputStream.println("content-length:123");
outputStream.println("Host:" + this.url + ":" + PORT);
outputStream.println("Content-Type: multipart/form-data; boundary=" + boundary);
outputStream.println("User-Agent:android");
outputStream.println();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

然后,我们将mParamsMap中的所有参数通过输出流传递给服务端,代码如下:

private void writeParams(String boundary, PrintStream outputStream) {
Iterator<String> paramsKeySet = mParamsMap.keySet().iterator();
while (paramsKeySet.hasNext()) {
String paramName = paramsKeySet.next();
outputStream.println("--" + boundary);
outputStream.println("Content-Disposition: form-data; name=" + paramName);
outputStream.println();
outputStream.println(mParamsMap.get(paramName));
}
// 结束符
outputStream.println("--" + boundary + "--");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

每个参数都必须遵循特定的格式,在上文服务器解析参数时就是按照这里设定的格式进行. 格式如下:

--boundary
Content-Disposition: form-data; name="参数名"
空行
参数值
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

当参数结束之后需要写一个结束行,格式为:两个斜杠加上boundary值再加上两个斜杠. 此时请求数据就已经发送到服务端,此时我们等待服务器返回数据. 得到返回的数据之后将结果输出到控制台. 代码如下:

private void waitResponse(BufferedReader inputStream) throws IOException {
System.out.println("请求结果: ");
String responseLine = inputStream.readLine();
while (responseLine == null || !responseLine.contains("HTTP")) {
responseLine = inputStream.readLine();
}
//输出Response
while ((responseLine = inputStream.readLine()) != null) {
System.out.println(responseLine);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

此时,客户端的流程也执行完毕. 
下面,运行这个例子. 首先需要启动服务器,代码如下:

public static void main(String[] args) throws Exception {
new SimpleHttpServer().start();
}
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

服务器启动之后就会在后台等待客户端发起连接,此时我们再启动客户端,设置参数之后执行一个Http POST请求:

HttpPost httpPost = new HttpPost("127.0.0.1");
// 设置两个参数
httpPost.addParam("username", "mr.simple");
httpPost.addParam("pwd", "my_pwd123");
// 执行请求
httpPost.execute();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

执行结果如下图所示:

  • 服务端接到请求 

  • 客户端请求结果 

本文中我们用一个简单的示例模拟了Web服务器与客户端你的交互过程. 整个示例就是在TCP智商封装了一层HTTP,用户通过HTTP相关的类进行操作,但是传输层依旧是通过TCP层. 客户端与服务端之间开辟了一条双向的Socket,通过输入,输出流向对方发送,获取数据,而双方都遵循了规定的HTTP协议,因此,数据的发送与解析都能够顺利进行. 通过HTTP层屏蔽了直接使用Socket的复杂细节,使得整个通信过程更加简单,易用.

完整示例: 
SocketSamples

HTTP网络请求原理 (三) 简单模拟HTTP服务器的更多相关文章

  1. Flutter学习(7)——网络请求框架Dio简单使用

    原文地址: Flutter学习(7)--网络请求框架Dio简单使用 | Stars-One的杂货小窝 Flutter系列学习之前都是在个人博客发布,感兴趣可以过去看看 网络请求一般APP都是需要的,在 ...

  2. React Native中的网络请求fetch和简单封装

    React Native中的网络请求fetch使用方法最为简单,但却可以实现大多数的网络请求,需要了解更多的可以访问: https://segmentfault.com/a/1190000003810 ...

  3. 【Tomcat】Tomcat工作原理及简单模拟实现

    Tomcat应该都不陌生,我们经常会把写好的代码打包放在Tomcat里并启动,然后在浏览器里就能愉快的调用我们写的代码来实现相应的功能了,那么Tomcat是如何工作的? 一.Tomcat工作原理 我们 ...

  4. IOS网络请求原理

    1,为什么要用到网络,  只有通过网络跟外界进行数据交互,数据更新,应用才能保持哦新鲜.活力 如果没有网络,也就缺少数据变化.变成一滩死水. 2, 良好的移动网络应用 = 良好的UI + 良好的用户体 ...

  5. android 网络请求Volley的简单使用

    下载到本地jar包或者在线导入,jar地址:链接:http://pan.baidu.com/s/1gf3VZAb 密码:mmye //定义变量 private RequestQueue mQueue= ...

  6. Jest中Mock网络请求

    Jest中Mock网络请求 最近需要将一个比较老的库修改为TS并进行单元测试,修改为TS还能会一点,单元测试纯粹是现学现卖了,初学Jest框架,觉得在单元测试中比较麻烦的就是测试网络请求,所以记录一下 ...

  7. iOS - ASIHTTPRequest 网络请求

    前言 使用 iOS SDK 中的 HTTP 网络请求 API,相当的复杂,调用很繁琐,ASIHTTPRequest 就是一个对 CFNetwork API 进行了封装,并且使用起来非常简单的一套 AP ...

  8. 爬虫中网络请求的那些事之urllib库

    目录 爬虫之网络请求中的那些事 urllib库 urlopen函数 urlretrieve函数 urlencode.parse_qs函数 urlparse.urlsplit函数: request.Re ...

  9. Retrofit网络请求库应用02——json解析

    PS:上一篇写了Retrofit网络请求库的简单使用,仅仅是获取百度的源码,来证明连接成功,这篇讲解如何解析JSON数据,该框架不再是我们之前自己写的那样用JsonArray等来解析,这些东西,我们都 ...

随机推荐

  1. xtu summer individual 1 E - Palindromic Numbers

    E - Palindromic Numbers Time Limit:2000MS     Memory Limit:32768KB     64bit IO Format:%lld & %l ...

  2. zoj 2932 The Seven Percent Solution

    The Seven Percent Solution Time Limit: 2 Seconds      Memory Limit: 65536 KB Uniform Resource Identi ...

  3. cp: omitting directory解决方案

    cp: omitting directory是因为目录下面还有目录.应该使用递归方法.需要加入-r参数. 及:cp -r 该目录名.

  4. UVA 10245 The Closest Pair Problem【分治】

    题目链接: http://acm.hust.edu.cn/vjudge/problem/visitOriginUrl.action?id=21269 题意: 求平面最近点对. 分析: 经典问题. n比 ...

  5. QT程序--小工具集合

    这是在大一第一学期时写的参加程序设计大赛的作品,当时参加的时候仅仅只有贪吃蛇,迷宫算法和文件加密这三个功能,而且当时的界面并没有进行任何美化,现在想起来有点可惜.然而这并不是一个只写一遍的软件,在后期 ...

  6. spring mvc 集成freemarker模板

    主要使用到的jar 文件:spring mvc +freemarker.jar 第一步:spring mvc 集成 freemarker <!-- 定义跳转的文件的前后缀 ,视图模式配置--&g ...

  7. Android进程绝杀技--forceStop(转)

    一.概述 1.1 引言 话说Android开源系统拥有着App不计其数,百家争鸣,都想在这“大争之世”寻得系统存活的一席之地.然则系统资源有限,如若都割据为王,再强劲的CPU也会忙不过来,再庞大的内存 ...

  8. 西门子PLC学习笔记六-(Step7指令简单介绍)

    1.指令操作数 指令操作数由操作标示符和參数组成. 操作标识符由主标识符和辅标识符组成. 主标识符有:I(输入过程影像寄存器).Q(输出过程映像寄存器).M(位寄存器).PI(外部输入寄存器).PQ( ...

  9. sql跟踪及tkprof使用

    简述 在oracle数据库中,awr是关于数据库系统总体的负载情况和运行情况的报告.而当系统负载都显示正常,而client运行某些动作响应非常慢,或者某些终端连接的会话运行缓慢或异常时,就须要用到会话 ...

  10. 深入源代码解析Android中的Handler,Message,MessageQueue,Looper

    本文主要是对Handler和消息循环的实现原理进行源代码分析.假设不熟悉Handler能够參见博文< Android中Handler的使用>,里面对Android为何以引入Handler机 ...