最近做一个项目后端使用WCF接收Android手机拍照并带其它参数保存到服务器里;刚好把最近学习的WCF利用上,本以为是个比较简单的功能应该很好实现,没想到其中碰到不少问题,在网上搜索很久一直没有想到的解决方案,最后实现对数据流的分段写入然后后端再来解析流实现的此功能;后端运用WCF中的REST来接收数据;REST还是比较简单的知识,若是不懂可以简单网上了解一下;下面我们先了解一些本次运用到的理论知识:
一:理论知识
由于低层协议特性限制,WCF的流模式只支持如下四种:1:BasicHttpBinding 2:NetTcpBinding 3:NetNamedPipeBinding 4:WebHttpBinding
1.设置TransferMode。它支持四种模式(Buffered、Streamed、StreamedRequest、StreamedResponse),请根据具体情况设置成三种Stream模式之一。
2.修改MaxReceivedMessageSize。该值默认大小为64k,因此,当传输数据大于64k时,则抛出CommunicationException异常。
3.修改receiveTimeout 和sendTimeout。大数据传送时间较长,需要修改这两个值,以免传输超时。
二:解决问题
WCF如果使用Stream做为参数时只能唯一一个,不能有其它另外的参数,这个也是本次碰到要重点解决的一个问题;可是我们Android手机除的图片还要有其它的参数,最后决定采用手机端把参数跟图片都一起写入Stream里面,后端WCF再来解析这个参数的流;
下面就是定义好Stream的格式,传过来的Stream分成三部分: 参数信息长度 参数信息 图片
1 参数信息长度(1字节):用于存放参数信息的长度(以字节为单位);
2 参数信息: 除图片以外的参数,以JSON的形式存放如{"type":"jpg","EmployeeID":"12","TaskID":"13"}
3 图片:图片的字节
三:WCF编码内容
1:我们首先定义一个WCF契约,由于我们运用REST(在命名空间ServiceModel.Web下面)契约IAndroidInfo内容如下,采用POST方式进行接收:
using System.ServiceModel; using System.Runtime.Serialization; using System.ServiceModel.Web; using System.IO; namespace Coreius.CEIMS.AndroidInterface { [ServiceContract] public interface IAndroidInfo { [WebInvoke(UriTemplate = "GpsUpFile", Method = "POST", BodyStyle = WebMessageBodyStyle.Wrapped, RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json)] bool GpsUpFile(Stream ImageContext); } }
2:根据契约我们定义服务的内容,接收一个流的参数内容,首先把这个Stream转化成字节,然后根据我们先前约定好的内容获得第一个字节的值,再根据此值定义我们另外三个参数的字节长度,再通过JSON转换格式把它里面的三个参数值取出来,最后其它字节是存放一张手机拍的照片,把它存放在于们服务器D盘文件夹下
using System.Linq; using System.Text; using System.ServiceModel; using System.ServiceModel.Web; using System.IO; using Newtonsoft.Json; namespace Coreius.CEIMS.AndroidService { public class AndroidInfoService:IAndroidInfo { public bool GpsUpFile(Stream ImageContext) { byte[] m_Bytes = ReadToEnd(ImageContext); int len = (int)m_Bytes[0]; byte[] data = m_Bytes.Skip(1).Take(len).ToArray(); string Jsonstr = System.Text.Encoding.Default.GetString(data); JsonModel item = JsonConvert.DeserializeObject<JsonModel>(Jsonstr); string ImageType=item.type; string EmployeeID=item.EmployeeID; string TaskID=item.TaskID; byte[] Imagedata = m_Bytes.Skip(1 + len).ToArray(); string DiskName = "d:"; string FileAddress = "\\UpLoad\\"; string LocationAddress = DiskName + FileAddress; if (!DirFileHelper.IsExistDirectory(LocationAddress)) { DirFileHelper.CreateDirectory(LocationAddress); } string ImageName = DateTime.Now.ToString("yyyyMMddhhmmss.") + ImageType; string ImagePath = LocationAddress + ImageName; if (!File.Exists(ImagePath)) { try { System.IO.File.WriteAllBytes(ImagePath, Imagedata); ImageContext.Close(); return true; } catch { return false; } } else { return false; } } } }
上面的代码用到几个方法,比如把流转化成字节、把JSON转化成实现等,代码如下:
public byte[] ReadToEnd(System.IO.Stream stream) { long originalPosition = 0; if (stream.CanSeek) { originalPosition = stream.Position; stream.Position = 0; } try { byte[] readBuffer = new byte[4096]; int totalBytesRead = 0; int bytesRead; while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0) { totalBytesRead += bytesRead; if (totalBytesRead == readBuffer.Length) { int nextByte = stream.ReadByte(); if (nextByte != -1) { byte[] temp = new byte[readBuffer.Length * 2]; Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length); Buffer.SetByte(temp, totalBytesRead, (byte)nextByte); readBuffer = temp; totalBytesRead++; } } } byte[] buffer = readBuffer; if (readBuffer.Length != totalBytesRead) { buffer = new byte[totalBytesRead]; Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead); } return buffer; } finally { if (stream.CanSeek) { stream.Position = originalPosition; } } } public class JsonModel { public string type { get; set; } public string EmployeeID { get; set; } public string TaskID { get; set; } }
3:新建一个文本,然后修改其后缀名为.svc,作为我们发布服务(宿主为IIS)让Android手机调用, 然后把下面的代码写入
<%@ ServiceHost Language="C#" Debug="true" Service="Coreius.CEIMS.AndroidService.AndroidInfoService" %>
修改Web.config里面的内容:
<?xml version="1.0" encoding="utf-8"?> <configuration> <appSettings> <add key="ConnectionString" value="server=127.0.0.1;database=Coreius;uid=sa;pwd=admin"/> </appSettings> <system.web> <compilation debug="true" targetFramework="4.0" /> </system.web> <system.serviceModel> <behaviors> <endpointBehaviors> <behavior name="webHttp"> <webHttp helpEnabled="true"/> </behavior> </endpointBehaviors> <serviceBehaviors> <behavior name="MapConfigBehavior"> <!-- 为避免泄漏元数据信息,请在部署前将以下值设置为 false 并删除上面的元数据终结点 --> <serviceMetadata httpGetEnabled="true"/> <!-- 要接收故障异常详细信息以进行调试,请将以下值设置为 true。在部署前设置为 false 以避免泄漏异常信息 --> <serviceDebug includeExceptionDetailInFaults="true"/> <dataContractSerializer maxItemsInObjectGraph="2147483647"/> </behavior> </serviceBehaviors> </behaviors> <bindings> <webHttpBinding> <binding name="webHttpBindConfig" receiveTimeout="00:30:00" sendTimeout="00:30:00" maxReceivedMessageSize="104857600" transferMode="Streamed"> <readerQuotas maxStringContentLength="2147483647" maxArrayLength="2147483647"/> <security mode="None"></security> </binding> </webHttpBinding> </bindings> <services> <service name="Coreius.CEIMS.AndroidService.AndroidInfoService" behaviorConfiguration="MapConfigBehavior"> <endpoint binding="webHttpBinding" contract="Coreius.CEIMS.AndroidInterface.IAndroidInfo" bindingConfiguration="webHttpBindConfig" behaviorConfiguration="webHttp"/> </service> </services> </system.serviceModel> </configuration>
此处有些要注意的地方:
(1):此处采用的是webHttpBinding 所以一定要设置behaviorConfiguration才会有效果,其中helpEnabled="true"则是为实现可以在发布可以查看帮助信息
<behavior name="webHttp"> <webHttp helpEnabled="true"/> </behavior>
(2):为了实现上传大文件所以我们要如下设置最大值,其中security是设置访问服务的认证,此处是把它设置成为不认证,transferMode就是设置运用流的模式
<webHttpBinding> <binding name="webHttpBindConfig" receiveTimeout="00:30:00" sendTimeout="00:30:00" maxReceivedMessageSize="104857600" transferMode="Streamed"> <readerQuotas maxStringContentLength="2147483647" maxArrayLength="2147483647"/> <security mode="None"></security> </binding> </webHttpBinding>
4:编写完上面的代码后就可以服务器IIS上部署这个WCF服务:
四:Android编码
由于Android手机端的代码是另外一个朋友编写,所以就把大体的代码贴出来,大体的原理就是把参数跟图片写入流(图片进行压缩处理),然后调用部署好的WCF服务
代码一:因为服务器不是公用的,所以下面的IP我就随便修改的一个;
private
void
toUploadFile(File file) throws FileNotFoundException {
String result =
null
;
requestTime= 0;
int
res = 0;
long
requestTime = System.currentTimeMillis();
long
responseTime = 0;
//封装参数信息
JSONObject jsonObject =
new
JSONObject();
try
{
jsonObject.put(
"EmployeeID"
, MainActivity.guid);
jsonObject.put(
"TaskID"
,
"e52df9b4-ee3b-46c5-8387-329b76356641"
);
String[] type = file.getName().split(
"\\."
);
jsonObject.put(
"type"
, type[type.length-1]);
}
catch
(JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
/**上传文件*/
HttpParams httpParameters =
new
BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, 1000*30);
HttpConnectionParams.setSoTimeout(httpParameters, 1000*30);
HttpConnectionParams.setTcpNoDelay(httpParameters,
true
);
String path = PictureUtil.zipNewImage(file);
//压缩文件后返回的文件路径
byte
[] bytes =
null
;
InputStream
is
;
File myfile =
new
File(path);
try
{
is
=
new
FileInputStream(path);
bytes =
new
byte
[(
int
) myfile.length()];
int
len = 0;
int
curLen = 0;
while
((len =
is
.read(bytes)) != -1) {
curLen += len;
is
.read(bytes);
}
is
.close();
}
catch
(FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
catch
(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
byte
[] updata = GpsImagePackage.getPacket(jsonObject.toString(), bytes);
//参数与文件封装成单个数据包
HttpClient httpClient =
new
DefaultHttpClient(httpParameters);
HttpPost httpPost =
new
HttpPost(MyUrl.upload_file);
HttpResponse httpResponse;
//单个文件流上传
InputStream input =
new
ByteArrayInputStream( updata );
InputStreamEntity reqEntity;
reqEntity =
new
InputStreamEntity(input, -1);
reqEntity.setContentType(
"binary/octet-stream"
);
reqEntity.setChunked(
true
);
httpPost.setEntity(reqEntity);
try
{
httpResponse = httpClient.execute(httpPost);
responseTime = System.currentTimeMillis();
this
.requestTime = (
int
) ((responseTime-requestTime)/1000);
res = httpResponse.getStatusLine().getStatusCode();
if
(httpResponse.getStatusLine().getStatusCode() ==200) {
Log.e(TAG,
"request success"
);
Log.e(TAG,
"result : "
+ result);
return
;
}
else
{
Log.e(TAG,
"request error"
);
sendMessage(UPLOAD_SERVER_ERROR_CODE,
"上传失败:code="
+ res);
return
;
}
}
catch
(ClientProtocolException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
catch
(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
package com.anthony.util;
/**
* 服务器端接口
* @author YWJ
*
*/
public
class
MyUrl {
public
static
String upload_GPS = http:
//122.199.19.23:8088/AndroidInfoService.svc/GpsUpFile;
}
|
代码二:
package com.anthony.util;
public
class
GpsImagePackage {
public
GpsImagePackage() {
// TODO Auto-generated constructor stub
}
//封装字节数组与参数
public
static
byte
[] getPacket(String json,
byte
[] image){
byte
[] jsonb = json.getBytes();
int
length = image.length + jsonb.length;
System.
out
.println(image.length +
" "
+ jsonb.length);
byte
[] bytes =
new
byte
[length+1];
byte
[] lengthb = InttoByteArray(jsonb.length, 1);
System.arraycopy(lengthb, 0, bytes, 0, 1);
System.arraycopy(jsonb, 0, bytes, 1, jsonb.length);
System.arraycopy(image, 0, bytes, 1+jsonb.length, image.length);
return
bytes;
}
//将int转换为字节数组
public
static
byte
[] InttoByteArray(
int
iSource,
int
iArrayLen) {
byte
[] bLocalArr =
new
byte
[iArrayLen];
for
(
int
i = 0; (i < 4) && (i < iArrayLen); i++) {
bLocalArr[i] = (
byte
)( iSource>>8*i & 0xFF );
}
return
bLocalArr;
}
// 将byte数组bRefArr转为一个整数,字节数组的低位是整型的低字节位
public
static
int
BytestoInt(
byte
[] bRefArr) {
int
iOutcome = 0;
byte
bLoop;
for
(
int
i =0; i<bRefArr.length ; i++) {
bLoop = bRefArr[i];
iOutcome+= (bLoop & 0xFF) << (8 * i);
}
return
iOutcome;
}
}
|
五:运行效果: