需求:
描述:实时在客户端上获取到哪些款需要补货.
要求: 后台需要使用c#,并且哪些需要补货的逻辑写在公司框架内,客户端采用PDA(即Android客户端 版本4.4) . 用户打开了补货通知页面时不需要在通知栏推送 如果不在时需要通知栏推送消息.
实现思路设计
思路图解释:
想法是启动服务时候创建TcpListener监听指定的Ip端口创建接收连接的线程和接收数据的回调由于 定义的存储方式(Dictionary<string, ClientSocketManager> Clients = new Dictionary<string, ClientSocketManager>();)所以采用了先由客户端建立连接然后服务端收到连接请求时立即发送一条数据给客户端要求客户端发送用户签名给服务端以便服务端可以根据用户Id之类的数据去发送给指定的用户 设置的数据格式为/*star|状态,user标识|end*/真实数据
客户端登录时候和服务端建立连接在任意界面都可以接收到通知打开通知时候进入展示数据的页面
服务端关键性代码
protected override void OnStart(string[] args)
{
if (IsListened) return;
//启动监听
IPAddress ip = IPAddress.Parse(ServiceSetting._IPAddress);
IPEndPoint point = new IPEndPoint(ip, ServiceSetting._IPEndPoint);
listener = new TcpListener(point);
try
{
listener.Start();
}
catch (Exception ex)
{
LogHelper.Log(ex);
return;
}
IsListened = true;
//接受连接请求的异步调用
AsyncCallback callback = new AsyncCallback(AcceptCallBack);
listener.BeginAcceptSocket(callback, listener);
LogHelper.Write("服务已启动……", LogLev.Info);
}
OnStart
private void AcceptCallBack(IAsyncResult ar)
{
try
{
//完成异步接收连接请求的异步调用
Socket handle = listener.EndAcceptSocket(ar);
ClientSocketManager manager = new ClientSocketManager(handle); AsyncCallback callback;
//继续调用异步方法接收连接请求
if (IsListened)
{
callback = new AsyncCallback(AcceptCallBack);
listener.BeginAcceptSocket(callback, listener);
}
//开始在连接上进行异步的数据接收
manager.ClearBuffer();
callback = new AsyncCallback(ReceiveCallback);
manager.socket.BeginReceive(manager.Rcvbuffer, , manager.Rcvbuffer.Length, SocketFlags.None, callback, manager);
}
catch (Exception ex)
{
//在调用EndAcceptSocket方法时可能引发异常
//套接字Listener被关闭,则设置为未启动侦听状态
IsListened = false;
LogHelper.Log(ex);
return;
}
}
接收连接回调
private void ReceiveCallback(IAsyncResult ar)
{
ClientSocketManager manager = (ClientSocketManager)ar.AsyncState;
try
{
int i = manager.socket.EndReceive(ar);
if (i == )
{
RemoveOneClientbyManager(manager);
return;
}
else
{
string data = Encoding.UTF8.GetString(manager.Rcvbuffer, , i);
//manager.socket.RemoteEndPoint.ToString() 获取客户端IP
manager.ClearBuffer();
//根据传入数据处理用户数据 设置数据格式为/*star|状态,user标识|end*/真实数据
//匹配中间的状态正则表达式 ^(/[*]star\|).*(?=\|end[*]/)
//首次传入保存连接信息
//打开通知时接收到回调
string packgeHead = Regex.Match(data, @"^(/[*]star\|).*(?=\|end[*]/)").ToString().Replace("/*star|", "");
if (string.IsNullOrEmpty(packgeHead))
{
LogHelper.Write("客户端没有发送指定的数据头", LogLev.Warn);
return;
}
string[] heads = packgeHead.Split(',');
switch (heads[])
{
case "first":// 首次传入保存连接信息
lock (lockObj)
{
Clients.Add(heads[], manager);
}
//ToRemove
SendToClient(new List<string>() { "aaa" }, "您有一条新的通知");
//endRemove
break;
case "data"://用户打开了页面可以发送数据
ReceiveThenSend(manager);
break;
default:
LogHelper.Write("客户端发送的数据头有问题", LogLev.Warn);
break;
}
AsyncCallback callback = new AsyncCallback(ReceiveCallback);
manager.socket.BeginReceive(manager.Rcvbuffer, , manager.Rcvbuffer.Length, SocketFlags.None, callback, manager);
}
}
catch (Exception ex)
{
RemoveOneClientbyManager(manager);
LogHelper.Log(ex);
return;
}
}
接收数据回调
private void SendData(ClientSocketManager manager, string data)
{
try
{
byte[] msg = Encoding.UTF8.GetBytes(data);
AsyncCallback callback = new AsyncCallback(new Action<IAsyncResult>(ar =>
{
ClientSocketManager frd = (ClientSocketManager)ar.AsyncState;
try
{
frd.socket.EndSend(ar);
}
catch (Exception ex)
{
RemoveOneClientbyManager(manager);
LogHelper.Log(ex);
return;
}
}));
manager.socket.BeginSend(msg, , msg.Length, SocketFlags.None, callback, manager);
}
catch (Exception ex)
{
RemoveOneClientbyManager(manager);
LogHelper.Log(ex);
return;
}
}
发送数据到客户端
客户端关键性代码
先授权
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.GET_TASKS" />
AndroidManifest.xml
在Application中插入代码 (ps:文件名由于开始打算建的是父级的Activity后来实验不行就没有更换名称)
package com.example.sockettestclient; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException; import android.app.ActivityManager;
import android.app.Application;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.NetworkInfo.State;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.NotificationCompat;
import android.widget.EditText;
import android.widget.TextView; public class BaseActivity extends Application{
@Override
public void onCreate(){
super.onCreate();
}
//NotificationManager mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//发送通知
protected void InitNotificationManager(String Title,String Content) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
Intent intent = new Intent(this, TestGetSocketRes.class);//将要跳转的界面
builder.setAutoCancel(true);//点击后消失
builder.setSmallIcon(R.drawable.info);//设置通知栏消息标题的头像
builder.setDefaults(NotificationCompat.DEFAULT_SOUND);//设置通知铃声
builder.setTicker(Title);
builder.setContentText(Content);//通知内容
builder.setContentTitle(Title);
//添加参数
Bundle bundle = new Bundle();
bundle.putString("title", Title);
bundle.putString("Content", Content);
intent.putExtras(bundle);
//利用PendingIntent来包装我们的intent对象,使其延迟跳转
PendingIntent intentPend = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(intentPend);
NotificationManager manager = (NotificationManager) this.getSystemService(this.NOTIFICATION_SERVICE);
manager.notify(0, builder.build());
}
protected boolean isConnect = false;
protected static final String ServerIP = "192.168.47.102";
protected static final int ServerPort = 4567;
protected Socket socket = null;
protected ReceiveThread receiveThread = null;
protected boolean isReceive = false;
protected Handler SocketUIHandler = null;
protected OutputStream outStream;
protected String strMessage;
protected TextView textReceive = null;
protected EditText textSend = null;
private boolean IsInPage=false;
private String UserID="aaa";
public enum PostState {
first, data
}
public void SetUserInfo() {
UserID="aaa";
}
public String GetSocketPostDataHeadInfo(PostState state) {
return "/*star|"+state.toString()+","+UserID+"|end*/";
}
Runnable connectThread = new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
try {
//初始化Scoket,连接到服务器
socket = new Socket(ServerIP, ServerPort);
isConnect = true;
//启动接收线程
isReceive = true;
receiveThread = new ReceiveThread(socket);
receiveThread.start();
strMessage = GetSocketPostDataHeadInfo(PostState.first);
new Thread(sendThread).start();
System.out.println("----connected success----");
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("UnknownHostException-->" + e.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out.println("IOException" + e.toString());
}
}
};
//接收线程
protected class ReceiveThread extends Thread{
private InputStream inStream = null; private byte[] buffer;
private String str = null; ReceiveThread(Socket socket){
try {
inStream = socket.getInputStream();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run(){
while(isReceive){
buffer = new byte[512];
try {
inStream.read(buffer);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
str = new String(buffer,"UTF-8").trim();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(str!=""||str!=null){
if(getRunningActivityName().equals("com.example.sockettestclient.TestGetSocketRes")){
IsInPage=true;
}
if(IsInPage){
Message msg = new Message();
msg.obj = "/*star|data,aaa|end*/"+str;
SocketUIHandler.sendMessage(msg);
}else {
InitNotificationManager("通知",str);
}
if(str.indexOf("_pageend")>=0){
IsInPage=false;
}
}
}
}
}
private String getRunningActivityName(){
ActivityManager activityManager=(ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
String runningActivity=activityManager.getRunningTasks(1).get(0).topActivity.getClassName();
return runningActivity;
}
//发送消息的接口
Runnable sendThread = new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
byte[] sendBuffer = null;
try {
sendBuffer = strMessage.getBytes("UTF-8");
} catch (UnsupportedEncodingException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
outStream = socket.getOutputStream();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
outStream.write(sendBuffer);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}; }
BaseApplication
protected void onCreate(Bundle savedInstanceState) {
final BaseActivity app = (BaseActivity)getApplication();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_login);
Button btnConnect = (Button)findViewById(R.id.button1);
//连接
btnConnect.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
if (!app.isConnect){
new Thread(app.connectThread).start();
}
Intent intent = new Intent();
intent.setClass(TestLogin.this, UserLookPage.class);
startActivity(intent);
}
}); }
登录页
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test_get_socket_res);
app = (BaseActivity)getApplication();
TextView textReceive = (TextView)findViewById(R.id.textView1);
textReceive.setMovementMethod(ScrollingMovementMethod.getInstance());
textReceive.setText("");
Intent intent=getIntent();
String title=intent.getStringExtra("title");
String Content=intent.getStringExtra("Content");
if(app.isConnect){
app.strMessage = app.GetSocketPostDataHeadInfo(PostState.data)+Content;
new Thread(app.sendThread).start();
}
final TextView resval=(TextView)findViewById(R.id.textView1);
app.SocketUIHandler= new Handler(){
@Override
public void handleMessage(Message msg){
String removestr=app.GetSocketPostDataHeadInfo(PostState.data);
resval.append((msg.obj).toString().replace(removestr, ""));
}
};
//new Thread(app.sendThread).start();
}
接收数据页
开发中遇到的问题
1.最初设计思路是直接后台将数据返回然后直接显示在通知栏用户打开通知栏将存在客户端的数据显示到页面上结果折腾了5个小时的问题就是后台返回的文本太多前台一次性收不完然后造成了页面显示数据不全而且通知栏有一大段的文本提示
最后解决方案就是提示时候只提示有一条消息然后打开时服务端继续推送到客户端 客户端采用追加的形式添加进去(可以优化为服务端分多次返回数据当前是服务端一次性取完数据)
生成选项中选择如果较新则复制
运行效果
服务端运行
测试环境:VS2015,Neon.3 Release (4.6.3),Android4.4,逍遥安卓模拟器4.4版本
本例运行需要更改的内容
本例demo下载
未加wcf版本demo
链接: http://pan.baidu.com/s/1pKOoMld 密码: e5pg
补充:demo中联入WCF
添加WCF服务工厂
public class AndroidServiceFactory : IAndroidServiceFactory
{
private readonly string serviceUri = Common.Constants.WCFAndroidBindAddressUrl; public IAndroidService CreateService()
{
return this.CreateService<IAndroidService>(serviceUri);
} private T CreateService<T>(string uri)
{
var key = string.Format("{0} - {1}", typeof(T), uri); if (Caching.Get(key) == null)
{
var binding = new WSHttpBinding();
binding.MaxReceivedMessageSize = maxReceivedMessageSize;
binding.SendTimeout = new TimeSpan(, , );
binding.CloseTimeout = new TimeSpan(, , );
binding.ReceiveTimeout = new TimeSpan(, , );
binding.OpenTimeout = new TimeSpan(, , );
binding.ReaderQuotas = new XmlDictionaryReaderQuotas();
binding.ReaderQuotas.MaxStringContentLength = maxReceivedMessageSize;
binding.ReaderQuotas.MaxArrayLength = maxReceivedMessageSize;
binding.ReaderQuotas.MaxBytesPerRead = maxReceivedMessageSize; ChannelFactory<T> chan = new ChannelFactory<T>(binding, new EndpointAddress(uri));
chan.Open();
var service = chan.CreateChannel();
Caching.Set(key, service);
return service;
}
else
{
return (T)Caching.Get(key);
}
} private const int maxReceivedMessageSize = int.MaxValue;
}
AndroidServiceFactory
/// <summary>
/// 轮询库存
/// </summary>
/// <param name="users"></param>
/// <param name="AlertTitle"></param>
private void WcfGetInventory()
{
try
{
while (true)
{
string str = wcf.AndroidService.Replenishmentnotice();
//记录缺货的库存信息
if (!string.IsNullOrEmpty(str))
{
lock (lockObj)
{
SendClientData = str;
}
}
//发送客户端通知
SendToAllClient(ServiceSetting.AlertTitle);
//等待30分钟后重新查询1800000
Thread.Sleep();//10秒
if (Thread.CurrentThread.IsAlive)
{
WcfReTry = ;
}
else {
if (WcfReTry <= ServiceSetting.WcfReTryCount)
{
//重启线程
Thread th = new Thread(WcfGetInventory);
th.Start();
WcfReTry++;
}
LogHelper.Write("WcfGetInventory方法线程死掉并且无法重启", LogLev.Error);
}
}
}
catch (Exception ex)
{
if (WcfReTry <= ServiceSetting.WcfReTryCount) {
//重启线程
Thread th = new Thread(WcfGetInventory);
th.Start();
WcfReTry++;
}
LogHelper.Log(ex);
}
}
加入轮询wcf方法
然后在服务启动函数内启动线程