j2me用socket模拟http做无线网络传输
程序感悟 2007-09-15 16:35:21 阅读157 评论0字号:大中小
博客注册了好久,不知道有什么可写的,都没动过。一个字也没有写,如果是块铁估计早生锈了。最近听到几个朋友跟我说要我写点东西放到我的这个小窝内。所以趁着今天不上班,免强写点吧(感觉是被逼的,哈哈~~~)。
其实这个是最近才做的。所以记忆深刻,以前没对HTTP协议做过深刻的了解,所以这三个类写了我两个星期。虽然时候是花得多了点,但写出来后还是感觉学到了不少。为了记念我的这第一份J2ME代码,我决定贴在这。有空的时候再来看看。也为我的博客添上第一件家俱。。。。。
首先贴出的是一个基类。从这个基类派生两个类。一个做HTTP连接。另一个做SOCKET模拟HTTP请求服务端数据。其中有我们部份公司的消息处理机制在里面。
package com.eno.kjava.net;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import javax.microedition.io.HttpConnection;
import javax.microedition.io.StreamConnection;
public class ENONetConnection {
protected String m_addr;
protected String OpenUrl;
protected String OnlineUrl;
protected int m_port;
protected boolean m_fKeepAlive;
private NetNotifier m_notifier;
private ByteArrayOutputStream m_baos = new ByteArrayOutputStream();
protected String m_strErrMsg = "";
protected int m_nContentLength = 0; // 当前需要要接收的数据长度
protected boolean m_fProxyMode;
private byte[] m_recvBuff = new byte[2048];
private int m_recvLen = 0;
protected StreamConnection m_conn = null;
protected DataInputStream m_is = null;
protected boolean isConnected = false;// 连接是否正常
protected DataOutputStream m_os = null;
protected static String CMCC_WAPADDR = "10.0.0.172:9201"; // 中国移动的WAP网关
protected static String CMCC_HTTPPROXY = "10.0.0.172:80"; // 中国移动的HTTP代理网关
protected static String CMCC_WAPPROXY = "10.0.0.172:8080"; // 中国移动的WAP代理网关
public ENONetConnection(String addr, int port) {
m_addr = addr;
m_port = port;
m_fKeepAlive = false;
m_fProxyMode = true;
}
public ENONetConnection(String addr, int port, boolean fKeepAlive) {
m_addr = addr;
m_port = port;
m_fKeepAlive = fKeepAlive;
m_fProxyMode = true;
}
public boolean sendRequest(String strURL, byte[] byData) {
return true;
}
public byte[] getResponse() {
return null;
}
/**
* 从url中取数据,若byData不为空,则采用post方式.
*
* @param strURL
* @param byData
* @return
*/
public byte[] getData(String strURL, byte[] byData) {
byte[] byRtn = null;
if (!isConnected()) {
if (strURL != null && !strURL.equals(""))
m_addr = m_addr + "/" + strURL;
connect();
}
if (isConnected()) {
if (sendRequest(strURL, byData)) {
byRtn = getResponse();
} else
setErrMsg("发送服务器失败!");
} else
setErrMsg("连接服务器失败!");
// 当返回错误或非keepAlive方式时,关掉连接.
if (byRtn == null || !isKeepAlive() || !m_strErrMsg.equals("")) {
setErrMsg(m_strErrMsg);
close();
}
return byRtn;
}
/***************************************************************************
* 单字节读取 DataInputStream 中的数据
*
* @return 字节数组
*/
protected byte[] readByteArrayFromStream() {
m_baos.reset();
try {
m_recvLen = 0;
while (true) {
// 有些机种不支持批量读取数据,使用单字节读取的方式来进行
int chr = m_is.read();
if (chr > -1) {
m_recvBuff[m_recvLen++] = (byte) chr;
if (m_recvLen == m_recvBuff.length) {
m_baos.write(m_recvBuff, 0, m_recvLen);
m_recvLen = 0;
}
} else {
if (m_recvLen > 0) {
m_baos.write(m_recvBuff, 0, m_recvLen);
m_recvLen = 0;
}
break;
}
}
} catch (Exception e) {
m_strErrMsg += "readByteArrayFromStream exception. ";
}
return m_baos.toByteArray();
}
/**
* 连接到m_addr,m_port.
*
* @return 成功与否标志?
*/
public boolean connect() {
return isConnected;
}
/**
* 关闭连接.
*
*/
public void close() {
m_fKeepAlive = false;
if (m_os != null) {
try {
m_os.close();
} catch (Exception allerr) {
}
m_os = null;
}
if (m_is != null) {
try {
m_is.close();
} catch (Exception allerr) {
}
m_is = null;
}
if (m_conn != null) {
try {
m_conn.close();
// System.out.println("关闭连接!!!");
} catch (Exception allerr) {
}
m_conn = null;
}
}
/**
* 判断连接是否正常?
*
* @return 是否正常?
*/
public boolean isConnected() {
return isConnected;
}
/**
* 当getData()返回空时,通过getErrMsg()取到错误信息.
*
* @return
*/
public String getErrMsg() {
return m_strErrMsg;
}
public void setErrMsg(String strErrMsg) {
m_strErrMsg = strErrMsg;
}
/**
* 设置采用http或cmwap网关代理方式
*
* @param fProxyMode
*/
public void setProxyMode(boolean fProxyMode) {
m_fProxyMode = fProxyMode;
}
public void ResetURL() {
// 进来的地址是 http|socket:// ip:prot [test/index.jsp?XXXXXX]
int nPos = m_addr.indexOf("://");
if (nPos == -1)
return;
else
nPos += 3;
int nLength = m_addr.indexOf('/', nPos);
if (nLength != -1 && nLength <= nPos)
return;
OpenUrl = CMCC_HTTPPROXY;
String ConType = m_addr.substring(0, nPos);
if (nLength != -1) {
if (ConType.equals("http://")) {
if (m_fProxyMode) { // 要OPEN url 和 X-ONLINE-HOST
OpenUrl = ConType + CMCC_HTTPPROXY
+ m_addr.substring(nLength);
} else { // 要OPEN url 和 HOST
OpenUrl = m_addr;
}
OnlineUrl = m_addr.substring(nPos, nLength);
} else if (ConType.equals("socket://")) {
if (m_fProxyMode) { // 要OPEN url 和 X-ONLINE-HOST
OpenUrl = ConType + CMCC_HTTPPROXY;
} else { // 要OPEN url 和 HOST
OpenUrl = ConType + m_addr.substring(nPos, nLength);
}
OnlineUrl = m_addr.substring(nPos, nLength);
}
} else // 没有后面的/
{
if (m_fProxyMode) { // 要OPEN url 和 X-ONLINE-HOST
OpenUrl = ConType + CMCC_HTTPPROXY;
} else { // 要OPEN url 和 HOST
OpenUrl = ConType + m_addr.substring(nPos);
}
// if(ConType.equals("http://")){
// OpenUrl += "/";
// }
OnlineUrl = m_addr.substring(nPos);
}
}
public boolean isKeepAlive() {
return m_fKeepAlive;
}
/***************************************************************************
* 清空当前连接地址
*
* @param rmUrl
*/
public void setUrl(String rmUrl) {
if (m_addr == null || rmUrl == null || m_addr.compareTo(rmUrl) != 0) {
this.close();
m_addr = rmUrl;
// System.out.println("setUrl方法内尝试关闭连接");
}
}
/**
* Add request properties for the configuration, profiles, and locale of
* this system.
*/
protected void setRequestHeaders(HttpConnection c) {
// String prof = System.getProperty("microedition.profiles");
// String conf = System.getProperty("microedition.configuration");
// String ua = "Profile/" + prof + " Configuration/" + conf;
// Let's impersonate an iMode phone
String ua = "ENO KJava Client";
try {
c.setRequestProperty("User-Agent", ua);
} catch (Exception e) {
}
String locale = System.getProperty("microedition.locale");
if (locale != null) {
try {
c.setRequestProperty("Content-Language", locale);
} catch (Exception e) {
}
}
}
/**
* 设置ENONETConection内的消息机制
* @param notifier
*/
public void setNotifier(NetNotifier notifier) {
m_notifier = notifier;
}
public NetNotifier getNotifier() {
return m_notifier;
}
protected void reportNetMsg(String s) {
if (m_notifier != null)
m_notifier.onNotify(s);
}
}
具体实现HTTP连接方式,这个是一般用得最多的实现方式。 不用多说了。
package com.eno.kjava.net;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
//import javax.microedition.io.SocketConnection;
import javax.microedition.io.StreamConnection;
//import com.eno.kjava.system.ENOSystem;
public class ENOHttpConn extends ENONetConnection {
public ENOHttpConn(String addr, int port) {
super(addr, port);
m_addr ="http://"+ addr;
if(m_port != -1){
m_addr = m_addr +":"+ port;
}
}
public ENOHttpConn(String addr, int port, boolean fKeepAlive) {
super(addr, port, fKeepAlive);
}
/**
* 连接到m_addr,m_port.
*
* @return 成功与否标志?
*/
public boolean connect() {
setUrl(m_addr);
if (m_conn == null) {
try {
ResetURL();
if(m_fProxyMode){
m_conn = (StreamConnection)Connector.open(OpenUrl,
Connector.READ_WRITE, true);
((HttpConnection)m_conn).setRequestProperty("X-Online-Host", OnlineUrl); //这个是代理连接的关键所在
}
else{
m_conn = (StreamConnection)Connector.open(OpenUrl,
Connector.READ_WRITE, true);
}
setRequestHeaders((HttpConnection)m_conn); // 其它的Http头
((HttpConnection)m_conn).setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
isConnected = true;
reportNetMsg("成功建立连接....");
} catch (Exception err) {
m_strErrMsg += "open exception. "+err.toString();
isConnected = false;
}
}
return isConnected;
}
/**
* 已经连接成功,发送请求.
*
* @param strURL
* @param byData
* @return
*/
public boolean sendRequest(String strURL, byte[] byData) {
boolean sendSuccess = true;
try {
if (byData != null){
((HttpConnection)m_conn).setRequestMethod(HttpConnection.POST);
}
if (m_os == null) {
m_os = m_conn.openDataOutputStream();
}
} catch (Exception e) {
m_strErrMsg += "openDataOutputStream exception. " + e.toString();
}
if (m_os == null)
sendSuccess = false;
try {
if (byData != null)
m_os.write(byData);
// m_os.flush();
reportNetMsg("请求发送完毕....");
} catch (Exception e) {
m_strErrMsg += "write exception. " + e.toString();
sendSuccess = false;
}
return sendSuccess;
}
/**
* 取服务端响应的数据.
*
* @return
*/
public byte[] getResponse() {
byte[] resultData = null;
// 打开 DataInputStream 对象,进行返回数据的读取
if (m_is == null) {
try {
m_is = m_conn.openDataInputStream();
} catch (Exception e) {
m_strErrMsg += "openDataInputStream exception. " + e.toString();
}
if (m_is == null)
return null;
}
m_nContentLength = 0;
// reportNetMsg("接收完成,开始解析数据包。");
// Only HTTP_OK (200) means the content is returned.
int respCode = 0;
try {
respCode = ((HttpConnection) m_conn).getResponseCode();
} catch (Exception e) {
m_strErrMsg += "getResponseCode exception. " + e.toString();
}
if (respCode == HttpConnection.HTTP_OK) {
m_nContentLength = (int) ((HttpConnection) m_conn).getLength();
if (m_nContentLength > 0) {
String strReadStream = null;
if (strReadStream == null) {
resultData = new byte[(int) m_nContentLength];
int actual = 0;
int bytesread = 0;
int maxPerRead = 256;
while ((bytesread < m_nContentLength) && (actual != -1)) {
if ((m_nContentLength - bytesread) > 256)
maxPerRead = 256;
else
maxPerRead = m_nContentLength - bytesread;
try {
actual = m_is.read(resultData, bytesread,
maxPerRead);
} catch (Exception e) {
m_strErrMsg += "read exception. " + e.toString();
break;
}
bytesread += actual;
}
} else {
resultData = readByteArrayFromStream();
}
} else {
resultData = readByteArrayFromStream();
}
}
return resultData;
}
}
下面这个是用socket模拟http发送请求,得到数据。发送的数据用HTTP头包裹出去,得到服务器返回的数据也是带有包头数据的数据包,然后对包进行解析。最后得到要的实际数据。这就是整个流程和思路。。
package com.eno.kjava.net;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
//import java.io.InputStreamReader;
import javax.microedition.io.Connector;
//import javax.microedition.io.HttpConnection;
import javax.microedition.io.SocketConnection;
//import javax.microedition.io.StreamConnection;
public class ENOHttpVSocketConn extends ENONetConnection {
private byte crlf13 = (byte) 13; // ' '
private byte crlf10 = (byte) 10; // ' '
// private StringBuffer socketHead = new StringBuffer(); // 用于保存包头内容
private ByteArrayOutputStream chunkedData = new ByteArrayOutputStream();
private boolean isEnd = true;// 读完头后,流是否还有数据可读
private boolean isChunked = false;
public ENOHttpVSocketConn(String addr, int port) {
super(addr, port);
m_addr = "socket://"+addr+":"+port;
m_fKeepAlive = true;
}
public ENOHttpVSocketConn(String addr, int port, boolean fKeepAlive) {
super(addr, port, fKeepAlive);
m_fKeepAlive = true;
}
/**
* 已经连接成功,发送请求.
*
* @param strURL
* @param byData
* @return
*/
public boolean sendRequest(String strURL, byte[] byData) {
boolean sendSuccess = true;
if (m_os == null) {
try {
m_os = m_conn.openDataOutputStream();
} catch (Exception e) {
m_strErrMsg += "openDataOutputStream exception. ";
}
if (m_os == null)
sendSuccess = false;
}
try {
// 设置请求头
String requestHead = "";
if (byData == null)
requestHead = "GET /";
else
requestHead = "POST /";
if (strURL != null)
requestHead += strURL + " HTTP/1.1 ";
else
requestHead += " HTTP/1.1 ";
// Host
if (m_fProxyMode) {
requestHead += "X-Online-Host: " + OnlineUrl
+ " ";
} else {
requestHead += "Host: " + OnlineUrl + " ";
}
// 加其它的请求头
requestHead += "User-Agent: ENO KJava Client ";
requestHead += "Content-Language: "
+ System.getProperty("microedition.locale") + " ";
requestHead += "Content-Type: application/x-www-form-urlencoded ";
if (byData != null) {
requestHead += "Content-Length:" + byData.length;
requestHead += " ";
}
// 结束Http Header
requestHead += " ";
// System.out.println("请求头:" + requestHead);
m_os.write(requestHead.getBytes());
if (byData != null)
m_os.write(byData);
// m_os.flush();
reportNetMsg("请求发送完毕....");
} catch (Exception e) {
m_strErrMsg += "write exception. " + e.toString();
sendSuccess = false;
}
return sendSuccess;
}
/**
* 连接到m_addr,m_port.
*
* @return 成功与否标志?
*/
public boolean connect() {
setUrl(m_addr);
if (m_conn == null) {
try {
ResetURL();
m_conn = (SocketConnection) Connector.open(OpenUrl,Connector.READ_WRITE, true);
((SocketConnection)m_conn).setSocketOption(SocketConnection.LINGER, 5);//关闭连接前等待的秒数
if(m_fKeepAlive)
((SocketConnection)m_conn).setSocketOption(SocketConnection.KEEPALIVE, 1);//非O为启动长连接属性
isConnected = true;
reportNetMsg("成功建立连接....");
} catch (Exception err) {
m_strErrMsg += "open exception. " + err.toString();
isConnected = false;
}
}
return isConnected;
}
/**
* 取服务端响应的数据.
*
* @return
*/
public byte[] getResponse() {
byte[] resultData = null;
if (m_is == null) {
try {
m_is = m_conn.openDataInputStream();
} catch (Exception e) {
m_strErrMsg += "openDataInputStream exception. "
+ e.toString();
}
if (m_is == null)
return null;
}
m_nContentLength = 0;
// 开始解析数据包
// reportNetMsg("接收完成,开始解析数据包。");
try {
this.ReadSocketHeader();// 读包头并为 m_nContentLength 赋值。
if (isEnd)
return null;
} catch (Exception e) {
m_strErrMsg += "ReadSocketHeader exception. " + e.getMessage();
return null;
}
if (m_nContentLength > 0) {
String strReadStream = null;
if (strReadStream == null) {
resultData = new byte[(int) m_nContentLength];
int actual = 0;
int bytesread = 0;
int maxPerRead = 256;
while ((bytesread < m_nContentLength) && (actual != -1)) {
if ((m_nContentLength - bytesread) > 256)
maxPerRead = 256;
else
maxPerRead = m_nContentLength - bytesread;
try {
actual = m_is.read(resultData, bytesread, maxPerRead);
} catch (Exception e) {
m_strErrMsg += "read exception. " + e.getMessage();
break;
}
bytesread += actual;
}
} else {
resultData = readByteArrayFromStream();
}
} else if (isChunked) {
resultData = getchunkedResponse();
} else {
resultData = readByteArrayFromStream();
}
return resultData;
}
/***************************************************************************
* 这个方法处理返回的是chunked的数据
*
* @return
*/
public byte[] getchunkedResponse() {
chunkedData.reset();
int chunkedSize = 0;
boolean readFrist = true;// 是不是第一次在包头后读chuck包
// 循环连续读取chunk包
do {
int chunkcrlf;
int chunkcrlfNum = 0; // 已经连接的回车换行数 crlfNum=4为头部结束
StringBuffer temp = new StringBuffer();
try {
// 从现在起,读chunk包的大小。
while ((chunkcrlf = m_is.read()) != -1) // 读取头部
{
if (chunkcrlf == crlf13 || chunkcrlf == crlf10) {
chunkcrlfNum++;
}
temp = temp.append((char) chunkcrlf); // byte数组相加
// 去掉HTTP包头后。整个通信chunk包结构是:chunk大小内容包大小内容、、、、、
if (readFrist == true && chunkcrlfNum == 2) {
break;
} else if (readFrist == false && chunkcrlfNum == 4) {
break;
}
}
} catch (IOException ioe) {
m_strErrMsg += "Read chunked Data Error!" + ioe.getMessage();
return null;
}
try {
// 本次包的大小为:(十六进制)
chunkedSize = Integer.parseInt(temp.toString().trim(), 16);
// 接着块读取本次包内容
if (chunkedSize != 0) {
int readed = 0; // 已经读取数
int available = 0;// input.available(); //可读数
while (readed < chunkedSize) {
available = chunkedSize - readed; // size-readed--剩余数
if (available > 256)
available = 256; // size-readed--剩余数
byte[] buffer = new byte[available];
int reading = m_is.read(buffer);
chunkedData.write(buffer, 0, reading);
readed += reading; // 已读字符
}
}
// 第一个chunk包读完
readFrist = false;
} catch (Exception e) {
m_strErrMsg += "Read chunked Data Error!" + e.getMessage();
return null;
}
} while (chunkedSize != 0);
// 返回读得的内容
return chunkedData.toByteArray();
}
/***************************************************************************
* 读取返回的 包头信息。得到包头内 Content-Length的长度 如果包头内没有 Content-Length 的信息。那么
* DataInputStream 也把头信息读完了。接下来就让readByteArrayFromStream方法读后面的数据
*/
private void ReadSocketHeader() // 读取头部 并获得大小
{
int crlf;
int crlfNum = 0; // 已经连接的回车换行数 crlfNum=4为头部结束
StringBuffer socketHead = new StringBuffer(); // 用于保存包头内容
try {
while ((crlf = m_is.read()) != -1) // 读取头部
{
if (crlf == crlf13 || crlf == crlf10) {
crlfNum++;
} else {
crlfNum = 0;
} // 不是则清
socketHead = socketHead.append((char) crlf); // byte数组相加
if (crlfNum == 4) {
isEnd = false;// 读完头后还有数据可读。
break;
}
}
} catch (IOException e) {
m_strErrMsg += "Read Http Header Error!" + e.getMessage();
return;
}
String tempStr = (new String(socketHead)).toUpperCase();
String resp = "200 OK";
int reIndex = tempStr.indexOf(resp);
if (reIndex == -1) {
isEnd = true;
return;
}
// 判断包是不是以chunked返回的。Transfer-Encoding: chunked
String chu = "TRANSFER-ENCODING: CHUNKED";
int chIndex = tempStr.indexOf(chu);
if (chIndex != -1) {
isChunked = true;
return;
}
String ss1 = "CONTENT-LENGTH:";
int clIndex = tempStr.indexOf(ss1);
try {
byte requst[] = tempStr.getBytes();
if (clIndex != -1) { // 从clIndex+1起至
StringBuffer sb = new StringBuffer();
for (int i = (clIndex + 16);; i++) {
if (requst[i] != (byte) 13 && requst[i] != (byte) 10) {
sb.append((char) requst[i]);
} else
break;
}
m_nContentLength = Integer.parseInt(sb.toString()); // 正式的HTML文件的大小
}
} catch (Exception e) {
m_strErrMsg += "得到content-length时发生异常!" + e.getMessage();
}
}
}
具体调用:
ENONetConnection conn = new ENOHttpConn("wap.guosen.cn",8001);
conn.setProxyMode(false);
byte[] byRtn = conn.getData(null,new String("1").getBytes());
// ENONetConnection conn = new ENOHttpVSocketConn("wap.guosen.cn",8001);
// conn.setProxyMode(false);
// byte[] byRtn = conn.getData("upload/soft/gettest.jsp?re_length=120",new String("").getBytes());