Proper locking and another example
[StratumLibrary.git] / StratumLibrary / Stratum.cs
1 \feffusing System;\r
2 using System.Net;\r
3 using System.Threading;\r
4 using System.Net.Sockets;\r
5 using Newtonsoft.Json.Linq;\r
6 \r
7 using System.Text;\r
8 using System.Collections.Generic;\r
9 \r
10 namespace Stratum\r
11 {\r
12     delegate void NotificationCallback(out object[] notificationData);\r
13 \r
14     public class Stratum\r
15     {\r
16         private Socket client;\r
17 \r
18         object responsesLock = new object();\r
19         private Dictionary<string, string> responses = new Dictionary<string, string>();\r
20         ManualResetEvent gotResponse = new ManualResetEvent(false);\r
21 \r
22         /// <summary>\r
23         /// Constructor of Stratum interface class\r
24         /// </summary>\r
25         /// <param name="ipAddress">IPv4 address</param>\r
26         /// <param name="port">Port number</param>\r
27         public Stratum(string ipAddress, int port)\r
28         {\r
29             // End point for the remote device\r
30             IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(ipAddress), port);\r
31 \r
32             // Create TCP socket\r
33             client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);\r
34 \r
35             // Connect done event\r
36             ManualResetEvent connectDone = new ManualResetEvent(false);\r
37 \r
38             Action<IAsyncResult> ConnectCallback = null;\r
39             ConnectCallback = (IAsyncResult ar) =>\r
40             {\r
41                 // Retrieve socket from the state object\r
42                 Socket arClient = (Socket)ar.AsyncState;\r
43 \r
44                 // Complete the connection\r
45                 arClient.EndConnect(ar);\r
46 \r
47                 // Signal that connection has been established\r
48                 connectDone.Set();\r
49             };\r
50 \r
51             // Connect to the remote device\r
52             client.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), client);\r
53 \r
54             // Wait for signal\r
55             connectDone.WaitOne();\r
56 \r
57             // Start receive handler\r
58             Receiver(client);\r
59         }\r
60 \r
61         ~Stratum()\r
62         {\r
63             // Release the socket\r
64             client.Shutdown(SocketShutdown.Both);\r
65             client.Close();\r
66         }\r
67 \r
68         private void Send(Socket client, String data)\r
69         {\r
70             // Send done event\r
71             ManualResetEvent sendDone = new ManualResetEvent(false);\r
72 \r
73             Action<IAsyncResult> SendCallback = null;\r
74             SendCallback = (IAsyncResult ar) =>\r
75             {\r
76                 // Retrieve the socket from the state object\r
77                 Socket arClient = (Socket)ar.AsyncState;\r
78 \r
79                 // Complete sending the data to the remote device\r
80                 int bytesSent = arClient.EndSend(ar);\r
81 \r
82                 // Signal that all bytes have been sent\r
83                 sendDone.Set();\r
84             };\r
85 \r
86             // Convert the string data to byte data using ASCII encoding.\r
87             byte[] byteData = Encoding.ASCII.GetBytes(data);\r
88 \r
89             // Begin sending the data to the remote device.\r
90             client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(SendCallback), client);\r
91 \r
92             // Wait for signal\r
93             sendDone.WaitOne();\r
94         }\r
95 \r
96         /// <summary>\r
97         /// Invoke remote method\r
98         /// </summary>\r
99         /// <typeparam name="T">Return type</typeparam>\r
100         /// <param name="method">Method name</param>\r
101         /// <returns>StratumResponse object</returns>\r
102         public StratumResponse<T> Invoke<T>(string method)\r
103         {\r
104             var req = new StratumRequest()\r
105             {\r
106                 Method = method,\r
107                 Params = new object[] { }\r
108             };\r
109             return Invoke<T>(req);\r
110         }\r
111 \r
112         /// <summary>\r
113         /// Invoke remote method\r
114         /// </summary>\r
115         /// <typeparam name="T">Return type</typeparam>\r
116         /// <param name="method">Method name</param>\r
117         /// <param name="arg">Argument</param>\r
118         /// <returns>StratumResponse object</returns>\r
119         public StratumResponse<T> Invoke<T>(string method, object arg)\r
120         {\r
121             var req = new StratumRequest()\r
122             {\r
123                 Method = method,\r
124                 Params = new object[] { arg }\r
125             };\r
126             return Invoke<T>(req);\r
127         }\r
128 \r
129         /// <summary>\r
130         /// Invoke remote method\r
131         /// </summary>\r
132         /// <typeparam name="T">Return type</typeparam>\r
133         /// <param name="method">Method name</param>\r
134         /// <param name="args">Arguments array</param>\r
135         /// <returns>StratumResponse object</returns>\r
136         public StratumResponse<T> Invoke<T>(string method, object[] args)\r
137         {\r
138             var req = new StratumRequest()\r
139             {\r
140                 Method = method,\r
141                 Params = args\r
142             };\r
143             return Invoke<T>(req);\r
144         }\r
145 \r
146         private StratumResponse<T> Invoke<T>(StratumRequest stratumReq)\r
147         {\r
148             // Serialize stratumReq into JSON string\r
149             var reqJSON = Newtonsoft.Json.JsonConvert.SerializeObject(stratumReq) + '\n';\r
150             var reqId = (string) stratumReq.Id;\r
151 \r
152             string strResponse = "";\r
153             StratumResponse<T> responseObj = null;\r
154 \r
155             // Send JSON data to the remote device.\r
156             Send(client, reqJSON);\r
157 \r
158             // Wait for response\r
159             gotResponse.WaitOne();\r
160 \r
161             lock (responsesLock)\r
162             {\r
163                 // Deserialize the response\r
164                 strResponse = responses[reqId];\r
165                 responses.Remove(reqId);\r
166             }\r
167             responseObj = Newtonsoft.Json.JsonConvert.DeserializeObject<StratumResponse<T>>(strResponse);\r
168 \r
169             // Reset the state\r
170             gotResponse.Reset();\r
171 \r
172             if (responseObj == null)\r
173             {\r
174                 try\r
175                 {\r
176                     JObject jo = Newtonsoft.Json.JsonConvert.DeserializeObject(strResponse) as JObject;\r
177                     throw new Exception(jo["Error"].ToString());\r
178                 }\r
179                 catch (Newtonsoft.Json.JsonSerializationException)\r
180                 {\r
181                     throw new Exception("Inconsistent or empty response");\r
182                 }\r
183             }\r
184 \r
185             return responseObj;\r
186         }\r
187 \r
188         private void Receiver(Socket client)\r
189         {\r
190             // Create the reading state object.\r
191             StratumReadState state = new StratumReadState(client);\r
192 \r
193             Action<IAsyncResult> ReceiveCallback = null;\r
194             ReceiveCallback = (IAsyncResult ar) =>\r
195             {\r
196                 // Retrieve the state object and the client socket \r
197                 // from the asynchronous state object.\r
198                 StratumReadState arStatus = (StratumReadState)ar.AsyncState;\r
199                 Socket arClient = arStatus.workSocket;\r
200 \r
201                 // Read data from the remote device.\r
202                 int bytesRead = arClient.EndReceive(ar);\r
203 \r
204                 if (bytesRead <= 0)\r
205                     return;\r
206 \r
207                 lock (arStatus.sb)\r
208                 {\r
209                     // There might be more data, so store the data received so far.\r
210                     arStatus.sb.Append(Encoding.ASCII.GetString(arStatus.buffer, 0, bytesRead));\r
211 \r
212                     if (arStatus.buffer[bytesRead - 1] == '\n')\r
213                     {\r
214                         string strMessage = arStatus.sb.ToString();\r
215                         arStatus.sb.Clear();\r
216 \r
217                         try\r
218                         {\r
219                             JObject jo = Newtonsoft.Json.JsonConvert.DeserializeObject(strMessage) as JObject;\r
220                             string requestId = (string)jo["id"];\r
221 \r
222                             if (!String.IsNullOrEmpty(requestId))\r
223                             {\r
224                                 lock (responsesLock)\r
225                                 {\r
226                                     responses.Add(requestId, strMessage);\r
227                                 }\r
228 \r
229                                 gotResponse.Set();\r
230                             }\r
231                             else\r
232                             {\r
233                                 // TODO: notifications handling\r
234                                 Console.WriteLine("Notification: {0}", strMessage);\r
235                             }\r
236                         }\r
237                         catch (Newtonsoft.Json.JsonSerializationException e)\r
238                         {\r
239                             // TODO: handle parse error\r
240                         }\r
241                     }\r
242                 }\r
243 \r
244                 arClient.BeginReceive(arStatus.buffer, 0, StratumReadState.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), arStatus);\r
245             };\r
246 \r
247             client.BeginReceive(state.buffer, 0, StratumReadState.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);\r
248         }\r
249     }\r
250 \r
251 }