需求:
描述:实时在客户端上获取到哪些款需要补货.
要求: 后台需要使用c#,并且哪些需要补货的逻辑写在公司框架内,客户端采用PDA(即Android客户端 版本4.4) . 用户打开了补货通知页面时不需要在通知栏推送 如果不在时需要通知栏推送消息.
实现思路设计
思路图解释:
想法是启动服务时候创建TcpListener监听指定的Ip端口创建接收连接的线程和接收数据的回调由于 定义的存储方式(Dictionary<string, ClientSocketManager> Clients = new Dictionary<string, ClientSocketManager>();)所以采用了先由客户端建立连接然后服务端收到连接请求时立即发送一条数据给客户端要求客户端发送用户签名给服务端以便服务端可以根据用户Id之类的数据去发送给指定的用户 设置的数据格式为/*star|状态,user标识|end*/真实数据
客户端登录时候和服务端建立连接在任意界面都可以接收到通知打开通知时候进入展示数据的页面
服务端关键性代码
1 protected override void OnStart(string[] args) 2 { 3 if (IsListened) return; 4 //启动监听 5 IPAddress ip = IPAddress.Parse(ServiceSetting._IPAddress); 6 IPEndPoint point = new IPEndPoint(ip, ServiceSetting._IPEndPoint); 7 listener = new TcpListener(point); 8 try 9 { 10 listener.Start(100); 11 } 12 catch (Exception ex) 13 { 14 LogHelper.Log(ex); 15 return; 16 } 17 IsListened = true; 18 //接受连接请求的异步调用 19 AsyncCallback callback = new AsyncCallback(AcceptCallBack); 20 listener.BeginAcceptSocket(callback, listener); 21 LogHelper.Write("服务已启动……", LogLev.Info); 22 }
1 private void AcceptCallBack(IAsyncResult ar) 2 { 3 try 4 { 5 //完成异步接收连接请求的异步调用 6 Socket handle = listener.EndAcceptSocket(ar); 7 ClientSocketManager manager = new ClientSocketManager(handle); 8 9 AsyncCallback callback; 10 //继续调用异步方法接收连接请求 11 if (IsListened) 12 { 13 callback = new AsyncCallback(AcceptCallBack); 14 listener.BeginAcceptSocket(callback, listener); 15 } 16 //开始在连接上进行异步的数据接收 17 manager.ClearBuffer(); 18 callback = new AsyncCallback(ReceiveCallback); 19 manager.socket.BeginReceive(manager.Rcvbuffer, 0, manager.Rcvbuffer.Length, SocketFlags.None, callback, manager); 20 } 21 catch (Exception ex) 22 { 23 //在调用EndAcceptSocket方法时可能引发异常 24 //套接字Listener被关闭,则设置为未启动侦听状态 25 IsListened = false; 26 LogHelper.Log(ex); 27 return; 28 } 29 }
1 private void ReceiveCallback(IAsyncResult ar) 2 { 3 ClientSocketManager manager = (ClientSocketManager)ar.AsyncState; 4 try 5 { 6 int i = manager.socket.EndReceive(ar); 7 if (i == 0) 8 { 9 RemoveOneClientbyManager(manager); 10 return; 11 } 12 else 13 { 14 string data = Encoding.UTF8.GetString(manager.Rcvbuffer, 0, i); 15 //manager.socket.RemoteEndPoint.ToString() 获取客户端IP 16 manager.ClearBuffer(); 17 //根据传入数据处理用户数据 设置数据格式为/*star|状态,user标识|end*/真实数据 18 //匹配中间的状态正则表达式 ^(/[*]star|).*(?=|end[*]/) 19 //首次传入保存连接信息 20 //打开通知时接收到回调 21 string packgeHead = Regex.Match(data, @"^(/[*]star|).*(?=|end[*]/)").ToString().Replace("/*star|", ""); 22 if (string.IsNullOrEmpty(packgeHead)) 23 { 24 LogHelper.Write("客户端没有发送指定的数据头", LogLev.Warn); 25 return; 26 } 27 string[] heads = packgeHead.Split(','); 28 switch (heads[0]) 29 { 30 case "first":// 首次传入保存连接信息 31 lock (lockObj) 32 { 33 Clients.Add(heads[1], manager); 34 } 35 //ToRemove 36 SendToClient(new List<string>() { "aaa" }, "您有一条新的通知"); 37 //endRemove 38 break; 39 case "data"://用户打开了页面可以发送数据 40 ReceiveThenSend(manager); 41 break; 42 default: 43 LogHelper.Write("客户端发送的数据头有问题", LogLev.Warn); 44 break; 45 } 46 AsyncCallback callback = new AsyncCallback(ReceiveCallback); 47 manager.socket.BeginReceive(manager.Rcvbuffer, 0, manager.Rcvbuffer.Length, SocketFlags.None, callback, manager); 48 } 49 } 50 catch (Exception ex) 51 { 52 RemoveOneClientbyManager(manager); 53 LogHelper.Log(ex); 54 return; 55 } 56 }
1 private void SendData(ClientSocketManager manager, string data) 2 { 3 try 4 { 5 byte[] msg = Encoding.UTF8.GetBytes(data); 6 AsyncCallback callback = new AsyncCallback(new Action<IAsyncResult>(ar => 7 { 8 ClientSocketManager frd = (ClientSocketManager)ar.AsyncState; 9 try 10 { 11 frd.socket.EndSend(ar); 12 } 13 catch (Exception ex) 14 { 15 RemoveOneClientbyManager(manager); 16 LogHelper.Log(ex); 17 return; 18 } 19 })); 20 manager.socket.BeginSend(msg, 0, msg.Length, SocketFlags.None, callback, manager); 21 } 22 catch (Exception ex) 23 { 24 RemoveOneClientbyManager(manager); 25 LogHelper.Log(ex); 26 return; 27 } 28 }
客户端关键性代码
先授权
1 <uses-permission android:name="android.permission.INTERNET" /> 2 <uses-permission android:name="android.permission.GET_TASKS" />
在Application中插入代码 (ps:文件名由于开始打算建的是父级的Activity后来实验不行就没有更换名称)
1 package com.example.sockettestclient; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.io.OutputStream; 6 import java.io.UnsupportedEncodingException; 7 import java.net.Socket; 8 import java.net.UnknownHostException; 9 10 import android.app.ActivityManager; 11 import android.app.Application; 12 import android.app.NotificationManager; 13 import android.app.PendingIntent; 14 import android.content.Context; 15 import android.content.Intent; 16 import android.net.NetworkInfo.State; 17 import android.os.Bundle; 18 import android.os.Handler; 19 import android.os.Message; 20 import android.support.v4.app.NotificationCompat; 21 import android.widget.EditText; 22 import android.widget.TextView; 23 24 public class BaseActivity extends Application{ 25 @Override 26 public void onCreate(){ 27 super.onCreate(); 28 } 29 //NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); 30 //发送通知 31 protected void InitNotificationManager(String Title,String Content) { 32 NotificationCompat.Builder builder = new NotificationCompat.Builder(this); 33 Intent intent = new Intent(this, TestGetSocketRes.class);//将要跳转的界面 34 builder.setAutoCancel(true);//点击后消失 35 builder.setSmallIcon(R.drawable.info);//设置通知栏消息标题的头像 36 builder.setDefaults(NotificationCompat.DEFAULT_SOUND);//设置通知铃声 37 builder.setTicker(Title); 38 builder.setContentText(Content);//通知内容 39 builder.setContentTitle(Title); 40 //添加参数 41 Bundle bundle = new Bundle(); 42 bundle.putString("title", Title); 43 bundle.putString("Content", Content); 44 intent.putExtras(bundle); 45 //利用PendingIntent来包装我们的intent对象,使其延迟跳转 46 PendingIntent intentPend = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); 47 builder.setContentIntent(intentPend); 48 NotificationManager manager = (NotificationManager) this.getSystemService(this.NOTIFICATION_SERVICE); 49 manager.notify(0, builder.build()); 50 } 51 protected boolean isConnect = false; 52 protected static final String ServerIP = "192.168.47.102"; 53 protected static final int ServerPort = 4567; 54 protected Socket socket = null; 55 protected ReceiveThread receiveThread = null; 56 protected boolean isReceive = false; 57 protected Handler SocketUIHandler = null; 58 protected OutputStream outStream; 59 protected String strMessage; 60 protected TextView textReceive = null; 61 protected EditText textSend = null; 62 private boolean IsInPage=false; 63 private String UserID="aaa"; 64 public enum PostState { 65 first, data 66 } 67 public void SetUserInfo() { 68 UserID="aaa"; 69 } 70 public String GetSocketPostDataHeadInfo(PostState state) { 71 return "/*star|"+state.toString()+","+UserID+"|end*/"; 72 } 73 Runnable connectThread = new Runnable() { 74 75 @Override 76 public void run() { 77 // TODO Auto-generated method stub 78 try { 79 //初始化Scoket,连接到服务器 80 socket = new Socket(ServerIP, ServerPort); 81 isConnect = true; 82 //启动接收线程 83 isReceive = true; 84 receiveThread = new ReceiveThread(socket); 85 receiveThread.start(); 86 strMessage = GetSocketPostDataHeadInfo(PostState.first); 87 new Thread(sendThread).start(); 88 System.out.println("----connected success----"); 89 } catch (UnknownHostException e) { 90 // TODO Auto-generated catch block 91 e.printStackTrace(); 92 System.out.println("UnknownHostException-->" + e.toString()); 93 } catch (IOException e) { 94 // TODO Auto-generated catch block 95 e.printStackTrace(); 96 System.out.println("IOException" + e.toString()); 97 } 98 } 99 }; 100 //接收线程 101 protected class ReceiveThread extends Thread{ 102 private InputStream inStream = null; 103 104 private byte[] buffer; 105 private String str = null; 106 107 ReceiveThread(Socket socket){ 108 try { 109 inStream = socket.getInputStream(); 110 } catch (IOException e) { 111 // TODO Auto-generated catch block 112 e.printStackTrace(); 113 } 114 } 115 @Override 116 public void run(){ 117 while(isReceive){ 118 buffer = new byte[512]; 119 try { 120 inStream.read(buffer); 121 } catch (IOException e) { 122 // TODO Auto-generated catch block 123 e.printStackTrace(); 124 } 125 try { 126 str = new String(buffer,"UTF-8").trim(); 127 } catch (UnsupportedEncodingException e) { 128 // TODO Auto-generated catch block 129 e.printStackTrace(); 130 } 131 if(str!=""||str!=null){ 132 if(getRunningActivityName().equals("com.example.sockettestclient.TestGetSocketRes")){ 133 IsInPage=true; 134 } 135 if(IsInPage){ 136 Message msg = new Message(); 137 msg.obj = "/*star|data,aaa|end*/"+str; 138 SocketUIHandler.sendMessage(msg); 139 }else { 140 InitNotificationManager("通知",str); 141 } 142 if(str.indexOf("_pageend")>=0){ 143 IsInPage=false; 144 } 145 } 146 } 147 } 148 } 149 private String getRunningActivityName(){ 150 ActivityManager activityManager=(ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); 151 String runningActivity=activityManager.getRunningTasks(1).get(0).topActivity.getClassName(); 152 return runningActivity; 153 } 154 //发送消息的接口 155 Runnable sendThread = new Runnable() { 156 157 @Override 158 public void run() { 159 // TODO Auto-generated method stub 160 byte[] sendBuffer = null; 161 try { 162 sendBuffer = strMessage.getBytes("UTF-8"); 163 } catch (UnsupportedEncodingException e1) { 164 // TODO Auto-generated catch block 165 e1.printStackTrace(); 166 } 167 try { 168 outStream = socket.getOutputStream(); 169 } catch (IOException e) { 170 // TODO Auto-generated catch block 171 e.printStackTrace(); 172 } 173 try { 174 outStream.write(sendBuffer); 175 } catch (IOException e) { 176 // TODO Auto-generated catch block 177 e.printStackTrace(); 178 } 179 } 180 }; 181 182 183 }
1 protected void onCreate(Bundle savedInstanceState) { 2 final BaseActivity app = (BaseActivity)getApplication(); 3 super.onCreate(savedInstanceState); 4 setContentView(R.layout.activity_test_login); 5 Button btnConnect = (Button)findViewById(R.id.button1); 6 //连接 7 btnConnect.setOnClickListener(new View.OnClickListener() { 8 9 @Override 10 public void onClick(View v) { 11 // TODO Auto-generated method stub 12 if (!app.isConnect){ 13 new Thread(app.connectThread).start(); 14 } 15 Intent intent = new Intent(); 16 intent.setClass(TestLogin.this, UserLookPage.class); 17 startActivity(intent); 18 } 19 }); 20 21 }
1 protected void onCreate(Bundle savedInstanceState) { 2 super.onCreate(savedInstanceState); 3 setContentView(R.layout.activity_test_get_socket_res); 4 app = (BaseActivity)getApplication(); 5 TextView textReceive = (TextView)findViewById(R.id.textView1); 6 textReceive.setMovementMethod(ScrollingMovementMethod.getInstance()); 7 textReceive.setText(""); 8 Intent intent=getIntent(); 9 String title=intent.getStringExtra("title"); 10 String Content=intent.getStringExtra("Content"); 11 if(app.isConnect){ 12 app.strMessage = app.GetSocketPostDataHeadInfo(PostState.data)+Content; 13 new Thread(app.sendThread).start(); 14 } 15 final TextView resval=(TextView)findViewById(R.id.textView1); 16 app.SocketUIHandler= new Handler(){ 17 @Override 18 public void handleMessage(Message msg){ 19 String removestr=app.GetSocketPostDataHeadInfo(PostState.data); 20 resval.append((msg.obj).toString().replace(removestr, "")); 21 } 22 }; 23 //new Thread(app.sendThread).start(); 24 }
开发中遇到的问题
1.最初设计思路是直接后台将数据返回然后直接显示在通知栏用户打开通知栏将存在客户端的数据显示到页面上结果折腾了5个小时的问题就是后台返回的文本太多前台一次性收不完然后造成了页面显示数据不全而且通知栏有一大段的文本提示
最后解决方案就是提示时候只提示有一条消息然后打开时服务端继续推送到客户端 客户端采用追加的形式添加进去(可以优化为服务端分多次返回数据当前是服务端一次性取完数据)
2.使用log4net时配置正确显示不出的问题 解决方案
生成选项中选择如果较新则复制
运行效果
服务端运行
效果
点击登录
收到通知
跳转到显示数据界面并传入数据
测试环境:VS2015,Neon.3 Release (4.6.3),Android4.4,逍遥安卓模拟器4.4版本
本例运行需要更改的内容
中的
中的
中的
本例demo下载
未加wcf版本demo
链接: http://pan.baidu.com/s/1pKOoMld 密码: e5pg
补充:demo中联入WCF
添加WCF服务工厂
1 public class AndroidServiceFactory : IAndroidServiceFactory 2 { 3 private readonly string serviceUri = Common.Constants.WCFAndroidBindAddressUrl; 4 5 public IAndroidService CreateService() 6 { 7 return this.CreateService<IAndroidService>(serviceUri); 8 } 9 10 private T CreateService<T>(string uri) 11 { 12 var key = string.Format("{0} - {1}", typeof(T), uri); 13 14 if (Caching.Get(key) == null) 15 { 16 var binding = new WSHttpBinding(); 17 binding.MaxReceivedMessageSize = maxReceivedMessageSize; 18 binding.SendTimeout = new TimeSpan(0, 10, 0); 19 binding.CloseTimeout = new TimeSpan(0, 10, 0); 20 binding.ReceiveTimeout = new TimeSpan(0, 10, 0); 21 binding.OpenTimeout = new TimeSpan(0, 10, 0); 22 binding.ReaderQuotas = new XmlDictionaryReaderQuotas(); 23 binding.ReaderQuotas.MaxStringContentLength = maxReceivedMessageSize; 24 binding.ReaderQuotas.MaxArrayLength = maxReceivedMessageSize; 25 binding.ReaderQuotas.MaxBytesPerRead = maxReceivedMessageSize; 26 27 ChannelFactory<T> chan = new ChannelFactory<T>(binding, new EndpointAddress(uri)); 28 chan.Open(); 29 var service = chan.CreateChannel(); 30 Caching.Set(key, service); 31 return service; 32 } 33 else 34 { 35 return (T)Caching.Get(key); 36 } 37 } 38 39 private const int maxReceivedMessageSize = int.MaxValue; 40 }
1 /// <summary> 2 /// 轮询库存 3 /// </summary> 4 /// <param name="users"></param> 5 /// <param name="AlertTitle"></param> 6 private void WcfGetInventory() 7 { 8 try 9 { 10 while (true) 11 { 12 string str = wcf.AndroidService.Replenishmentnotice(); 13 //记录缺货的库存信息 14 if (!string.IsNullOrEmpty(str)) 15 { 16 lock (lockObj) 17 { 18 SendClientData = str; 19 } 20 } 21 //发送客户端通知 22 SendToAllClient(ServiceSetting.AlertTitle); 23 //等待30分钟后重新查询1800000 24 Thread.Sleep(10000);//10秒 25 if (Thread.CurrentThread.IsAlive) 26 { 27 WcfReTry = 0; 28 } 29 else { 30 if (WcfReTry <= ServiceSetting.WcfReTryCount) 31 { 32 //重启线程 33 Thread th = new Thread(WcfGetInventory); 34 th.Start(); 35 WcfReTry++; 36 } 37 LogHelper.Write("WcfGetInventory方法线程死掉并且无法重启", LogLev.Error); 38 } 39 } 40 } 41 catch (Exception ex) 42 { 43 if (WcfReTry <= ServiceSetting.WcfReTryCount) { 44 //重启线程 45 Thread th = new Thread(WcfGetInventory); 46 th.Start(); 47 WcfReTry++; 48 } 49 LogHelper.Log(ex); 50 } 51 }
然后在服务启动函数内启动线程