android http——网络请求二次封装的框架设计
2016-05-17 18:51
1171 查看
android app中少不了要用到网络请求。网上已经有很多优秀的网络请求框架,谷歌推荐的okhttp、齐射并发的volley 、异步请求的asynchttp。
但是我要说的并不是重新设计网络请求框架,而是基于所有优秀网络请求框架进行的二次封装,成为一个为自己项目所用的网络请求。
不要问为什么这么做,只要你使用到了网络请求,不管有没有用第三方请求框架都必须为它(网络请求流程处理)再次设计一个简单易用的框架,我们把这个过程叫做网络请求的二次封装。
先来看看一个网络请求的流程:
1,get,post请求数据
请求数据需要url,如果是post请求还需要一个map集合在存储请求参数;
那么就可以把请求数据看作:
这么一个数据结构。
2,发出请求
这段代码只是模拟一个post网络请求,并且经过一系列的处理返回成功或者失败到相应的接口。
3,处理结果
拿到结果后 我们一般会根据服务器返回的固定字段来解析json数据:
通过和服务器返回数据相匹配来解析json并保存到相应的实体类中。
4,显示结果
当我们拿到处理后的结果后要发送给相应的页面用于显示。这时候的处理就“八仙过海各显神通”了。不过最常见的就是通过观察者模式,让结果进入发送者内,然后给所有观察者发送网络请求的处理结果。
这就是一个网络请求的大致流程。其中具体的网络请求网上就有很多优秀的处理框架了。我们也没有必要再去自己写,况且自己写的不一定完美。
所以,我们要做的就是既要用别人的框架也要将别人的框架和自己app的耦合性降到最低。
那么我们应该从什么地方入手呢?
先看一张图:
这就是大概的一个接口对接的示意图。
而我们要做的部分就是将红色框,即app内部请求和解析进行二次封装。
—————————————————分割线———————————————–
那么我要说的就是为网络请求流程处理设计的框架。
先来看一张我要讲的网络请求二次封装的框架结构示意图:
得到处理过的结果的ResponseResult类(也就是图中的ResponseSuccess等类)可以使用观察者模式向所有的观察者发出请求返回结果。
接下来我们就一步一步的实现这个二次封装的框架。等到成品出来后,各位看客就能感受到它的简单和易用是多么的强大。
还有MD5Util、Base64、NetWorkUtil;
这没有什么可说的。
1,app key;服务器与前端使用同一个名字来匹配。
2,时间戳;目的是为了控制这个请求在一个时间范围内有效。例如:两分钟。如果超过了两分钟,还在用这个请求请求服务器那就不会成功。
3,接口版本号;升级专用。前端app升级后为了兼容后台接口而定制的。
4,sign(md5加密);这个加密方式每个人有每个人的做法。我的做法是将所有的请求参数加起来生成一个md5密文然后后台拿到我的请求参数后同样的方法生成一个md5密文然后匹配。
这些参数应该和服务器一起去协商定义。
那么有了这些写死的,每次请求都要有的参数我们就可以提前实例化一个map先把他们存起来。
一般也就这些方法,如果有更多方法也没关系。
这个接口先放在这里,暂且最后在实现具体的方法。
这里我们也做一下封装。
需要注意的是,请求参数的封装中貌似多了一个class参数。其实这个参数也算是请求参数的一个。
这个参数的作用是当服务器返回了成功后的结果,json数据解析能够更具这个类来解析。
后面的代码就可以看出它的作用。
抽象类需要三个方法继承。成功,失败和所有。类似于try/catch/finally方法。
这样一来,
成功的请求进ResponseSuccess;这个类中就包含了:对象,状态码和内容;
失败的请求进ResponseFailure;这个类中就包含了:对象,状态码和错误信息;
不管失败和成功都要进ResponseComplete;这个类中就包含了:对象,状态码、内容和错误信息;当然,它的参数会因为状态不同而出现null。
至于抽象类中的RequestAction 则是接下来要讲的。
通过带有构造器的枚举在每一个枚举中都实例化一个请求内容(RequestContent )对象。
这样,当我们调用每一个枚举的时候都会得到一个RequestContent的对象,于此同时也就得到了RequestContent对象中的请求参数map集合实例(getParameters())。
有了请求参数实例我们就可以不断的往里面添加参数。这样做的目的是把填写url,给json数据一个解析参照对象(String.class)以及实例化一个参数集合的工作都交给枚举来完成。
这样,我们只需要调用枚举,传入url和数据解析参照对象的class就能够得到参数集合,就能够发出请求。
其中HttpClient是模拟代码。它指你使用的一切网络请求框架。
因为框架的设计无法用具体代码表达出来,所以这里我选择使用模拟逻辑处理过程。
到这里,整个框架的设计就完成了。那么我们应该怎么去使用呢?
使用网络请求:
不管你在哪里提交网络请求,都只需要这么几行代码就搞定。
那么我们再来看如何添加新的rul:
首先在RequestUrls类中添加一个url:
然后在RequestAction枚举中添加一个新枚举:
这样,添加一个新的请求就完成了。
不管是提交请求,还是添加新请求都是几行代码就解决的问题。
那么我们如何去接收返回结果呢?
不管你是用的是eventbus.jar还是接口还是观察者模式得到返回结果后都可以这样处理:
拿到结果后就可以自己处理一些逻辑了。
整个二次封装的流程就是这样。
这个二次封装的框架目的在于简化请求接收代码,并且留下接口(onSuccess,onFailure)以供第三方框架使用。
现在再返回来看这张图:
我们二次封装网络请求的目的就是做红色的框所做的工作。并且把整个网络请求流程分开来,解耦。
就像几个楔形:服务器<第三方框架<二次封装框架<数据请求;
这样才能构成一个完整的app网络请求流程。而且这样分为几个楔形,成功的达到了解耦的作用。
这样,不管你换哪个第三方请求框架,只要把第三方请求框架的onSuccess()和onFailure()方法给接上去就可以照常使用。
这也是二次封装网络请求而设计框架的最重要的目的。
这里写成博客以供大家前来学习。
但是我要说的并不是重新设计网络请求框架,而是基于所有优秀网络请求框架进行的二次封装,成为一个为自己项目所用的网络请求。
不要问为什么这么做,只要你使用到了网络请求,不管有没有用第三方请求框架都必须为它(网络请求流程处理)再次设计一个简单易用的框架,我们把这个过程叫做网络请求的二次封装。
先来看看一个网络请求的流程:
1,get,post请求数据
请求数据需要url,如果是post请求还需要一个map集合在存储请求参数;
那么就可以把请求数据看作:
String url="http://192.168.1.1:8080/websservice/index/selectindex.action?"; Map<String,Object> parameters=new HashMap(); parameters.put("userinfo.name","admin"); parameters.put("userinfo.password","admins1");
这么一个数据结构。
2,发出请求
private void sendPostRequest(String url, Map<String, Object> params) { //首先判断是否有网络 if (!NetWorkUtil.isNetworkConnected(mContext)) return; HttpClient.post(Context, url, params, new HttpBack() { @Override public void onSuccess(int statusCode, byte[] responseBody) { try { String jsonSuccess = new String(responseBody, "UTF-8"); } catch (Exception e) { e.printStackTrace(); } } @Override public void onFailure(int statusCode, byte[] responseBody, Message error) { try { String jsonFinal = new String(responseBody, "UTF-8"); } catch (Exception e) { e.printStackTrace(); } } });
这段代码只是模拟一个post网络请求,并且经过一系列的处理返回成功或者失败到相应的接口。
3,处理结果
拿到结果后 我们一般会根据服务器返回的固定字段来解析json数据:
//状态码 String TAG_STATUS_CODE = "statusCode"; //请求失败后的失败内容 String TAG_MESSAGE = "message"; //json对象 String TAG_OBJECT = "object"; //json集合 String TAG_OBJECTS = "objects";
通过和服务器返回数据相匹配来解析json并保存到相应的实体类中。
4,显示结果
当我们拿到处理后的结果后要发送给相应的页面用于显示。这时候的处理就“八仙过海各显神通”了。不过最常见的就是通过观察者模式,让结果进入发送者内,然后给所有观察者发送网络请求的处理结果。
这就是一个网络请求的大致流程。其中具体的网络请求网上就有很多优秀的处理框架了。我们也没有必要再去自己写,况且自己写的不一定完美。
所以,我们要做的就是既要用别人的框架也要将别人的框架和自己app的耦合性降到最低。
那么我们应该从什么地方入手呢?
先看一张图:
这就是大概的一个接口对接的示意图。
而我们要做的部分就是将红色框,即app内部请求和解析进行二次封装。
—————————————————分割线———————————————–
那么我要说的就是为网络请求流程处理设计的框架。
先来看一张我要讲的网络请求二次封装的框架结构示意图:
得到处理过的结果的ResponseResult类(也就是图中的ResponseSuccess等类)可以使用观察者模式向所有的观察者发出请求返回结果。
接下来我们就一步一步的实现这个二次封装的框架。等到成品出来后,各位看客就能感受到它的简单和易用是多么的强大。
1,准备工作
/** * 请求的url */ public class RequestUrls { public static final String IP = "http://192.168.1.1:8080/";//url public static final String ROOT_URL = IP + "HttpService/";//项目名 /** * 获取首页数据的URL */ public static final String GET_INDEX_CONTENT_URL = ROOT_URL + "index/selectIndexContent.action"; }
/** * 服务器返回状态吗 */ public class StatusCode { /** * 状态码:请求成功 */ public static final int REQUEST_SUCCESS = 200; /** * 状态码:没有更多数据 */ public static final int NOT_MORE_DATA = 201; /** * 状态码:服务器繁忙 */ public static final int SERVER_BUSY = 300; /** * 状态码:未知错误 */ public static final int SERVER_ERROR = 500; }
还有MD5Util、Base64、NetWorkUtil;
这没有什么可说的。
2,生成默认参数和参数map实例
一般来说,一个网络请求必须带的几个参数:1,app key;服务器与前端使用同一个名字来匹配。
2,时间戳;目的是为了控制这个请求在一个时间范围内有效。例如:两分钟。如果超过了两分钟,还在用这个请求请求服务器那就不会成功。
3,接口版本号;升级专用。前端app升级后为了兼容后台接口而定制的。
4,sign(md5加密);这个加密方式每个人有每个人的做法。我的做法是将所有的请求参数加起来生成一个md5密文然后后台拿到我的请求参数后同样的方法生成一个md5密文然后匹配。
这些参数应该和服务器一起去协商定义。
那么有了这些写死的,每次请求都要有的参数我们就可以提前实例化一个map先把他们存起来。
/** * 生成默认参数和参数map实例 */ public class SignUtils { public static final String KEY_SIGN = "sign"; public static final String KEY_PRIVATE = "key"; public static final String KEY_TIMESTAMP = "timestamp"; public static final String KEY_VERSION = "version"; public static String getSignMD5(Map<String, Object> param) { Collection<String> keySet = param.keySet(); List<String> list = new ArrayList<>(keySet); //对key键值按字典升序排序 Collections.sort(list); String paramStr = ""; for (int i = 0; i < list.size(); i++) { if (list.get(i).equals(KEY_SIGN)) continue; paramStr += list.get(i) + "=" + param.get(list.get(i)) + "&"; } if (!TextUtils.isEmpty(paramStr)) paramStr = paramStr.substring(0, paramStr.length() - 1); return MD5.MD5(paramStr); } public static Map<String, Object> getParameters() { Map<String, Object> params = new HashMap<>(); params.put(KEY_PRIVATE, "alsfoxShop_plat"); params.put(KEY_VERSION,"1.01"); params.put(KEY_TIMESTAMP, System.currentTimeMillis()); return params; } }
3,定义网络请求方法接口
/** * 该接口规定所有请求所需的常量和方法 */ public interface IRequest { //状态码 String TAG_STATUS_CODE = "statusCode"; //请求失败后的失败内容 String TAG_MESSAGE = "message"; //json对象 String TAG_OBJECT = "object"; //json集合 String TAG_OBJECTS = "objects"; //默认超时时间 int VALUE_DEFAULT_TIME_OUT = 20 * 1000; /** * 发送get请求 */ void sendGetRequest(RequestAction action); /** * 发送post请求,包含多文件上传方式的传文件 * * @param action 请求对象 */ void sendPostRequest(RequestAction action); /** * 取消所有请求,可能中断请求 */ void cancelAllRequests(boolean mayInterruptIfRunning); /** * 重新设置请求超时时间 */ void setTimeOut(int value); /** * 下载文件 */ void downloadFile(String url); }
一般也就这些方法,如果有更多方法也没关系。
这个接口先放在这里,暂且最后在实现具体的方法。
4,请求参数封装
上面我说到,请求的参数无非包含rul还有参数的map集合。这里我们也做一下封装。
/** * 外界将请求的地址,请求结果的解析参照对象以及请求参数都保存到这个类中 */ public class RequestContent { /** * 发送请求的路径 */ private String requestUrl; /** * 请求结果的解析参照对象(服务器返回的数据类型) */ private Class<?> cls; /** * 请求参数,new出一个map对象,将默认的先添加进去 */ private Map<String, Object> parameters = SignUtils.getParameters(); /** * 外界传入参数的构造方法,没有返回结果的请求 */ public RequestContent(String requestUrl) { this.requestUrl = requestUrl; } /** * 外界传入参数的构造方法,带返回结果的请求,需要传入一个对象依照它来解析数据 */ public RequestContent(String requestUrl, Class<?> cls) { this.requestUrl = requestUrl; this.cls = cls; } //还有变量的get/set方法,这里就不贴出来了 …… }
需要注意的是,请求参数的封装中貌似多了一个class参数。其实这个参数也算是请求参数的一个。
这个参数的作用是当服务器返回了成功后的结果,json数据解析能够更具这个类来解析。
后面的代码就可以看出它的作用。
5,返回结果封装
/** * 返回结果抽象类,工厂方法模式 */ public abstract class ResponseResult { //请求对象 protected RequestAction requestAction; //状态码 protected int statusCode; }
抽象类需要三个方法继承。成功,失败和所有。类似于try/catch/finally方法。
/** * 成功得到服务器结果后,所有的参数进入这个方法; */ public class ResponseSuccess extends ResponseResult { //服务器返回的内容 private Object content; public Object getContent() { return content; } public void setContent(Object content) { this.content = content; } }
/** * 失败得到服务器结果后,所有的参数进入这个方法 */ public class ResponseFailure extends ResponseResult { private String message;//错误信息 public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
/** * 不管服务器的结果是成功还是失败,所有的参数进入这个方法 */ public class ResponseComplete extends ResponseResult { /** * 不管成功或者失败都会进入的方法 * 类似于try/catch方法中的finally方法 */ //服务器返回的内容 private Object content; private String message;//错误信息 public Object getContent() { return content; } public void setContent(Object content) { this.content = content; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
这样一来,
成功的请求进ResponseSuccess;这个类中就包含了:对象,状态码和内容;
失败的请求进ResponseFailure;这个类中就包含了:对象,状态码和错误信息;
不管失败和成功都要进ResponseComplete;这个类中就包含了:对象,状态码、内容和错误信息;当然,它的参数会因为状态不同而出现null。
至于抽象类中的RequestAction 则是接下来要讲的。
6,发出请求的枚举
/** * 发出请求的枚举 */ public enum RequestAction { /** * 枚举中实例化一个请求对象,并传入请求的url,如果该请求有返回值则还需要传入解析参照对象 */ GET_INDEX_CONTENT(new RequestContent(RequestUrls.GET_INDEX_CONTENT_URL, String.class)),; /** * 构造器中传入请求对象 */ public RequestContent requestContent; RequestAction(RequestContent requestContent) { this.requestContent = requestContent; } }
通过带有构造器的枚举在每一个枚举中都实例化一个请求内容(RequestContent )对象。
这样,当我们调用每一个枚举的时候都会得到一个RequestContent的对象,于此同时也就得到了RequestContent对象中的请求参数map集合实例(getParameters())。
有了请求参数实例我们就可以不断的往里面添加参数。这样做的目的是把填写url,给json数据一个解析参照对象(String.class)以及实例化一个参数集合的工作都交给枚举来完成。
这样,我们只需要调用枚举,传入url和数据解析参照对象的class就能够得到参数集合,就能够发出请求。
7,继承网络请求接口的具体实现
/** * 具体的请求方法 */ public class Requester implements IRequest { //因为这里继承了接口所以不能通过枚举来实现单例了 private static volatile IRequest instance = null; public synchronized static IRequest getInstance() { if (instance == null) { synchronized (IRequest.class) { if (instance == null) { instance = new Requester(); } } } return instance; } private EventBus eventBus; private Gson gson; private Requester() { //eventbus是一个三方工具库,可以代替观察者模式发送网络请求返回结果。如果不会用这个工具库,可以百度,或者自己换成可以处理返回结果的代码;例如接口,例如观察者模式 eventBus = EventBus.getDefault(); //gson是一个三方json数据解析工具库。 gson = new Gson(); } //解析成功的参数 private ResponseResult createResponseSuccess(int statusCode, String response, RequestAction requestAction) { ResponseSuccess responseSuccess = null; try { JSONObject jsonObject = new JSONObject(response); responseSuccess = new ResponseSuccess(); responseSuccess.setStatusCode(statusCode); responseSuccess.setRequestAction(requestAction); if (requestAction.getClass() == null) return responseSuccess; if (jsonObject.has(TAG_OBJECT)) {//如果json数据中包含object,说明是一个对象 Object object = gson.fromJson(jsonObject.getString(TAG_OBJECT), requestAction.getClass());//这里用到的是gson的解析数据方法 responseSuccess.setContent(object); } else if (jsonObject.has(TAG_OBJECTS)) {//如果json数据中包含objects,说明是一个对象集合 JSONArray array = jsonObject.getJSONArray(TAG_OBJECTS); List<Object> data = new ArrayList<>(); for (int i = 0; i < array.length(); i++) { data.add(gson.fromJson(array.getString(i), requestAction.getClass())); } responseSuccess.setContent(data); } } catch (Exception e) { e.printStackTrace(); } return responseSuccess; } //解析失败的参数 private ResponseResult createResponseFailure(int statusCode, String message, RequestAction requestAction) { ResponseFailure responseFailure = new ResponseFailure(); responseFailure.setStatusCode(statusCode); responseFailure.setMessage(message); responseFailure.setRequestAction(requestAction); return responseFailure; } //不管成功还是失败都进该方法进行解析 private ResponseResult createResponseComplete(int statusCode, String responseString, RequestAction requestAction) { ResponseComplete responseComplete = new ResponseComplete(); responseComplete.setStatusCode(statusCode); responseComplete.setMessage(responseString); responseComplete.setRequestAction(requestAction); return responseComplete; } /** * post请求 */ @Override public void sendPostRequest(final RequestAction action) { if (!NetWorkUtil.isNetworkConnected(UIAppliaction.getInstance())) return; if (action.requestContent.getParameters() == null) { HttpClient.get(action.requestContent.getRequestUrl(), httpBackState(action)); } else { HttpClient.post(action.requestContent.getRequestUrl(), action.requestContent.getParameters(), httpBackState(action)); } } //服务器返回结果处理 private void httpBackState(final RequestAction action) { //该方法是网络请求框架中的回调方法,这里只是模拟代码 new HttpBack() { @Override public void onSuccess(int statusCode, byte[] responseBody) { try { String jsonSuccess = new String(responseBody, "UTF-8"); ResponseResult success = createResponseSuccess(statusCode, jsonSuccess, action); ResponseResult complete = createResponseComplete(statusCode, null, action); //给发送了网络请求的类返回请求结果 eventBus.post(success); eventBus.post(complete); } catch (Exception e) { e.printStackTrace(); } } @Override public void onFailure(int statusCode, byte[] responseBody, String error) { try { ResponseResult failure = createResponseFailure(statusCode, error, action); ResponseResult complete = createResponseComplete(statusCode, error, action); //给发送了网络请求的类返回请求结果 eventBus.post(failure); eventBus.post(complete); } catch (Exception e) { e.printStackTrace(); } } }; } /** * get请求 */ @Override public void sendGetRequest(final RequestAction action) { sendPostRequest(action); } /** * 取消所有请求 * * @param mayInterruptIfRunning */ @Override public void cancelAllRequests(boolean mayInterruptIfRunning) { HttpClient.cancelAllRequests(mayInterruptIfRunning); } /** * 设置超时时间 * * @param value 毫秒 */ @Override public void setTimeOut(int value) { HttpClient.setOutTime(value); } /** * 下载文件 * * @param url */ @Override public void downloadFile(String url) { //模拟下载文件 HttpClient.downloadFile(url, new HttpFileBack() { @Override public void onStart(int statusCode, byte[] responseBody) { } @Override public void onOngoing(int statusCode, byte[] responseBody, double schedule) { } @Override public void onError(int statusCode, byte[] responseBody, String message) { } }); } }
其中HttpClient是模拟代码。它指你使用的一切网络请求框架。
因为框架的设计无法用具体代码表达出来,所以这里我选择使用模拟逻辑处理过程。
到这里,整个框架的设计就完成了。那么我们应该怎么去使用呢?
使用网络请求:
//使用方法 public void test() { //实例化请求方法 IRequest request = Requester.getInstance(); //post请求 //通过枚举获得请求参数的map实例 Map<String, Object> params = RequestAction.GET_INDEX_CONTENT.requestContent.getParameters(); //添加参数 params.put("userinfo.name", "admin"); params.put("userinfo.pwd", "admins1"); //发送网络请求 request.sendPostRequest(RequestAction.GET_INDEX_CONTENT); //get请求 request.sendGetRequest(RequestAction.GET_INDEX_CONTENT); }
不管你在哪里提交网络请求,都只需要这么几行代码就搞定。
那么我们再来看如何添加新的rul:
首先在RequestUrls类中添加一个url:
public static final String GET_USER_CONTENT_URL = ROOT_URL + "index/selectIndexContent.action";
然后在RequestAction枚举中添加一个新枚举:
GET_USER_CONTENT(new RequestContent(RequestUrls.GET_USER_CONTENT_URL, UserInfoBean.class)),
这样,添加一个新的请求就完成了。
不管是提交请求,还是添加新请求都是几行代码就解决的问题。
那么我们如何去接收返回结果呢?
不管你是用的是eventbus.jar还是接口还是观察者模式得到返回结果后都可以这样处理:
//成功 public void onRequestSuccess(ResponseSuccess success) { switch (success.getRequestAction()) { case GET_INDEX_CONTENT: success.getContent();//内容 success.getStatusCode();//返回码 success.getRequestAction();//请求枚举 success.getClass();//参照的解析类型 break; } } //失败 public void onRequestFailure(ResponseFailure failure) { switch (failure.getRequestAction()) { case GET_INDEX_CONTENT: failure.getMessage();//错误信息 failure.getStatusCode();//返回码 failure.getRequestAction();//请求枚举 failure.getClass();//参照的解析类型 break; } } //所有 public void onRequestCompleted(ResponseComplete complete) { switch (complete.getRequestAction()) { case GET_INDEX_CONTENT: //注意:解析的内容可能为null,因为成功或者失败都会进来 complete.getContent();//内容,可能为null complete.getMessage();//错误信息,可能为null complete.getStatusCode();//返回码 complete.getRequestAction();//请求枚举 complete.getClass();//参照的解析类型 break; } }
拿到结果后就可以自己处理一些逻辑了。
整个二次封装的流程就是这样。
这个二次封装的框架目的在于简化请求接收代码,并且留下接口(onSuccess,onFailure)以供第三方框架使用。
现在再返回来看这张图:
我们二次封装网络请求的目的就是做红色的框所做的工作。并且把整个网络请求流程分开来,解耦。
就像几个楔形:服务器<第三方框架<二次封装框架<数据请求;
这样才能构成一个完整的app网络请求流程。而且这样分为几个楔形,成功的达到了解耦的作用。
这样,不管你换哪个第三方请求框架,只要把第三方请求框架的onSuccess()和onFailure()方法给接上去就可以照常使用。
这也是二次封装网络请求而设计框架的最重要的目的。
这里写成博客以供大家前来学习。
相关文章推荐
- Swift学习笔记(3)iOS 9 中的网络请求
- Linux网络编程--进程间通信(一)
- tcpdump 结合wireshark
- LoadImage一个从网络访问图片,并存到缓存(内存,磁盘)直接可以用
- HttpURLConnection发送Get和Post请求
- java HttpClient 获取页面Cookie信息
- [转]TCP 的那些事儿(下)
- [转]TCP 的那些事儿(上)
- HTTP发送POST请求说明
- [译文]用神经网络实现能够自主避让障碍的生物
- 递归神经网络不可思议的有效性
- TCP/IP详解,卷1:协议 (Week 1)
- UNIX网络编程:共享内存区
- ios开发——代码实现获得手机系统的名字和版本号&当前网络状态
- Overlay网络与物理网络的关系
- https、SSL与数字证书介绍
- uploadify上传文件出现http302错误
- TCP 与 UDP的区别
- [原创]java WEB学习笔记07:关于HTTP协议
- 【HDU4859】 海岸线(网络流-最小割)