[csharp] 這幾天一直研究Delphi連接.net的socket程序,終於有一些進展。 需求: 服務端截取前4個字節,轉換為數字,次數字為業務代碼。將決定調用哪個業務邏輯。 [html] using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Net.Sockets; using System.Threading; using PivasUpdate; using log4net; using System.Reflection; using System.Net; namespace PivasUpdate { public class ServiceProcess { private const int LENGTH=1024; private const int GETVERSION = 1; private const int DOWNLOADNEW = 2; private const int DOWNLOADFILE = 3; public static AutoResetEvent allDone = new AutoResetEvent(false); private static readonly ILog log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private string cliendAddres; private string INSTALLPATH = ConfigUtils.GetWindowsServiceInstallPath(ConfigUtils.SERVICENAME); public void ProcessCallback(IAsyncResult ar) { Socket handler = null; try { StateObject state = (StateObject)ar.AsyncState; handler = state.workSocket; cliendAddres = IPAddress.Parse(((IPEndPoint)handler.RemoteEndPoint).Address.ToString()) + ":" + ((IPEndPoint)handler.RemoteEndPoint).Port.ToString() + ":"; int bytesRead = handler.EndReceive(ar); log.Info(cliendAddres + "接受到數據包大小:" + bytesRead + "字節"); int control = BitConverter.ToInt32(state.buffer, 0); string c = UTF8Encoding.UTF8.GetString(state.buffer, 4, bytesRead - 4); switch (control) { case GETVERSION: log.Info(cliendAddres + "獲取最新版本,請求版本號:" + c); GetVersionNo(c, handler); break; case DOWNLOADNEW: log.Info(cliendAddres + "下載更新配置文件:" + INSTALLPATH+"/"+c); DownLoadNew(c, handler); break; case DOWNLOADFILE: log.Info(cliendAddres + "下載文件," + INSTALLPATH+"/"+c); DownLoadFile(INSTALLPATH + "/" + c, handler); break; default: break; } } catch (Exception e) { log.Error(cliendAddres + "程序出錯," + e.Message); } finally { if (handler != null) { handler.Shutdown(SocketShutdown.Both); handler.Close(); handler = null; log.Info("與客戶端:" + cliendAddres + "斷開連接。"); } } } public void DownLoadFile(string filePath,Socket handler) { if (!File.Exists(filePath)) { string fail = "文件不存在."; log.Info(cliendAddres+"訪問文件:"+filePath+"不存在。"); byte[] failBytes = Encoding.UTF8.GetBytes(fail); handler.BeginSend(failBytes, 0, failBytes.Length, 0, new AsyncCallback(SendCallBack), handler); allDone.WaitOne(); return; } handler.BeginSendFile(filePath, new AsyncCallback(delegate(IAsyncResult ar) { handler.EndSendFile(ar); allDone.Set(); log.Info(cliendAddres+"發送文件:"+filePath+"成功."); } ), handler); allDone.WaitOne(); } public void DownLoadNew(string versioncode,Socket handler) { string[] strs = versioncode.Split('.'); string result = ""; List<byte> resbyte = new List<byte>(); if (strs.Length != 3) { result = "版本號錯誤"; log.Warn(cliendAddres+"版本號錯誤," + versioncode); resbyte.AddRange(Encoding.UTF8.GetBytes(result)); handler.BeginSend(resbyte.ToArray(), 0, resbyte.Count, 0, new AsyncCallback(SendCallBack), handler); return; } FileStream stream = File.OpenRead(INSTALLPATH+"/version/" + versioncode + "/update.xml"); List<byte> list = new List<byte>(); int count = 0; byte[] buf = new byte[1024]; while ((count=stream.Read(buf,0,buf.Length))>0) list.AddRange(buf); handler.BeginSendFile(INSTALLPATH+"/version/" + versioncode + "/update.xml", new AsyncCallback(delegate(IAsyncResult ar) { handler.EndSendFile(ar); log.Info(cliendAddres+"發送文件成功,文件名update.xml"); allDone.Set(); } ), handler); allDone.WaitOne(); } public void GetVersionNo(string content,Socket handler) { string[] strs = content.Split('.'); string result = ""; List<byte> resbyte = new List<byte>(); if (strs.Length != 3) { result = "版本號錯誤"; log.Warn(cliendAddres+"版本號錯誤,"+content); resbyte.AddRange(Encoding.UTF8.GetBytes(result)); handler.BeginSend(resbyte.ToArray(), 0, resbyte.Count, 0, new AsyncCallback(SendCallBack), handler); return; } string[] dirs = Directory.GetDirectories(INSTALLPATH+"/version"); for (int i = 0; i < dirs.Length; i++) { if (dirs[i].EndsWith("\\"+content)) { if (i != dirs.Length - 1) { result = Directory.CreateDirectory(dirs[i + 1]).Name; break; } else { result = dirs[i]; } } } if (result == "") { resbyte.AddRange(Encoding.UTF8.GetBytes("沒有找到此版本。")); log.Warn(cliendAddres+"沒有找到版本:"+content); } else { resbyte.AddRange(Encoding.UTF8.GetBytes(result)); log.Info(cliendAddres+"發送新版本號:"+result); } handler.BeginSend(resbyte.ToArray(), 0, resbyte.Count, 0, new AsyncCallback(SendCallBack), handler); allDone.WaitOne(); } private void SendCallBack(IAsyncResult ar) { try { // Retrieve the socket from the state object. Socket handler = (Socket)ar.AsyncState; // Complete sending the data to the remote device. int bytesSent = handler.EndSend(ar); log.Info(cliendAddres+"發送成功,發送字節數:"+bytesSent); allDone.Set(); } catch (Exception e) { log.Error(cliendAddres+"發送失敗:"+e.Message,e); } } } } 以上代碼為服務端邏輯。 delphi使用TclientSocket連接服務端程序。 其中有幾大問題。 TclientSocket.sendBuf可以發送任意字符字節等數據,但是我們這塊的想法是前4個字節為數字,後面為自定義的字符串,因此這就需要將字節進行處理。 首先將數字轉換為4個字節數組,然後將字符串轉換為utf8編碼的字節數組然後進行組合,發送。意想不到的是程序發送成功後,沒有任何返回的結果。 然後在服務端進行代碼的跟蹤,發現服務端接受的字節數組順序已亂,轉換的數字已經出錯。因此不會返回任何的結果。 後來經過多方面原因的查找,發現在delphi中字節數組使用的是動態數組 array of byte ,delphi動態數組在經過socket緩沖區時,會出現數據混亂的現象,那如何解決呢? 經多面證實發現,使用靜態數組 array [1..5] of byte 可以發送成功,服務端也可以解碼成功。 但是如果數據是動態的,長度也不確定,那麼這種方式也是不適用的。所以程序還是需要改進的,可以這麼做所有的指令還有數據都是字符串,服務端首先解碼,轉換為字符串,然後在解析字符串的指令。 經過研究sendBuf的方法發現第一個參數需要的是一個指針類型,而我傳遞的是一個數組,按道理來說兩個的效果都是一樣的,都是數組的第一個元素的地址,但是情況明顯不是這樣的。直接將數組當作參數返回的數數組對象的地址,而將第一個數組元素作為參數傳遞,則會將第一個元素的地址傳遞。因此將參數換掉就ok。 下面看一下delphi使用靜態數組如何發送指令 [delphi] var arr2 : TBytes; arr1 : array [0..14] of byte; seendText : UTF8String; rectext : WideString; begin mmo.Lines.Add('發送指令'+inttostr(1)); with client do begin Open; //SetLength(arr1,4); arr1[0]:=1; seendText:= UTF8Encode('1.1302.1714'); //SetLength(arr1,Length(arr1)+Length(seendText)); Move(seendText[1],arr1[4],Length(seendText)); client.Socket.SendBuf(arr1,Length(arr1)); rectext:=UTF8Decode(client.Socket.ReceiveText); mmo.Lines.Add(rectext); Close; end; end;