社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
对于Android开发来说,HTTP是网络开发中最为重要的、使用频率最高的手段,也是面试常问到的面试题。因此,深入了解HTTP是必备技能,只有了解它的基本原理才能够更好的运用。
HTTP是一种应用层协议,它通过TCP实现了可靠的数据传输,能够保证数据的完整性、正确性。对于移动开发而言,网络应用基本上都是C/S架构。客户端通过向服务器发起特定的请求,服务器返回结果,客户端解析结果,再将结果展现在UI上。详细交互的流程分为如下几步:
有人可能对上面流程有所疑惑,在开发过程用到的网络框架中从来都只要请求方式(post、get、put、delete等)和传递URL、参数,没有上述流程中的1、2、3步,而且IP+端口号是SOCKET通信俩大必备要素;如果你有如此疑惑,你就该仔细的看看这篇文章,首先给你解惑,我们的后台服务一般不会直接暴露IP和端口号,而是申请域名(如:www.baidu.com),所以我们传递URL首先进入域名服务器,在域名服务器中为我们进行了1-3步操作;HTTP确实是socket实现的。
在模拟http交互的过程前我们需要了解HTTP报文格式。不同的请求方式,它们的请求格式可能是不一样的,请求格式就是所谓的报文格式。但是通常来说一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成的,如下图:
//这里不展示域名解析,IP和PORT都为解析后的
public class Client {
//URL通过域名解析后的IP
public String ip;
//URL通过域名解析后的PORT,即服务器的端口
public int port;
//请求参数
private Map<String,String>mParamsMap = new HashMap<>();
//客户端Socket
Socket mSocket;
public Client(String ip,int port){
this.port = port;
this.ip = ip;
}
public void addParam(String key,String value){
mParamsMap.put(key, value);
}
public void execute(){
try{
//创建Socket连接
mSocket = new Socket(ip,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 {
try {
if (mSocket!=null){
mSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//代码省略
}
Client构造函数中传入请求的URL地址经DNS解析后成IP+PORT,然后用户调用addParam函数添加普通的文本参数,当用户设置好参数之后就可以通过execute函数执行该请求,在execute函数中客户端首先创建Socket连接,目标地址就是用户执行的URL。连接成功之后客户端就可以获取到输入、输出流,通过输出流客户端可以向服务端发送数据,通过输入流则可以获取服务端返回的数据。之后依次写入header、请求参数、最后等待Response的返回。
我们将header固定做出如下设置:
/**
*
* 模拟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.ip+":"+this.port);
outputStream.println("Content-Type:multipart/form-data;boundary = "+boundary);
outputStream.println("User-Agent:android");
outputStream.println();//空行
}
然后,我们将mParamsMap中的所有参数通过输出流传递给服务器:
/**
*
* 输入参数格式:
* --boundary
* Content-Disposition:form-data;name = "参数名"
* 空行
* 参数值
*/
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+"--");
}
等待服务器返回结果:
//将返回的数据输入到控制台
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);
}
}
此时客户端的流程就执行完毕了。
下面看服务端的流程代码:
public class SimpleHttpServer extends Thread {
public static final int HTTP_POST = 8000;
static ServerSocket mSocket = null;
public SimpleHttpServer(){
try {
//构造服务端Socket,监听8000端口
mSocket = new ServerSocket(HTTP_POST);
} 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();
}
}
}
SimpleHttpServer的构造函数中传递一个监听8000端口的服务端Socket,并开启无限循环,在该循环中调用ServerSocket的accept()函数等待客户端的连接,该函数会阻塞,直到客户端进行连接,接收连接后构造一处理线程。
public class DeliverThread extends Thread {
Socket mClientSocket;
//输入流
BufferedReader mInputStream;
//输出流
PrintStream mOutputStream;
//请求方式GET、POST等
String httpMethod;
//子路径
String subPath;
//分隔符
String boundary;
//请求参数
Map<String,String> mParams = new HashMap<>();
//是否已经解析完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
try {
if (mInputStream!=null) {
mInputStream.close();
}
if (mOutputStream!=null) {
mOutputStream.close();
}
if (mClientSocket!=null) {
mClientSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//省略代码
}
上面代码中主要包含如下步骤:
下面我们进行解析请求
private void parseRequest(){
String line;
try{
int lineNum = 0;
//从输入流读取数据
while ((line = mInputStream.readLine())!=null){
//第一行为请求行
if (lineNum == 0){
parseRequestLine(line);
}
//解析header参数
if (lineNum != 0 && !isParseHeader){
parseHeaders(line);
}
//判断是否是结束行
if (isEnd(line)){
break;
}
//解析请求参数
if (isParseHeader){
parseRequestParams(line);
}
lineNum++;
line = mInputStream.readLine();
}
}catch (IOException e){
e.printStackTrace();
}
}
参照之前的Client进行解析,首先解析第一行请求数据,即lineNum为0时:
/**
* 解析请求行 "POST /api/login/ HTTP/1.1"
* tempString = {POST,/api/login/,HTTP/1.1}
*/
private void parseRequestLine(String lineOne){
String[] tempString = lineOne.split(" ");
httpMethod = tempString[0];
subPath = tempString[1];
System.out.println("请求方式:"+httpMethod);
System.out.println("子路径:"+subPath);
System.out.println("HTTP版本:"+tempString[2]);
}
通过报文格式理解,请求行分为3部分即请求方式、请求子路径、协议版本,它们之间通过空格进行分割。请求行后面紧跟Header:
/**
* 解析header,参数为每个header的字符串
* 见client中writeHeader方法
*
*/
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);
}
}
/**
* 解析分隔符 boundary
* "Content-Type:multipart/form-data;boundary = xxxxxx"
*
*/
private String parseSecondField(String line){
String[] headerArray = line.split(";");
parseHeaderParam(headerArray[0]);
if (headerArray.length>1){
return headerArray[1].split("=")[1].trim();
}
return "";
}
/**
* 解析单个header
* "content-length: 123"
*/
private void parseHeaderParam(String headerLine){
String[] keyvalue = headerLine.split(":",0);
System.out.println("header参数名:"+keyvalue[0].trim()+"参数值:"+headerLine.substring(keyvalue[0].length()+1));
}
接下来就是参数解析:
/**
*解析参数
* -- boundary
* Content-Disposition: from-data;name = "参数名"
* 空行
* 参数值
*
*/
private void parseRequestParams(String paramLine) throws IOException {
String r = boundary;
String t ="--"+r;
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){
if (line.equals("--"+boundary+"--")){
return true;
}
return false;
}
至此,整个请求的各部分均解析完成。后面就要处理用户请求返回结果:
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();
}
}
此时所有的过程展示完成,下面做个测试:
public class TestHttpActivity extends AppCompatActivity implements View.OnClickListener {
SimpleHttpServer simpleHttpServer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_http);
//启动的时候开起服务器
if (simpleHttpServer==null){
simpleHttpServer = new SimpleHttpServer();
}
simpleHttpServer.start();//开启服务器
findViewById(R.id.client).setOnClickListener(this);
}
@Override
public void onClick(View v) {
//点击请求服务
Thread thread = new Thread(
new Runnable() {
@Override
public void run() {
Client httpPost = new Client("127.0.0.1", 8000);
httpPost.addParam("userName","admin");
httpPost.addParam("passWord","admin");
//执行操作
httpPost.execute();
}
}
);
thread.start();
}
}
执行结果
服务端:
客户端:
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!