最近作一個項目後端使用WCF接收Android手機拍照並帶其它參數保存到服務器裏;恰好把最近學習的WCF利用上,本覺得是個比較簡單的功能應該很好實現,沒想到其中碰到很多問題,在網上搜索好久一直沒有想到的解決方案,最後實現對數據流的分段寫入而後後端再來解析流實現的此功能;後端運用WCF中的REST來接收數據;REST仍是比較簡單的知識,如果不懂能夠簡單網上了解一下;下面咱們先了解一些本次運用到的理論知識:html
一:理論知識web
因爲低層協議特性限制,WCF的流模式只支持以下四種:1:BasicHttpBinding 2:NetTcpBinding 3:NetNamedPipeBinding 4:WebHttpBindingjson
1.設置TransferMode。它支持四種模式(Buffered、Streamed、StreamedRequest、StreamedResponse),請根據具體狀況設置成三種Stream模式之一。後端
2.修改MaxReceivedMessageSize。該值默認大小爲64k,所以,當傳輸數據大於64k時,則拋出CommunicationException異常。 數組
3.修改receiveTimeout 和sendTimeout。大數據傳送時間較長,須要修改這兩個值,以避免傳輸超時。服務器
二:解決問題app
WCF若是使用Stream作爲參數時只能惟一一個,不能有其它另外的參數,這個也是本次碰到要重點解決的一個問題;但是咱們Android手機除的圖片還要有其它的參數,最後決定採用手機端把參數跟圖片都一塊兒寫入Stream裏面,後端WCF再來解析這個參數的流;oop
下面就是定義好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/SetGpsInfo"; }
代碼二:
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; } }
五:運行效果:
若是,您認爲閱讀這篇博客讓您有些收穫,不妨點擊一下右下角的【推薦】按鈕。 由於,個人寫做熱情也離不開您的確定支持。