節點通訊存在兩種模型:共享內存(Shared memory)和消息傳遞(Messages passing)。javascript
內存映射文件對於託管世界的開發人員來講彷佛很陌生,但它確實已是很遠古的技術了,並且在操做系統中地位至關。實際上,任何想要共享數據的通訊模型都會在幕後使用它。html
內存映射文件到底是個什麼?內存映射文件容許你保留一塊地址空間,而後將該物理存儲映射到這塊內存空間中進行操做。物理存儲是文件管理,而內存映射文件是操做系統級內存管理。前端
優點:
1.訪問磁盤文件上的數據不需執行I/O操做和緩存操做(當訪問文件數據時,做用尤爲顯著);
2.讓運行在同一臺機器上的多個進程共享數據(單機多進程間數據通訊效率最高);java
利用文件與內存空間之間的映射,應用程序(包括多個進程)能夠經過直接在內存中進行讀寫來修改文件。.NET Framework 4 用託管代碼按照本機Windows函數訪問內存映射文件的方式來訪問內存映射文件,管理 Win32 中的內存映射文件 。ios
有兩種類型的內存映射文件:web
-
持久內存映射文件redis
持久文件是與磁盤上的源文件關聯的內存映射文件。在最後一個進程使用完此文件後,數據將保存到磁盤上的源文件中。這些內存映射文件適合用來處理很是大的源文件。數據庫
-
非持久內存映射文件c#
非持久文件是未與磁盤上的源文件關聯的內存映射文件。當最後一個進程使用完此文件後,數據將丟失,而且垃圾回收功能將回收此文件。這些文件適用於爲進程間通訊 (IPC) 建立共享內存。瀏覽器
1)在多個進程之間進行共享(進程可經過使用由建立同一內存映射文件的進程所指派的公用名來映射到此文件)。
2)若要使用一個內存映射文件,則必須建立該內存映射文件的完整視圖或部分視圖。還能夠建立內存映射文件的同一部分的多個視圖,進而建立併發內存。爲了使兩個視圖可以併發,必須基於同一內存映射文件建立這兩個視圖。
3)若是文件大於應用程序用於內存映射的邏輯內存空間(在 32 位計算機上爲2GB),則還須要使用多個視圖。
有兩種類型的視圖:流訪問視圖和隨機訪問視圖。使用流訪問視圖可對文件進行順序訪問;在使用持久文件時,隨機訪問視圖是首選方法。
.Net 共享內存 內存映射文件原理:經過操做系統的內存管理器訪問的,所以會自動將此文件分隔爲多個頁,並根據須要對其進行訪問。您不須要自行處理內存管理。以下圖:
C# .Net 共享內存 演示代碼以下:
//持久內存映射文件:基於現有文件建立一個具備指定公用名的內存映射文件
using (var mmf = MemoryMappedFile.CreateFromFile(@"c:\內存映射文件.data", FileMode.Open, "公用名"))
{
//經過指定的 偏移量和大小 建立內存映射文件視圖服務器
using (var accessor = mmf.CreateViewAccessor(offset, length)) //偏移量,能夠控制數據存儲的內存位置;大小,用來控制存儲所佔用的空間
{
//Marshal提供了一個方法集,這些方法用於分配非託管內存、複製非託管內存塊、將託管類型轉換爲非託管類型,此外還提供了在與非託管代碼交互時使用的其餘雜項方法。
int size = Marshal.SizeOf(typeof(char));
//修改內存映射文件視圖
for (long i = 0; i < length; i += size)
{
char c= accessor.ReadChar(i);
accessor.Write(i, ref c);
}
}
}
//另外一個進程或線程能夠,在系統內存中打開一個具備指定名稱的現有內存映射文件
using (var mmf = MemoryMappedFile.OpenExisting("公用名"))
{
using (var accessor = mmf.CreateViewAccessor(4000000, 2000000))
{
int size = Marshal.SizeOf(typeof(char));
for (long i = 0; i < length; i += size)
{
char c = accessor.ReadChar(i);
accessor.Write(i, ref c);
}
}
}
//非持久內存映射文件:未映射到磁盤上的現有文件的內存映射文件
using (MemoryMappedFile mmf = MemoryMappedFile.CreateNew("testmap", 10000))
{
bool mutexCreated;
//進程間同步
Mutex mutex = newMutex(true, "testmapmutex", out mutexCreated);
using (var stream = mmf.CreateViewStream()) //建立文件內存視圖流 基於流的操做
{
var writer = newBinaryWriter(stream);
writer.Write(1);
}
mutex.ReleaseMutex();
Console.WriteLine("Start Process B and press ENTER to continue.");
Console.ReadLine();
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
var reader = newBinaryReader(stream);
Console.WriteLine("Process A says: {0}", reader.ReadBoolean());
Console.WriteLine("Process B says: {0}", reader.ReadBoolean());
}
mutex.ReleaseMutex();
}
using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting("testmap"))
{
Mutex mutex = Mutex.OpenExisting("testmapmutex");
mutex.WaitOne();
using (var stream = mmf.CreateViewStream(1, 0))//注意這裏的偏移量
{
var writer = newBinaryWriter(stream);
writer.Write(0);
}
mutex.ReleaseMutex();
}
C# .Net 進程間通訊 共享內存 完整示例: C#共享內存非持久化方式通信的例子,通信時的線程和進程控制也沒有問題。以下是實現的代碼。
先啓動消息服務IMServer_Message,
再啓動狀態服務IMServer_State,
IMServer_Message回車一次(建立共享內存公用名和公用線程鎖,並視圖流方式寫共享內存),
IMServer_State回車一次(獲取共享內存並視圖流方式寫、視圖訪問器寫入結構體類型)
並馬上IMServer_Message再回車一次(讀取剛剛寫入的信息),
觀察IMServer_State屏顯變化並等待(線程鎖)約5s(線程鎖被釋放)後
在IMServer_Message上觀察屏顯(顯示剛剛寫入共享內存的信息)
IMServer_Message.exe 代碼
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Threading;
namespace IMServer_Message
{
/// <summary>
/// 用於共享內存方式通訊的 值類型 結構體
/// </summary>
public struct ServiceMsg
{
public int Id;
public long NowTime;
}
internal class Program
{
private static void Main(string[] args)
{
Console.Write("請輸入共享內存公用名(默認:testmap):");
string shareName = Console.ReadLine();
if (string.IsNullOrEmpty(shareName))
shareName = "testmap";
using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(shareName, 1024000,MemoryMappedFileAccess.ReadWrite))
{
bool mutexCreated;
//進程間同步
var mutex = new Mutex(true, "testmapmutex", out mutexCreated);
using (MemoryMappedViewStream stream = mmf.CreateViewStream()) //建立文件內存視圖流
{
var writer = new BinaryWriter(stream);
for (int i = 0; i < 5; i++)
{
writer.Write(i);
Console.WriteLine("{0}位置寫入流:{0}", i);
}
}
mutex.ReleaseMutex();
Console.WriteLine("啓動狀態服務,按【回車】讀取共享內存數據");
Console.ReadLine();
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream())
{
var reader = new BinaryReader(stream);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("{1}位置:{0}", reader.ReadInt32(), i);
}
}
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(1024, 10240))
{
int colorSize = Marshal.SizeOf(typeof (ServiceMsg));
ServiceMsg color;
for (int i = 0; i < 50; i += colorSize)
{
accessor.Read(i, out color);
Console.WriteLine("{1}\tNowTime:{0}", new DateTime(color.NowTime), color.Id);
}
}
mutex.ReleaseMutex();
}
Console.WriteLine("測試: 我是 即時通信 - 消息服務 我啓動啦!!!");
Console.ReadKey();
}
}
}
IMServer_State.exe代碼
using System;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Runtime.InteropServices;
using System.Threading;
namespace IMServer_State
{
/// <summary>
/// 用於共享內存方式通訊的 值類型 結構體
/// </summary>
public struct ServiceMsg
{
public int Id;
public long NowTime;
}
internal class Program
{
private static void Main(string[] args)
{
Console.Write("請輸入共享內存公用名(默認:testmap):");
string shareName = Console.ReadLine();
if (string.IsNullOrEmpty(shareName))
shareName = "testmap";
using (MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(shareName, 1024000,MemoryMappedFileAccess.ReadWrite))
{
Mutex mutex = Mutex.OpenExisting("testmapmutex");
mutex.WaitOne();
using (MemoryMappedViewStream stream = mmf.CreateViewStream(20, 0)) //注意這裏的偏移量
{
var writer = new BinaryWriter(stream);
for (int i = 5; i < 10; i++)
{
writer.Write(i);
Console.WriteLine("{0}位置寫入流:{0}", i);
}
}
using (MemoryMappedViewAccessor accessor = mmf.CreateViewAccessor(1024, 10240))
{
int colorSize = Marshal.SizeOf(typeof (ServiceMsg));
var color = new ServiceMsg();
for (int i = 0; i < colorSize*5; i += colorSize)
{
color.Id = i;
color.NowTime = DateTime.Now.Ticks;
//accessor.Read(i, out color);
accessor.Write(i, ref color);
Console.WriteLine("{1}\tNowTime:{0}", new DateTime(color.NowTime), color.Id);
Thread.Sleep(1000);
}
}
Thread.Sleep(5000);
mutex.ReleaseMutex();
}
Console.WriteLine("測試: 我是 即時通信 - 狀態服務 我啓動啦!!!");
Console.ReadKey();
}
}
}
進程A寫數據,進程B讀數據;
進程A:
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE lhShareMemory;
char* lpBuffer = NULL;
lhShareMemory = CreateFileMapping(HANDLE(0xFFFFFFFF), NULL, PAGE_READWRITE,
0, 10, "mySharedMemory");
if (NULL == lhShareMemory)
{
if (ERROR_ALREADY_EXISTS == GetLastError())
{
cout << "Already exists!";
}
else
{
cout << "Create Sheared Memory unsuccessfully!";
}
return 0;
}
lpBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_WRITE, 0, 0, 10);
if (NULL == lpBuffer)
{
cout << "Get Share memory unsuccessfully!";
return 0;
}
strcpy(lpBuffer, "hello");
cout << *(lpBuffer + 40) << endl;
Sleep(600000);
UnmapViewOfFile(lpBuffer);
return 0;
}
進程B:
#include "stdafx.h"
#include <Windows.h>
#include <iostream>
#include <string>
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
HANDLE lhShareMemory;
char* lpcBuffer;
lhShareMemory = OpenFileMapping(FILE_MAP_READ, false, "mySharedMemory");
if (NULL == lhShareMemory)
{
cout << "Open share memory unsuccessfully!" << endl;
DWORD ldwError = GetLastError();
cout << ldwError;
return 0;
}
lpcBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_READ, 0, 0, 100);
if (NULL == lpcBuffer)
{
cout << "Open share memory unsuccessfully!";
return 0;
}
for (int i = 0; i < 100; ++i)
{
cout << *(lpcBuffer + i);
}
UnmapViewOfFile(lpcBuffer);
return 0;
}
.net環境下跨進程、高頻率讀寫數據
1、需求背景
一、最近項目要求高頻次地讀寫數據,數據量也不是很大,多表總共加起來在百萬條上下。
單表最大的也在25萬左右,歷史數據表由於不涉及因此不用考慮,
難點在於這個規模的熱點數據,變化很是頻繁。
數據來源於一些檢測設備的採集數據,一些大表,有可能在極短期內(如幾秒鐘)可能大部分都會變化,
並且主程序也有一些後臺服務須要不斷輪詢、讀寫某種類型的設備,因此要求信息交互時間儘量短。
二、以前的解決方案是把全部熱點數據,統一加載到共享內存裏邊,到也可以支撐的住(毫秒級的),可是因爲系統架構升級,以前的程序(20年前的)不能兼容。
只能從新寫一個,最早想到的是用redis,當時把全部API重寫完成後,測試發現效率不行,是的,你沒有看錯,redis也是有使用範圍的。
三、redis讀寫很是快,可是對於大批量讀寫操做我以爲支持不夠,雖然redis支持批量讀寫,可是效率仍是不夠快,
對於字符串(string)類型的批量讀寫,我測試過;效率比較好的在每批次200 至 250條之間,處理20萬條數據耗時5秒左右, (PC機,8G,4核)
而對於有序集合(sorted set)類型,批量寫的操做用起來很是彆扭,並且沒有修改API(若有其餘方式請指教),我測試過,效率沒string類型那麼高
其餘類型不適合個人業務場景,就沒考慮使用了
四、因此項目組最後決定仍是用回共享內存,先決定在.net環境下使用c#的共享內存,這個功能可能使用的人很少,其實在.net4.0版本就已經集成進來了
在System.IO.MemoryMappedFile命名空間下。這個類庫讓人很無語,由於裏邊能用的只有Write、Read這2種方法,並且只是針對字節的操做,
須要很是多的類型轉換,很是麻煩!想一想,只能以字節爲單位去構建一個須要存放百萬級數據的內存數據庫,得多麻煩?
須要手動搞定索引功能,由於要支持各類查詢,最後花了一天的時間寫完DEMO,最後測試後發現效率並無很大提升,由於當時加了互斥量測試,
可是離毫秒級差得遠。這個技術點有興趣的能夠了解下,園子裏有,如:https://www.cnblogs.com/zeroone/archive/2012/04/18/2454776.html
2、沒錯,第一節寫的太多了
一、最後分析,這應該是c#語言的瓶頸,c#對於這種騷操做是不那麼成熟的。
二、最後瞄來瞄去,決定使用VC開發一個dll,在裏邊封裝對內存數據的讀寫功能,而後c#調用
三、本人的C、C++不那麼熟、參考了一些實例,好比園子裏的:http://www.cnblogs.com/cwbcwb505/archive/2008/12/08/1350505.html
四、是的,你沒有看錯,2008年的,我還看到一篇更早的,看來底層開發C、C++那麼經久不衰不是沒有道理的,不少技術如今都在用
五、看看什麼是共享內存
3、開始寫代碼了
一、首先建2個控制檯項目,支持MFC,
二、先這樣:一個負責建立共享內存,初始化數據
三、再這樣:一個讀寫數據測試,最後修改
四、最後修改下圖片細節,測試一下,看看效果
五、完成了,see, 是否是很簡單呀?都會了嗎?
4、真的要貼代碼了
一、先定義個枚舉返回狀態
1 typedef enum 2 { 3 Success = 0, 4 AlreadyExists = 1, 5 Error = 2, 6 OverSize = 3 7 }enumMemory;
二、再定義個結構體用來測試
1 typedef struct 2 { 3 int TagID; 4 char TagName[32]; 5 int Area; 6 double EngVal; 7 double UpdateTime; 8 double RawMax; 9 double RawMin; 10 double RawVal; 11 char Name[50]; 12 char Al; 13 double ASTime; 14 char MaskState; 15 double AMTime; 16 char Cf; 17 char Tdf; 18 char AlarmCode[32]; 19 }TENG;
三、開始建立共享內存
1 int Create(UINT size) 2 { 3 // Data 4 HANDLE fileMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, size, 「Name」); 5 6 if (fileMap == NULL || fileMap == INVALID_HANDLE_VALUE) 7 return Error; 8 9 if (GetLastError() == ERROR_ALREADY_EXISTS) 10 return AlreadyExists; 11 12 // init 13 void *mapView = MapViewOfFile(fileMap, FILE_MAP_WRITE, 0, 0, size); 14 15 if (mapView == NULL) 16 return Error; 17 else 18 memset(mapView, 0, size); 19 20 return Success; 21 }
四、再開始寫數據
1 int Write(void *pDate, UINT nSize, UINT offset) 2 { 3 // open 4 HANDLE fileMap = OpenFileMapping(FILE_MAP_WRITE, FALSE, 「Name」); 5 6 if (fileMap == NULL) 7 return Error; 8 9 // hander 10 void *mapView = MapViewOfFile(fileMap, FILE_MAP_WRITE, 0, 0, nSize); 11 12 if (mapView == NULL) 13 return Error; 14 else 15 WriteDataPtr = mapView; 16 17 // write 18 memcpy(mapView, pDate, nSize); 19 20 UnmapViewOfFile(pMapView); 21 return Success; 22 }
五、開始讀數據
1 int Read(void *pData, UINT nSize, UINT offset) 2 { 3 // open 4 HANDLE fileMap = OpenFileMapping(FILE_MAP_READ, FALSE, GetTableName()); 5 6 if (fileMap == NULL) 7 return Error; 8 9 // hander 10 void *pMapView = MapViewOfFile(fileMap, FILE_MAP_READ, 0, 0, nSize); 11 12 if (pMapView == NULL) 13 return Error; 14 else 15 ReadDataPtr = pMapView; 16 17 memcpy(pData, (pMapView, nSize); 18 19 UnmapViewOfFile(pMapView); 20 return Success; 21 }
六、OK了,不復雜,網上都有這些資料,最後咱們貼上測試程序
1 int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) 2 { 3 int length = 100000; 4 CEng * ceng = new CEng(); 5 DWORD dwStart = GetTickCount(); 6 7 for (int i = 0; i < length; i++) { 8 TENG eng; 9 ceng->Read(&eng, ceng->size, ceng->size * i); 10 11 eng.EngVal = i; 12 ceng->Write(&eng, ceng->size, (i*ceng->size)); 13 14 if (i % 10000 == 0 || i == length - 1) 15 printf("正在讀寫的Eng.TagName:%s \n", eng.TagName); 16 } 17 18 printf("總條數%d,耗時:%d 毫秒 \n", length, GetTickCount() - dwStart); 19 20 // 驗證數據 21 TENG eng5000; 22 ceng->Read(&eng5000, ceng->size, ceng->size * 5000); 23 printf("\n驗證數據 \n"); 24 printf("第5000個Eng的TagID:%d, EngVal:%lf \n", eng5000.TagID, eng5000.EngVal); 25 26 27 scanf_s("按任意鍵結束"); 28 return 0; 29 }
七、還有寫測試程序
1 int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) 2 { 3 int length = 100000; 4 CEng * ceng = new CEng(); 5 ceng->Create(ceng->size * length); 6 7 DWORD dwStart = GetTickCount(); 8 9 for (int i = 0; i < length; i++) 10 { 11 TENG eng; 12 memset(&eng, 0, ceng->size); 13 14 eng.TagID = i; 15 sprintf_s(eng.AlarmCode, "AlarmCode.%d", i); 16 sprintf_s(eng.TagName, "TagName.%d", i); 17 18 if (i % 10000 == 0 || i == length - 1) 19 printf("正在寫入的Eng.TagName:%s \n", eng.TagName); 20 21 ceng->Write(&eng, ceng->size, (i*ceng->size)); 22 } 23 24 25 // print time 26 printf("寫入數據完畢,總條數:%d\n", length); 27 printf("初始化值共享內存區耗時:%d 毫秒 \n", GetTickCount() - dwStart); 28 29 30 scanf_s("按任意鍵結束"); 31 return 0; 32 }
八、固然得再貼一遍啦
5、差點忘記作成DLL了
一、定義外部函數
1 extern "C" __declspec(dllexport) int ReadFromSharedMemory(TENG *pData, int nSize, int offset) 2 { 3 return ceng->Read(pData, nSize, offset); 4 } 5 6 extern "C" __declspec(dllexport) int WriteToSharedMemory(void *pData, int nSize, int offset) 7 { 8 return ceng->Write(pData, nSize, offset); 9 }
二、好了,VC到此爲止,能夠去領盒飯了,c#進場
1 public class Lib 2 { 3 [DllImport("ConsoleApplication4.dll", CallingConvention = CallingConvention.Cdecl)] 4 public static extern int ReadFromSharedMemory(IntPtr pData, int nSize, int offset); 5 6 [DllImport("ConsoleApplication4.dll", CallingConvention = CallingConvention.Cdecl)] 7 public static extern int WriteToSharedMemory(IntPtr pData, int nSize, int offset); 8 }
三、c#測試一下
1 static void Main(string[] args) 2 { 3 var length = 100000; 4 var startTime = DateTime.Now; 5 var size = Marshal.SizeOf(typeof(TEng)); 6 var intPtrOut = Marshal.AllocHGlobal(size); 7 var intPtrIn = Marshal.AllocHGlobal(size); 8 9 for (var i = 0; i < length; i++) 10 { 11 Lib.ReadFromSharedMemory(intPtrOut, size, size * i); 12 13 var eng = Marshal.PtrToStructure<TEng>(intPtrOut); 14 eng.EngVal = i; 15 16 Marshal.StructureToPtr(eng, intPtrIn, true); 17 Lib.WriteToSharedMemory(intPtrIn, size, size * i); 18 19 if (i % 10000 == 0) 20 Console.WriteLine("eng.TagID:{0}", eng.TagID); 21 } 22 23 Console.WriteLine("總條數{0},耗時:{1} 毫秒", length.ToString(), 24 (DateTime.Now - startTime).TotalMilliseconds.ToString()); 25 26 // 驗證數據 27 var intPtr100 = Marshal.AllocHGlobal(size); 28 Lib.ReadFromSharedMemory(intPtr100, size, size * 100); 29 30 var eng100 = Marshal.PtrToStructure<TEng>(intPtr100); 31 32 Console.WriteLine(); 33 Console.WriteLine("驗證數據"); 34 Console.WriteLine("第100個Eng的TagID:{0},EngVal:{1}", eng100.TagID, eng100.EngVal); 35 36 Console.ReadKey(); 37 }
四、165毫秒,相比在VC下運行,差了一個數量級,可是,也不錯了;
由於c#環境下須要不斷的Marshal.PtrToStructure、Marshal.StructureToPtr,頻繁地把數據在託管內存俞共享內存之間搬運
是須要耗費時間的,這點有更好處理方式的請指教,
6、由於跨線程、進程,因此要考慮加入互斥量哦
一、很簡單,MFC下有現成的類CMutex,加在Write裏邊在看看效率
互斥量是須要耗費資源的,多了將進100毫秒
二、讀寫都加上互斥量試試看
又多了80多毫秒,
魚與熊掌不可兼得啊。要根據實際運用場景以爲是否加上互斥量
好了,人家51去遊玩、我卻宅家裏碼程序,可見個人趣味仍是挺高的,洗澡、洗衣服、而後去吃飯、一天沒進食了,
內存映射
1,Redis操做性能,單機220w每秒,存放數據15億,至強32核,512G內存
2,C#內存操做,每秒1.6億次,AMD8核8G內存
3,C#共享內存操做,簡稱MMF,4000w讀寫每秒
使用C#開發Android應用之WebApp
近段時間瞭解了一下VS2017開發安卓應用的一些技術,特意把C#開發WebApp的一些過程記錄下來,
歡迎你們一塊兒指教、討論,廢話少說,是時候開始表演真正的技術了。。
一、新建空白Android應用
二、拖一個WebView控件進來
三、打開模擬器Genymotion,選擇一個系統版本,啓動
四、加載網頁
4.1 打開MainActivity.cs,在OnCreate方法裏添加2行代碼
1 protected override void OnCreate(Bundle savedInstanceState) 2 { 3 base.OnCreate(savedInstanceState); 4 5 // Set our view from the "main" layout resource 6 SetContentView(Resource.Layout.Main); 7 8 var web = FindViewById<WebView>(Resource.Id.webView1); 9 web.LoadUrl("http://www.baidu.com"); 10 }
加載網頁就是這樣簡單,F5調試,就能夠看到模擬器有了變化,打開了咱們的應用,並如期加載了網頁
五、網頁端調用手機APP後臺方法
5.1 打開MainActivity.cs,重寫OnCreate爲以下
1 protected override void OnCreate(Bundle savedInstanceState) 2 { 3 base.OnCreate(savedInstanceState); 4 5 var webView = new WebView(this); 6 SetContentView(webView); 7 8 webView.Settings.JavaScriptEnabled = true; 9 webView.AddJavascriptInterface(new CustomJSInterface(this), "CSharp"); 10 webView.LoadUrl("http://192.168.0.113:8080/"); 11 }
標紅的是實現前端調用後臺方法的關鍵,新建CustomJSInterface.cs
1 public class CustomJSInterface : Java.Lang.Object 2 { 3 Context context; 4 5 public CustomJSInterface(Context context) 6 { 7 this.context = context; 8 } 9 10 [Export] 11 [JavascriptInterface] 12 public void ShowToast(string message) 13 { 14 Toast.MakeText(context, message, ToastLength.Short).Show(); 15 } 16 }
而"http://192.168.0.113:8080/"是咱們的Web站點,大部分業務邏輯在網站裏處理,WebApp只是在外表包了一個殼
5.2 咱們再新建一個本地Web站點
改動首頁HTML,主要功能是點擊按鈕,會調用後臺ShowToast,這是個提示功能
1 @{ 2 ViewBag.Title = "Home Page"; 3 } 4 5 <br /> 6 <div class="jumbotron"> 7 <button type="button" onClick="CSharp.ShowToast('hello')">由前端調用C#後臺方法</button> 8 </div>
瀏覽器預覽
5.3 VS2017按F5部署,能夠看的模擬器也正常把本地站點加載進來了
點擊"獲取前端JS返回的數據"
六、APP執行前端JS方法
6.1 重寫OnCreate
1 // 必須重寫WebView客戶端 2 webView.SetWebViewClient(new CustomWebViewClient()); 3 // 先打開首頁 4 webView.LoadUrl("http://192.168.0.113:8080/"); 5 6 // APP主動獲取前端數據 7 var btn = FindViewById<Button>(Resource.Id.button1); 8 btn.Click += delegate 9 { 10 var callback = new ReceiveValueCallback(); 11 callback.OnReceiveValueCallback += (message) => 12 { 13 Toast.MakeText(this.ApplicationContext, message, ToastLength.Short).Show(); 14 }; 15 16 webView.EvaluateJavascript("GetData()", callback); 17 };
6.2 新建CustomWebViewClient.cs
1 class CustomWebViewClient : WebViewClient 2 { 3 public override bool ShouldOverrideUrlLoading(WebView view, String url) 4 { 5 view.LoadUrl(url); 6 return true; 7 } 8 }
6.3 新建ReceiveValueCallback.cs,這個類主要負責處理前端返回的數據
1 public class ReceiveValueCallback : Java.Lang.Object, IValueCallback 2 { 3 public delegate void OnReceiveValueCallbackHandler(string message); 4 public event OnReceiveValueCallbackHandler OnReceiveValueCallback; 5 6 // 重寫ReceiveValue方法 7 public void OnReceiveValue(Java.Lang.Object value) 8 { 9 OnReceiveValueCallback(value.ToString()); 10 } 11 }
6.4 修改Index.html
1 @{ 2 ViewBag.Title = "Home Page"; 3 } 4 5 <br /> 6 <div class="jumbotron"> 7 <button type="button" onClick="CSharp.ShowToast('hello')">由前端調用C#後臺方法</button> 8 </div> 9 10 <script type="text/javascript"> 11 function GetData() { 12 return "123456789"; 13 } 14 </script>
6.5 VS2017按F5部署
6.6 點擊按鈕"獲取前端JS返回的數據"
七、WebAPP使用疑問
7.1 細心的人可能注意到:前端代碼徹底能夠本身處理完業務,那還有WebApp什麼事情呢?這時的APP徹底就跟一個瀏覽器差很少!
7.2 確實是這樣的WebApp相對與其餘安卓APP來講,是輕量級的,只是一個殼子,可是他也是有其合適的使用範圍;
好比:若是前端並無數據持久化功能(如純JS前端),這時要保存數據只能調用其餘的WebApi,而因爲JS的特性可能會引發一些安全問題。
或者根本沒有第三方API,數據須要保存在手機端,JS也沒有這種權限。
因此既兼顧了像升級Web站點那樣簡便,又有一些手機端的操做權限,WebApp應運而生。
分佈式事務之消息補償解決方案
1、數據庫本地事務
先看看數據庫事務的定義:單個邏輯工做單元執行的一系列操做,要麼徹底地執行,要麼徹底地不執行
這個比較容易理解,操做過數據庫的通常都懂,既是業務需求涉及到多個數據表操做的時候,須要用到事務
要麼一塊兒更新,要麼一塊兒不更新,不會出現只更新了部分數據表的狀況,下邊看看數據庫事務的使用
1 begin tran 2 begin try 3 update Table1 set Field = 1 where ID = 1 4 update Table2 set Field = 2 where ID = 1 5 end try 6 begin catch 7 rollback tran 8 end catch 9 commit tran
上實例在小型項目中通常是問題不大的,由於小型項目通常是單機系統,數據庫、Web服務大都在一臺服務器上,甚至可能只有一個數據庫文件,
這種狀況下使用本地事務沒有一點問題;
可是本地事務有很大的缺陷,由於開啓事務通常是鎖表的,事務執行期間會一直鎖着,其餘的操做通常都要排隊等待,對性能要求比較高的系統是不能忍受的。
特別是涉及改動不一樣數據庫的操做,這會形成跨庫事務,性能更加低
若是還涉及到不在同一臺服務器、甚至不一樣網段部署的數據庫,那本地事務簡直是系統運行的災難,是首先須要丟棄的解決方案。
那若是遇到上述狀況,該怎麼作呢,這就涉及到分佈式事務了
2、分段式事務的補償機制
若是有海量數據須要處理、或者要求高併發請求的話,同步的事務機制已是不現實的了,這種狀況下必須採用異步事務機制,既分段式的事務
分段式事務通常作法就是把需求任務分段式地完成,經過事務補償機制來保證業務最終執行成功,補償機制通常能夠歸類爲2種:
1 )定時任務補償:
經過定時任務去跟進後續任務,根據不一樣的狀態表肯定下一步的操做,從而保證業務最終執行成功,
這種辦法可能會涉及到不少的後臺服務,維護起來也會比較麻煩,這是應該是早期比較流行的作法
2) 消息補償:
經過消息中間件觸發下一段任務,既經過實時消息通知下一段任務開始執行,執行完畢後的消息回發通知來保證業務最終完成;
固然這也是異步進行的,可是能保證數據最終的完整性、一致性,也是近幾年比較熱門的作法
定時任務補償就不說了,這篇文章咱們來討論一下經過消息補償來完成分佈式事務的通常作法
3、分佈式事務之消息補償
0)咱們以簡單的產品下單場景來講明,(不要較真哈)
1)先來看看分佈式異步事務處理流程示意圖,APP1與APP2須要互相訂閱對方消息
2)首先看數據庫,2個,一個庫存庫,一個已下單成功的庫
1 -- 下單通知,主要做用保留已下單操做,消息發送失敗能夠根據此表從新發送 2 CREATE TABLE [dbo].[ProductMessage]( 3 [ID] [int] IDENTITY(1,1) NOT NULL, 4 [Product] [varchar](50) NULL, 5 [Amount] [int] NULL, 6 [UpdateTime] [datetime] NULL 7 ) 8 -- 庫存 9 CREATE TABLE [dbo].[ProductStock]( 10 [ID] [int] IDENTITY(1,1) NOT NULL, 11 [Product] [varchar](50) NULL, 12 [Amount] [int] NULL 13 ) 14 -- 下單成功 15 CREATE TABLE [dbo].[ProductSell]( 16 [ID] [int] IDENTITY(1,1) NOT NULL, 17 [Product] [varchar](50) NULL, 18 [Customer] [int] NULL, 19 [Amount] [int] NULL 20 ) 21 -- 下單成功消息,主要做用防止重複消費 22 CREATE TABLE [dbo].[ProductMessageApply]( 23 [ID] [int] IDENTITY(1,1) NOT NULL, 24 [MesageID] [int] NULL, 25 [CreateTime] [datetime] NULL 26 )
3)項目架構Demo
數據底層訪問使用的是Dapper、使用redis做爲消息中間件
4)實體層代碼
1 public class ProductMessage 2 { 3 [Key] 4 [IgnoreProperty(true)] 5 public int ID { get; set; } 6 public string Product { get; set; } 7 public int Amount { get; set; } 8 public DateTime UpdateTime { get; set; } 9 } 10 public class ProductMessageApply 11 { 12 [Key] 13 [IgnoreProperty(true)] 14 public int ID { get; set; } 15 public int MesageID { get; set; } 16 public DateTime CreateTime { get; set; } 17 } 18 public class ProductSell 19 { 20 [Key] 21 [IgnoreProperty(true)] 22 public int ID { get; set; } 23 public string Product { get; set; } 24 public int Customer { get; set; } 25 public int Amount { get; set; } 26 } 27 public class ProductStock 28 { 29 [Key] 30 [IgnoreProperty(true)] 31 public int ID { get; set; } 32 public string Product { get; set; } 33 public int Amount { get; set; } 34 }
5)服務接口層代碼
1 public interface IProductMessageApplyService 2 { 3 void Add(ProductMessageApply entity); 4 ProductMessageApply Get(int id); 5 } 6 public interface IProductMessageService 7 { 8 void Add(ProductMessage entity); 9 IEnumerable<ProductMessage> Gets(object paramPairs = null); 10 void Delete(int id); 11 } 12 public interface IProductSellService 13 { 14 void Add(ProductSell entity); 15 } 16 public interface IProductStockService 17 { 18 void ReduceReserve(int id, int amount); 19 }
6)庫存、消息通知
1 public class ProductMessageService : IProductMessageService 2 { 3 private IRepository<ProductMessage> repository; 4 5 public ProductMessageService(IRepository<ProductMessage> repository) 6 { 7 this.repository = repository; 8 } 9 10 public void Add(ProductMessage entity) 11 { 12 this.repository.Add(entity); 13 } 14 15 public IEnumerable<ProductMessage> Gets(object paramPairs = null) 16 { 17 return this.repository.Gets(paramPairs); 18 } 19 20 public void Delete(int id) 21 { 22 this.repository.Delete(id); 23 } 24 } 25 26 public class ProductStockService : IProductStockService 27 { 28 private IRepository<ProductStock> repository; 29 30 public ProductStockService(IRepository<ProductStock> repository) 31 { 32 this.repository = repository; 33 } 34 35 public void ReduceReserve(int id, int amount) 36 { 37 var entity = this.repository.Get(id); 38 if (entity == null) return; 39 40 entity.Amount = entity.Amount - amount; 41 this.repository.Update(entity); 42 } 43 }
7)下單、下單成功消息
1 public class ProductMessageApplyService : IProductMessageApplyService 2 { 3 private IRepository<ProductMessageApply> repository; 4 5 public ProductMessageApplyService(IRepository<ProductMessageApply> repository) 6 { 7 this.repository = repository; 8 } 9 10 public void Add(ProductMessageApply entity) 11 { 12 this.repository.Add(entity); 13 } 14 15 public ProductMessageApply Get(int id) 16 { 17 return this.repository.Get(id); 18 } 19 } 20 21 public class ProductSellService : IProductSellService 22 { 23 private IRepository<ProductSell> repository; 24 25 public ProductSellService(IRepository<ProductSell> repository) 26 { 27 this.repository = repository; 28 } 29 30 public void Add(ProductSell entity) 31 { 32 this.repository.Add(entity); 33 } 34 }
8)下單減庫存測試
1 namespace Demo.Reserve.App 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Console.WriteLine(string.Format("{0} 程序已啓動", DateTime.Now.ToString())); 8 9 Send(); 10 Subscribe(); 11 12 Console.ReadKey(); 13 } 14 15 private static void Send() 16 { 17 var unitOfWork = new UnitOfWork(Enums.Reserve); 18 19 try 20 { 21 var productStockRepository = new BaseRepository<ProductStock>(unitOfWork); 22 var productStockServic = new ProductStockService(productStockRepository); 23 var productMessageRepository = new BaseRepository<ProductMessage>(unitOfWork); 24 var productMessageService = new ProductMessageService(productMessageRepository); 25 26 var id = 1; 27 var amount = 2; 28 var productMessage = new ProductMessage() 29 { 30 Product = "ProductCode", 31 Amount = amount, 32 UpdateTime = DateTime.Now 33 }; 34 35 productStockServic.ReduceReserve(id, amount); 36 productMessageService.Add(productMessage); 37 unitOfWork.Commit(); 38 Console.WriteLine(string.Format("{0} 減庫存完成", DateTime.Now.ToString())); 39 Thread.Sleep(1000); 40 41 var message = JsonConvert.SerializeObject(productMessage); 42 RedisConfig.Instrace.Publish("channel.Send", message); 43 Console.WriteLine(string.Format("{0} 發送減庫存消息: {1}", DateTime.Now.ToString(), message)); 44 } 45 catch (Exception ex) 46 { 47 //Logger.Error(ex); 48 unitOfWork.Rollback(); 49 } 50 } 51 52 private static void Subscribe() 53 { 54 var client = RedisConfig.Instrace.NewClient(); 55 var subscriber = client.GetSubscriber(); 56 57 subscriber.Subscribe("channel.Success", (chl, message) => 58 { 59 try 60 { 61 var unitOfWork = new UnitOfWork(Enums.Reserve); 62 var productMessageRepository = new BaseRepository<ProductMessage>(unitOfWork); 63 var productMessageService = new ProductMessageService(productMessageRepository); 64 65 var messageID = message.ToString().ToInt(); 66 if (messageID > 0) 67 { 68 productMessageService.Delete(messageID); 69 Console.WriteLine(string.Format("{0} 收到消費成功消息:{1}", DateTime.Now.ToString(), message)); 70 } 71 } 72 catch (Exception ex) 73 { 74 //Logger.Error(ex); 75 } 76 }); 77 } 78 } 79 }
9)下單成功及消息回發測試
1 namespace Demo.Sell.App 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 Subscribe(); 8 9 Console.WriteLine(string.Format("{0} 程序已啓動", DateTime.Now.ToString())); 10 Console.ReadKey(); 11 } 12 13 private static void Subscribe() 14 { 15 var client = RedisConfig.Instrace.NewClient(); 16 var subscriber = client.GetSubscriber(); 17 18 subscriber.Subscribe("channel.Send", (chl, message) => 19 { 20 Consume(message); 21 }); 22 } 23 24 private static void Consume(string message) 25 { 26 var unitOfWork = new UnitOfWork(Enums.Sell); 27 28 try 29 { 30 Console.WriteLine(string.Format("{0} 收到減庫存消息: {1}", DateTime.Now.ToString(), message)); 31 32 var productMessage = JsonConvert.DeserializeObject<ProductMessage>(message); 33 34 var productSellRepository = new BaseRepository<ProductSell>(unitOfWork); 35 var productSellService = new ProductSellService(productSellRepository); 36 37 var productMessageApplyRepository = new BaseRepository<ProductMessageApply>(unitOfWork); 38 var productMessageApplyService = new ProductMessageApplyService(productMessageApplyRepository); 39 40 var noExists = productMessageApplyService.Get(productMessage.ID) == null; 41 if (noExists) 42 { 43 productSellService.Add(new ProductSell() 44 { 45 Product = productMessage.Product, 46 Amount = productMessage.Amount, 47 Customer = 123 48 }); 49 50 productMessageApplyService.Add(new ProductMessageApply() 51 { 52 MesageID = productMessage.ID, 53 CreateTime = DateTime.Now 54 }); 55 56 unitOfWork.Commit(); 57 Console.WriteLine(string.Format("{0} 消息消費完成", DateTime.Now.ToString())); 58 Thread.Sleep(1000); 59 } 60 61 RedisConfig.Instrace.Publish("channel.Success", productMessage.ID.ToString()); 62 Console.WriteLine(string.Format("{0} 發送消費完成通知:{1}", DateTime.Now.ToString(), productMessage.ID.ToString())); 63 } 64 catch (Exception ex) 65 { 66 //Logger.Error(ex); 67 unitOfWork.Rollback(); 68 } 69 } 70 } 71 }
10)好了,到了最後檢驗成果的時候了
先打開Demo.Sell.App.exe、而後打開Demo.Reserve.App.exe
大功告成!