最近做一個項目後端使用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;
}
}
五:運作效果:
如果,您認為閱讀這篇部落格讓您有些收獲,不妨點選一下右下角的【推薦】按鈕。 因為,我的寫作熱情也離不開您的肯定支援。
感謝您的閱讀
最近有個妹子弄的一個關于擴大眼界跟内含的訂閱号,每天都會更新一些深度内容,在這裡如果你感興趣也可以關注一下(嘿對美女跟知識感興趣),當然可以關注後輸入:github 會有我的微信号,如果有問題你也可以在那找到我;當然不感興趣無視此資訊;