使用WCF的一些问题
一个扫描出库程序,采用单实例、单线程模型,主要防止同个号多台电脑同时扫描出库(因为支持手动批量出库)
View Code
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.ComponentModel; 6 using System.ServiceModel; 7 using System.Threading; 8 using DotNetHalfGoodsHouse.EFModel; 9 using System.Data.SqlClient; 10 namespace DotNetHalfGoodsHouse.Services 11 { 12 #region 接口定义 13 [ServiceContract] 14 public interface IOutScanService 15 { 16 [OperationContract] 17 OutScanResponse PostScan(OutScanRequest request); 18 [OperationContract] 19 string GetMaxOrderId(); 20 21 } 22 #endregion 23 24 #region 服务实现 25 [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)] 26 public class OutScanService : IOutScanService, IDisposable 27 { 28 29 30 public OutScanResponse PostScan(OutScanRequest request) 31 { 32 33 Console.WriteLine(string.Format("PN:{0},被调用了!:ThreadId:{1}", request.PN, Thread.CurrentThread.ManagedThreadId)); 34 35 var response = new OutScanResponse() 36 { 37 Code=2, 38 ErrMsg="错误", 39 Amount=0 40 }; 41 42 try 43 { 44 #region 扫描出库 45 using (JL_MFGEntities dbx = new JL_MFGEntities(ModelSetting.ModelConnString)) 46 { 47 var rmBreakEnt = dbx.RMBreak.FirstOrDefault(ent => ent.PFIFOID == request.PN); 48 49 //记录是否存在 50 if (rmBreakEnt == null) throw new ScanException("该PN不存在!", 401); 51 52 //PN相关信息 53 response.Batno = rmBreakEnt.batno.ToString().Trim(); 54 response.CarType = rmBreakEnt.p_yw.Trim(); 55 response.Output = rmBreakEnt.Poutput.Value; 56 response.PlanId = rmBreakEnt.PlanID; 57 response.MPartNo = rmBreakEnt.partno.Trim(); 58 response.MPartName = rmBreakEnt.p_partnoName.Trim(); 59 response.Amount = rmBreakEnt.partnototalqty.Value; 60 61 //检测状态 62 if (rmBreakEnt.IsOut.Trim() != "否") throw new ScanException("已发状态为'是',该记录已经被扫描出库!", 402); 63 //检测发料记录 64 var findItem = dbx.ExecuteStoreQuery<int?>("Select count(*) from storage_fixbin Where fifoid=@PN", new SqlParameter("@PN", request.PN)).FirstOrDefault(); 65 if (findItem.HasValue && findItem.Value > 0) throw new ScanException("已存在发料记录,该记录已经被其他用户扫描",403); 66 67 //更改发料状态 68 rmBreakEnt.IsOut = "是"; 69 rmBreakEnt.OutDate = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"); 70 rmBreakEnt.OutOperator = request.Username; 71 72 //添加发料记录 73 var fixbin = new Storage_Fixbin() 74 { 75 #region 76 PlanID = rmBreakEnt.PlanID, 77 FGpartno = rmBreakEnt.FGpartno, 78 p_yw = rmBreakEnt.p_yw, 79 p_FGName = rmBreakEnt.p_FGName, 80 Batno = rmBreakEnt.batno, 81 Poutput = rmBreakEnt.Poutput, 82 FIFOID = rmBreakEnt.PFIFOID, 83 Barcode = request.EmpCard, 84 partno = rmBreakEnt.partno, 85 partname = rmBreakEnt.p_partnoName, 86 supplier = rmBreakEnt.p_supplier, 87 unit = rmBreakEnt.UM, 88 OutticketID = request.OrderId,//出库单号 89 OutSubID = request.OrderItemId,//子单号 90 OutQty = rmBreakEnt.partnototalqty, 91 OutDate = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss"), 92 OutOperator = request.Username, 93 Shift = request.EmpGroup, 94 ShiftMonitor = request.EmpName, 95 TicketStatus = "生产性领料", 96 Ptype = "发料", 97 Machine = request.Machine, 98 Reason = "正常发送" 99 #endregion 100 101 }; 102 dbx.Storage_Fixbin.AddObject(fixbin); 103 //更新预定量 104 var rmReserveEnt = dbx.RMHoldQty.FirstOrDefault(ent => ent.partno == fixbin.partno); 105 if (rmReserveEnt != null) 106 { 107 108 rmReserveEnt.holdqty -= rmBreakEnt.partnototalqty.Value; 109 } 110 111 #region 处理端子预定量 112 if (rmBreakEnt.Stock.Trim() == "端子") 113 { 114 var rmHandTEnt = dbx.HandTerminal.FirstOrDefault(ent => ent.partno == rmBreakEnt.partno && ent.Barcode == request.EmpCard); 115 if (rmHandTEnt == null) //执行添加 116 { 117 rmHandTEnt = new HandTerminal() 118 { 119 Barcode = request.EmpCard, 120 Name = request.EmpName, 121 Machine = request.Machine, 122 partno = rmBreakEnt.partno, 123 partname = rmBreakEnt.p_partnoName, 124 p_brand = rmBreakEnt.p_brand, 125 supplier = rmBreakEnt.p_supplier, 126 unit = rmBreakEnt.UM, 127 InQty = rmBreakEnt.partnototalqty, 128 Updatedate = DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss") 129 130 131 }; 132 dbx.HandTerminal.AddObject(rmHandTEnt); 133 } 134 else //执行更新 135 { 136 if (!rmHandTEnt.InQty.HasValue) rmHandTEnt.InQty = 0; 137 rmHandTEnt.InQty += rmBreakEnt.partnototalqty; 138 139 } 140 } 141 142 #endregion 143 144 dbx.SaveChanges(); 145 146 } 147 #endregion 148 response.Msg = "成功"; 149 response.ErrMsg = ""; 150 response.Code = 1; 151 } 152 catch (ScanException ex) 153 { 154 155 156 response.ErrMsg = ex.Message; 157 response.Code = ex.Code; 158 } 159 catch (Exception ex) 160 { 161 response.Code = 400; 162 163 response.ErrMsg = ex.Message; 164 if (ex.InnerException != null) 165 response.ErrMsg += string.Format("\r\n========Inner Exception=========\r\n{0}", ex.InnerException.Message); 166 } 167 168 169 return response; 170 } 171 172 173 public void Dispose() 174 { 175 176 } 177 178 179 public string GetMaxOrderId() 180 { 181 using (JL_MFGEntities dbx = new JL_MFGEntities(ModelSetting.ModelConnString)) 182 { 183 var list = dbx.ExecuteStoreQuery<string>("Select Max(OutTicketiD) from Storage_Fixbin").ToList(); 184 if (list.Count > 0) 185 { 186 var orderId = list[0].Trim(); 187 var newId=long.Parse(orderId) +1; 188 orderId="0000000000" + newId; 189 return orderId.Substring(orderId.Length - 10, 10); 190 191 } 192 193 } 194 return "0000000001"; 195 } 196 } 197 #endregion 198 199 #region 请求结构 200 201 public class OutScanRequest 202 { 203 public string EmpName { get; set; } 204 public string EmpCard { get; set; } 205 public string EmpGroup { get; set; } 206 public string Username { get; set; } 207 public string PN { get; set; } 208 public string Machine { get; set; } 209 public string OrderId { get; set; } 210 public int OrderItemId { get; set; } 211 } 212 public class OutScanResponse 213 { 214 /// <summary> 215 /// 1成功,2失败 216 /// </summary> 217 public int Code { get; set; } 218 public string ErrMsg { get; set; } 219 public string Msg { get; set; } 220 public string PlanId { get; set; } 221 public string CarType { get; set; } 222 public decimal Output { get; set; } 223 public string Batno { get; set; } 224 public string MPartNo { get; set; } 225 public string MPartName { get; set; } 226 public decimal Amount { get; set; } 227 } 228 [Serializable] 229 public class ScanException : Exception 230 { 231 public int Code { get; set; } 232 public ScanException(string msg,int code) 233 : base(msg) 234 { 235 this.Code = code; 236 } 237 } 238 #endregion 239 }
WCF采用TCP方式,并且多个请求使用同个TCP连接(在完成调用请求后不使用proxy.close()关闭TCP链接)
View Code
private IOutScanService _proxy; private IOutScanService ScanService { get { if (_proxy == null) { TryOpenChannel(); } else { var cObj = _proxy as ICommunicationObject; if (cObj.State == CommunicationState.Faulted || cObj.State == CommunicationState.Closed || cObj.State == CommunicationState.Closing ) { TryOpenChannel(); } } return _proxy; } } private void TryOpenChannel() { try { _proxy = WCFHelper.Factory.CreateChannel(); (_proxy as ICommunicationObject).Open(); } catch (Exception) { _proxy = null; throw; } }
在局域网使用不对绑定使用加密,需要注意的是该配置要在Server端与Client端同时设置。
View Code
#region WCF帮助类 public class WCFHelper { private static ChannelFactory<IOutScanService> _channelFac; public static ChannelFactory<IOutScanService> Factory { get { if (_channelFac == null) { _channelFac = new ChannelFactory<IOutScanService>(new NetTcpBinding(SecurityMode.None), EndpointStr); } return _channelFac; } } private static string EndpointStr { get { return DotNetHalfGoodsHouse.EFModel.ModelSetting.GetSetting("ServiceEndpoint", "net.tcp://192.168.1.31:8888/OutScanService"); } } }
采用匿名异步委托调用,防止界面卡死
View Code
private void txtPN_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { try { SetEnabled(false); Post(); } catch (Exception ex) { MessageBox.Show(ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { SetEnabled(true); txtPN.Text = ""; txtPN.Focus(); } } } #endregion private void SetEnabled(bool v) { if (v) { this.Cursor = Cursors.Arrow; } else { this.Cursor = Cursors.WaitCursor; } dgvScnList.Cursor = this.Cursor; txtPN.Enabled = v; btnOut.Enabled = v; cbxMaterialType.Enabled = v; cbxIncharge.Enabled = v; cbxEmpGroup.Enabled = v; } private OutScanRequest PreRequest(string pn) { var request = new OutScanRequest(); if (string.IsNullOrWhiteSpace(pn)) throw new DataInputException("请输入PN码!"); if (!pn.StartsWith("P")) { pn = "0000000000" + pn; pn = "P" + pn.Substring(pn.Length - 9); } request.PN = pn; request.EmpCard = txtEmpCard.Text.Trim(); if (string.IsNullOrWhiteSpace(request.EmpCard)) throw new DataInputException("请提员工身份识别卡!"); request.EmpGroup = cbxEmpGroup.Text.Trim(); request.EmpName = txtName.Text.Trim(); if (string.IsNullOrWhiteSpace(request.EmpName)) throw new DataInputException("请提供员工姓名!"); request.Machine = txtStation.Text.Trim(); request.Username = UserSetting.UserName.Trim(); return request; } private void Post() { #region string pn=txtPN.Text.Trim(); var request = PreRequest(pn); Post(request); #endregion } private void Post(OutScanRequest request) { //获取定单号 if (string.IsNullOrWhiteSpace(OrderId)) { GetOrderId(); } request.OrderId = OrderId; request.OrderItemId = OrderItemId; OrderItemId++; //异步处理 var fun = new Func<OutScanResponse>(() => { return ScanService.PostScan(request);}); var ar=fun.BeginInvoke(null,null); while (!ar.IsCompleted) { Application.DoEvents(); Thread.Sleep(50); } var response = fun.EndInvoke(ar); //不是同一批次 if (!string.IsNullOrWhiteSpace(response.PlanId) && string.Compare(response.PlanId.Trim(), PlanId, true) != 0) { PlanId = response.PlanId.Trim(); var action = new Action(() => { LoadData(); }); var loadDataAR = action.BeginInvoke(null, null); while (!loadDataAR.IsCompleted) { Application.DoEvents(); Thread.Sleep(50); } BindData(); } if (lsvOut.Items.Count >= 1000) { lsvOut.Items.RemoveAt(0); } if (response.Code == 1) { lsvOut.Items.Add(new ListViewItem(new string[] { request.PN, response.MPartNo, response.MPartName, response.Amount.ToString("0.00"), "成功", "成功发送!", DateTime.Now.ToString("yy-MM-dd HH:mm:ss") })); SuccessCount++; lblSuccesTips.Text = SuccessCount.ToString(); } else { var item = lsvOut.Items.Add(new ListViewItem(new string[] { request.PN, response.MPartNo, response.MPartName, response.Amount.ToString("0.00"), "失败", response.ErrMsg, DateTime.Now.ToString("yy-MM-dd HH:mm:ss") })); item.BackColor = Color.Red; item.Tag = request; ErrCount++; lblErrTips.Text = ErrCount.ToString(); } RemoveFromList(request.PN); lsvOut.EnsureVisible(lsvOut.Items.Count - 1); } private void GetOrderId() { var fun = new Func<string>(() => { return ScanService.GetMaxOrderId(); }); var ar = fun.BeginInvoke(null, null); while (!ar.IsCompleted) { Thread.Sleep(50); Application.DoEvents(); } OrderId = fun.EndInvoke(ar); OrderItemId = 1; } /// <summary> /// 将扫描过的记录从列表移除 /// </summary> /// <param name="pn"></param> private void RemoveFromList(string pn) { if (ScanData == null) return; var row = ScanData.Table.Select(string.Format(" pfifoid='{0}'", pn)).FirstOrDefault(); if (row != null) { ScanData.Table.Rows.Remove(row); } }
//==========发布配置部分============
1.使用TopShelf方式托管WCF,结果在XPsp3的系统上老是安装不了,不过放到win2003上就正常了
View Code
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using Topshelf; using System.IO; namespace OutScanServer { using DotNetHalfGoodsHouse; using DotNetHalfGoodsHouse.Services; class Program { private static string EndpointStr { get { return DotNetHalfGoodsHouse.EFModel.ModelSetting.GetSetting("ServiceEndpoint", "net.tcp://192.168.1.31:8888/OutScanService"); } } static void Main(string[] args) { var wsHost = HostFactory.New(x => { x.Service<ServiceHost>(s => { s.SetServiceName("配料扫描出库"); s.ConstructUsing(name=> new ServiceHost(typeof(OutScanService))); s.WhenStarted(tc => { tc.AddServiceEndpoint(typeof(IOutScanService), new NetTcpBinding(SecurityMode.None), EndpointStr); tc.Opened += (sObj, e) => { Console.WriteLine("服务启动!"); }; tc.Open(); }); s.WhenStopped(tc => { tc.Close(); }); }); x.RunAsLocalSystem(); x.StartAutomatically(); x.DependsOnMsSql(); x.SetDescription("配料扫描出库服务端!"); x.SetDisplayName("HaoDaOutScanService"); x.SetServiceName("HaoDaOutScanService"); }); wsHost.Run(); } } }
2.采用VS自带的Windows Service项目也不比TopShelf麻烦多少,而且XP上也可以装,就是安装时使用installuntil 注意要使用对应.net framework版本,不然会提示程序集找不到一类的错误。
View Code
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Linq; using System.ServiceProcess; using System.Text; using System.ServiceModel; namespace OutScanWinSvr { using DotNetHalfGoodsHouse; using DotNetHalfGoodsHouse.Services; public partial class OutScanWinSvr : ServiceBase { private ServiceHost _Host; public OutScanWinSvr() { InitializeComponent(); #region 初始华服务 _Host = new ServiceHost(typeof(OutScanService)); _Host.AddServiceEndpoint(typeof(IOutScanService), new NetTcpBinding(), DotNetHalfGoodsHouse.EFModel.ModelSetting.GetSetting("ServiceEndpoint","") ); #endregion } protected override void OnStart(string[] args) { _Host.Open(); } protected override void OnStop() { _Host.Close(); } } }
WCF客户端使用BasicHttpBinding时默认开启2条TCP,要开更多需要设置
System.Net.ServicePointManager.DefaultConnectionLimit = 512;