Android聊天软件的开发(三)--网络连接

本文介绍了Android聊天软件开发中的网络连接部分,包括服务器网络接口的实现,Servlet使用HttpClient进行网络请求,以及客户端如何进行网络连接。重点讲解了服务器端获取和校验验证码的流程,涉及天翼开放平台的接口使用,以及HTTPS和HTTP协议的应用。
摘要由CSDN通过智能技术生成

一,服务器网络接口

   服务器网络接口通过Servlet实现,可以获得客户端提交的数据,对数据进行查询存储操作,以及返回结果数据给客户端。客户端可以通过HTTP协议直接访问网络接口。
   HTTP协议有两种请求方法:GET请求和POST请求。这两者的主要区别在于GET请求通过URL提交数据,数据在URL中可以看到,对于敏感数据来说,是不安全的。而且GET方式提交的数据最多只能有1024字节。对于POST请求,数据放置在HTML HEADER内提交,提交数据没有大小限制。因此选择POST请求方式比较合适。

   Servlet接口访问的URL格式为:http://[服务器IP]:[服务器端口号(默认8080)]/[项目名称]/[Servlet名称]。其内部可以针对GET请求和POST请求作出不同的处理方式:doGet函数处理GET请求,doPost函数处理POST请求。所以只需要实现doPost函数。当然,为了方便通过浏览器测试网络接口(在地址栏输入URL,后面加上参数,“http:\\...?a=123&b=456”),可以在doGet函数中调用已经实现的doPost函数。

   接下来主要分析注册接口,主要有三部分:获取验证码,校验验证码和注册基本信息。其中,获取和校验验证码的过程如下图(图1):

   1.获取验证码

   验证码通过天翼开放平台下发,需要在 天翼开放平台 进行申请,会赠送你20天,每天调用次数100次以下的套餐。自从6月1号收费后,又增加了一个限制,使用者需要配置10个一下的测试手机号,接口只对测试手机号开放。(感觉天翼越来越抠门了,之前是免费使用的,现在是收费,还限制使用。。。)

   申请步骤:
   a,注册登录,可以使用电信手机号,或者微博账号登录。
   b,创建应用,填写相关的应用信息。
   c,应用创建后,会得到app_id和app_secret。这是你使用接口的凭证。
   调用下发验证码接口的时序过程,如下图(此过程即为图1的1-4步骤)。

   1.客户端提交手机号码到下发短信验证码接口,发起下发验证码请求。

   2.服务器通过HTTPS协议,以app_id和app_secret为主要参数,访问天翼的令牌接口,获取access_token。

   3.服务器通过HTTP协议,以app_id和access_token为主要参数,访问天翼的信任码接口,获取token。

   4.服务器随机生成6位数字的验证码作为verify_code,并将verify_code和当前时间time(检查验证码是否超时,时效为2分钟)以用户ID(手机号)作为key,存储在session中。然后通过Http协议,以app_id,access_token,token,phone和verify_code为主要参数,访问天翼的下发验证码接口,实现验证码的下发操作。

    2.校验验证码
   如下图(此过程即为图1的5-7步骤),客户端接收到验证码短信之后,将phone(手机号),verify_code(验证码)和timestamp(时间)提交到校验验证码接口进行校验。首先根据phone获取服务器中code(验证码)和time(下发时间)的缓存。然后对比timestamp和time,如果超过两分钟,则返回“验证码超时”。否则,继续校验verify_code和code。如果不一致,则返回“验证码错误”。否则,返回“验证成功”。
    3.注册基本信息
   在验证码校验成功后,客户端将用户ID,用户昵称,性别和密码提交到服务器完成注册。
   
   关于服务器的网络接口,有一个需要注意的地方。服务器与客户端的通信参数,都是JSON格式的。Servlet中使用JSON,必须导入6个jar包:commons-beanutils.jar,commons-lang.jar, commons-collections.jar,commons-logging.jar,ezmorph.jar,json-lib.jar。
   如果出现Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/lang/exception/NestableRuntimeException异常,则可能是缺少jar包,或者commons-lang.jar的版本为3.1。

二,Servlet使用HttpClient

   由于下发验证码接口中需要访问其他网络接口,所以需要使用Apache的HttpClient。可以在  此处 下载相应的jar包,和查看使用实例。目前HttpClient最新的版本是4.3,与4.2版本有一定的区别,具体可以查看官网介绍(我这里使用的是4.3版本)。
   Servlet中使用HttpClient:
   1.导入httpclient.jar和httpcore.jar。
   2.创建可以访问HTTPS协议的HttpClient。这里只有令牌接口需要通过HTTPS访问,其他接口的访问可以直接通过HttpClients.createDefault()获得HttpClient。
/** 获得可用于访问Https的HttpClient */
private static CloseableHttpClient CreateSSLInsecureClient() {
	try {
		SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, 
			new TrustStrategy() {
			// 信任所有
			public boolean isTrusted(X509Certificate[] chain,String authType) 
				throws CertificateException {
				return true;
			}
		}).build();
		SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
		return HttpClients.custom().setSSLSocketFactory(sslsf).build();
	} catch (Exception e) {
		logger.error("Https配置出现异常:", e);
	}
	return HttpClients.createDefault();
}
   3.访问接口和得到返回数据(以令牌接口为例)
/**
 * 调用令牌接口,获取access_token<BR/>
 * <BR/>
 * 
 * HTTPS请求格式:POST <BR/>
 * 响应格式:JOSN
 * 
 * @return access_token 如果调用接口失败,则返回null
 */
private static String GetAccessToken() {
	String accessToken = null;
	CloseableHttpClient httpClient = CreateSSLInsecureClient();
	HttpPost httpPost = new HttpPost(Constants.GetVerifyCode.GET_ACCESS_TOKEN_URL);
	List<NameValuePair> params = new ArrayList<NameValuePair>();
	params.add(new BasicNameValuePair(
			Constants.GetVerifyCode.Params.GRANT_TYPE_KEY,
			Constants.GetVerifyCode.Params.GRANT_TYPE_VALUE));
	params.add(new BasicNameValuePair(Constants.GetVerifyCode.Params.APP_ID,
			Constants.GetVerifyCode.APP_ID));
	params.add(new BasicNameValuePair(
			Constants.GetVerifyCode.Params.APP_SECRET,
			Constants.GetVerifyCode.APP_SECRET));
	try {
		HttpEntity reqEntity = new UrlEncodedFormEntity(params);//自动编码特殊字符
		logger.debug("获取access_token的请求参数:"
				+ EntityUtils.toString(reqEntity));
		httpPost.setEntity(reqEntity);
		HttpResponse response = httpClient.execute(httpPost);
		HttpEntity resEntity = response.getEntity();

		if (resEntity != null) {
			String resStr = EntityUtils.toString(resEntity);
			JSONObject resJson = JSONObject.fromObject(resStr);
			int resCode = resJson.getInt(Constants.GetVerifyCode.Params.RES_CODE);
			if (resCode == 0)// 请求成功
			{
				accessToken = resJson.getString(Constants.GetVerifyCode.Params.ACCESS_TOKEN);
				logger.debug("获取access_token成功  access_token=" + accessToken);
			} else {
				String resMsg = resJson.getString(Constants.GetVerifyCode.Params.RES_MSG);
				logger.error("获取access_token失败  res_message=" + resMsg);
			}
			return accessToken;
		}
	} catch (Exception e) {
		logger.error("调用令牌接口失败:", e);
	} finally {
		httpPost.releaseConnection();
		try {
			httpClient.close();
		} catch (IOException e) {
			logger.error("调用令牌接口时关闭HttpClient异常:", e);
		}
	}
	return accessToken;
}
 

三,客户端网络连接

   Android的HttpClient是提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包,并且它支持 HTTP 协议最新的版本和建议。使用HttpClient来实现客户端与服务器的网络通信,可以简化代码和提高可靠性。
   客户端网络连接的主要架构如下图。由于各个功能模块向服务器发起网络请求的步骤都是一样的,所以将其封装为一个工具类。但是,因为需要访问不同的服务器接口,返回相应的结果,以及处理的方式都不同,所以需要让每个功能模块的Activity实现回调函数接口,用于处理返回数据。
   具体步骤如下:
   1.Activity实现HttpCallBackListener接口,以及根据自身需求实现callBack函数,用于处理服务器返回的数据。
   2.开启新的线程,整理参数,和通过HttpUtils的sendRequest方法发起网络请求。其中id为服务器的接口ID,data为RSA加密后的参数,this为当前实现了HttpCallBackListener接口的Activity。
   3.HttpUtils的sendRequest方法会根据接口ID,设置对应URL。并且向服务器发起访问。
   4.如果访问成功,则回调callBack函数。
   具体代码:
   修改密码的Activity
public class ModifyPsdActivity extends Activity implements HttpCallBackListener{
	private static final String TAG = ModifyPsdActivity.class.getSimpleName();
	
	private Handler mMultiHandler;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.layout_menu_modify_psd);
		mContext = this;
		mMultiHandler = ThreadUtils.GetMultiHandler(TAG);
		
		//发起修改密码的请求
		mMultiHandler.post(new Runnable() {
			@Override
			public void run() {
				JSONObject reqJson = new JSONObject();
				try {
					reqJson.put(Constants.UserInfo.RequestParams.USER_ID,CacheUtils.GetUserId());
					reqJson.put(Constants.UserInfo.RequestParams.OLD_PSD,oldPsdMd5);
					reqJson.put(Constants.UserInfo.RequestParams.PASSWORD,newPsdMd5);

					Log.d(TAG, "修改登录密码请求,加密内容:" + reqJson.toString());
					// RSA 加密
					String data = EncryptUtils.GetRsaEncrypt(reqJson.toString());
					HttpUtils.sendRequest(Constants.ID.MODIFY_PSD,data, ModifyPsdActivity.this);
				} catch (ConnectTimeoutException e) {
					Log.e(TAG, "网络连接超时", e);
					// 提示用户
				} catch (SocketTimeoutException e) {
					Log.e(TAG, "系统繁忙", e);
					// 提示用户
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	
	
	/**网络连接成功后的回调函数*/
	@Override
	public void httpCallBack(int id, JSONObject resp) {
		switch (id) {
		case Constants.ID.MODIFY_PSD:
			Log.d(TAG, "修改登录密码的回调函数");
			String resCode = resp.optString(Constants.ResponseParams.RES_CODE);
			if ("0".equals(resCode)) {
				Log.d(TAG, "修改用户信息成功");
				//提示用户
			} else {
				String resMsg = resp.optString(Constants.ResponseParams.RES_MSG);
				Log.e(TAG, "修改登录密码失败 :" + resMsg);

				//显示错误
			}
			break;
		}
	}
}


   HttpUtils工具类
public class HttpUtils {
	private static final String TAG = HttpUtils.class.getSimpleName();

	/**
	 * 发送请求
	 * 
	 * @param id
	 *            模块ID
	 * @param data
	 *            RSA加密后的数据
	 * @param listener
	 *            响应的回调函数
	 * @throws ConnectTimeoutException
	 * @throws SocketTimeoutException
	 * @throws Exception
	 */
	public static void sendRequest(int id, String data,
			HttpCallBackListener listener) throws ConnectTimeoutException,
			SocketTimeoutException, Exception {
		String url = null;
		//需要上传photo的接口,则将photo作为一个参数进行传递
		String photo = null;
		boolean isNeedPhoto = false;
		switch (id) {
		case Constants.ID.GET_CODE:
			url = Constants.Register.GET_VERIFY_CODE_URL;
			break;
		case Constants.ID.CHECK_CODE:
			url = Constants.Register.CHECK_VERIFY_CODE_URL;
			break;
		case Constants.ID.REGISTER:
			url = Constants.Register.REGISTER_URL;
			break;
		case Constants.ID.LOGIN:
			url = Constants.Login.LOGIN_URL;
			break;
		case Constants.ID.MODIFY_USERINFO:
			url = Constants.UserInfo.MODIFY_USERINFO_URL;
			isNeedPhoto = true;
			String[] strArr = data.split("&");
			if(strArr.length == 2)//有photo的数据
			{
				photo = strArr[1];
			}
			data = strArr[0];
			Log.d(TAG, "photo="+photo);
			break;
		case Constants.ID.MODIFY_PSD:
			url = Constants.UserInfo.MODIFY_PSD_URL;
			break;
		case Constants.ID.GET_FRIEND_LIST:
			url = Constants.GetFriendList.GET_FRIEND_LIST_URL;
			break;
		case Constants.ID.SEARCH_USER:
			url = Constants.AddFriend.SEARCH_USER_URL;
			break;
		case Constants.ID.REMOVE_FRIEND:
			url = Constants.RemoveFriend.REMOVE_FRIEND_URL;
			break;
		default:
			Log.e(TAG, "业务ID不存在");
			return;
		}
		Log.d(TAG, "url = " + url);

		// HttpPost连接对象
		HttpPost httpPost = new HttpPost(url);
		//使用UTF-8编码
		httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
		// 使用NameValuePair来保存要传递的Post参数
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		// 添加要传递的参数
		params.add(new BasicNameValuePair(Constants.RequestParams.DATA, data));
		if(isNeedPhoto)
		{
			params.add(new BasicNameValuePair(Constants.RequestParams.PHOTO, photo));
		}
		
		// 设置字符集
		HttpEntity httpEntity = new UrlEncodedFormEntity(params, HTTP.UTF_8);
		httpPost.setEntity(httpEntity);
		// 使用自定义的HttpClient
		HttpClient httpClient = CustomerHttpClient.getHttpClient();
		// 取得HttpResponse
		HttpResponse httpResponse = httpClient.execute(httpPost);
		// HttpStatus.SC_OK表示连接成功
		if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
			Log.d(TAG, "连接成功");
			// 取得返回的字符串
			String respStr = EntityUtils.toString(httpResponse.getEntity());
			JSONObject respJson = new JSONObject(respStr);
			//回调
			listener.httpCallBack(id, respJson);
		} else {
			Log.e(TAG, "连接失败");
		}
	}
}

   自定义HttpClient,设置基本参数
public class CustomerHttpClient {
	private static final String CHARSET = HTTP.UTF_8;
	private static HttpClient customerHttpClient;

	private CustomerHttpClient() {
	}

	public static synchronized HttpClient getHttpClient() {
		if (customerHttpClient == null) {
			HttpParams params = new BasicHttpParams();
			// 设置一些基本参数
			HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
			HttpProtocolParams.setContentCharset(params, CHARSET);
			HttpProtocolParams.setUseExpectContinue(params, true);
			HttpProtocolParams.setUserAgent(params,
				"Mozilla/5.0(Linux;U;Android 2.2.1;en-us;Nexus One Build.FRG83) " 
				+ "AppleWebKit/553.1(KHTML,like Gecko) Version/4.0 Mobile Safari/533.1");
			// 超时设置
			/* 从连接池中取连接的超时时间 */
			ConnManagerParams.setTimeout(params, 2000);
			/* 连接超时 */
			HttpConnectionParams.setConnectionTimeout(params, 20000);
			/* 请求超时 : 获取服务器响应超时*/
			HttpConnectionParams.setSoTimeout(params, 20000);

			// 设置我们的HttpClient支持HTTP和HTTPS两种模式
			SchemeRegistry schReg = new SchemeRegistry();
			schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
			schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));

			// 使用线程安全的连接管理来创建HttpClient
			ClientConnectionManager conMgr = new ThreadSafeClientConnManager(params, schReg);
			customerHttpClient = new DefaultHttpClient(conMgr, params);
		}
		return customerHttpClient;
	}
}

 

   

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值