source: trunk/FACT++/src/fad.cc@ 11380

Last change on this file since 11380 was 11380, checked in by tbretz, 13 years ago
Implemented sending triggers from the ftm to the fad via Dim.
File size: 22.6 KB
Line 
1#include <iostream>
2#include <string>
3#include <boost/asio.hpp>
4#include <boost/bind.hpp>
5#include <boost/lexical_cast.hpp>
6#include <boost/asio/deadline_timer.hpp>
7#include <boost/enable_shared_from_this.hpp>
8
9using boost::lexical_cast;
10
11#include "Time.h"
12#include "Converter.h"
13
14#include "HeadersFAD.h"
15
16#include "dis.hxx"
17#include "Dim.h"
18
19using namespace std;
20using namespace FAD;
21
22namespace ba = boost::asio;
23namespace bs = boost::system;
24namespace dummy = ba::placeholders;
25
26using boost::lexical_cast;
27using ba::ip::tcp;
28
29class tcp_connection;
30
31class Trigger : public DimCommandHandler
32{
33 DimCommand fCmd;
34
35 vector<tcp_connection*> vec;
36
37public:
38 Trigger() : fCmd("FAD/TRIGGER", "C", this)
39 {
40 }
41
42 void Add(tcp_connection *ptr)
43 {
44 vec.push_back(ptr);
45 }
46
47 void Remove(tcp_connection *ptr)
48 {
49 vec.erase(find(vec.begin(), vec.end(), ptr));
50 }
51
52 void commandHandler();
53};
54
55// ------------------------------------------------------------------------
56
57class tcp_connection : public ba::ip::tcp::socket, public boost::enable_shared_from_this<tcp_connection>
58{
59public:
60 static Trigger fTrigger;
61
62 const int fBoardId;
63
64 double fStartTime;
65
66 void AsyncRead(ba::mutable_buffers_1 buffers)
67 {
68 ba::async_read(*this, buffers,
69 boost::bind(&tcp_connection::HandleReceivedData, shared_from_this(),
70 dummy::error, dummy::bytes_transferred));
71 }
72
73 void AsyncWrite(ba::ip::tcp::socket *socket, const ba::const_buffers_1 &buffers)
74 {
75 ba::async_write(*socket, buffers,
76 boost::bind(&tcp_connection::HandleSentData, shared_from_this(),
77 dummy::error, dummy::bytes_transferred));
78 }
79 void AsyncWait(ba::deadline_timer &timer, int seconds,
80 void (tcp_connection::*handler)(const bs::error_code&))// const
81 {
82 timer.expires_from_now(boost::posix_time::milliseconds(seconds));
83 timer.async_wait(boost::bind(handler, shared_from_this(), dummy::error));
84 }
85
86 // The constructor is prvate to force the obtained pointer to be shared
87 tcp_connection(ba::io_service& ioservice, int boardid) : ba::ip::tcp::socket(ioservice),
88 fBoardId(boardid), fRamRoi(kNumChannels), fTriggerSendData(ioservice),
89 fTriggerEnabled(false)
90 {
91 fTrigger.Add(this);
92 }
93 void PostTrigger()
94 {
95 if (fTriggerEnabled)
96 get_io_service().post(boost::bind(&tcp_connection::SendData, this));
97 }
98
99 // Callback when writing was successfull or failed
100 void HandleSentData(const boost::system::error_code& error, size_t bytes_transferred)
101 {
102 cout << "Data sent: (transmitted=" << bytes_transferred << ") rc=" << error.message() << " (" << error << ")" << endl;
103 }
104
105 vector<uint16_t> fBufCommand;
106 vector<uint16_t> fBuffer;
107
108 vector<uint16_t> fCommand;
109
110 FAD::EventHeader fHeader;
111 FAD::EventHeader fRam;
112 FAD::ChannelHeader fChHeader[kNumChannels];
113
114 vector<uint16_t> fRamRoi;
115
116 ba::deadline_timer fTriggerSendData;
117
118 bool fTriggerEnabled;
119 bool fCommandSocket;
120
121 int fSocket;
122
123 void SendData()
124 {
125 fHeader.fPackageLength = sizeof(EventHeader)/2+1;
126 fHeader.fEventCounter++;
127 fHeader.fTriggerId = fHeader.fEventCounter;
128 fHeader.fTimeStamp = uint32_t((Time(Time::utc).UnixTime()-fStartTime)*10000);
129 fHeader.fFreqRefClock = 997+rand()/(RAND_MAX/7);
130
131 for (int i=0; i<FAD::kNumTemp; i++)
132 fHeader.fTempDrs[i] = (42.+fBoardId/40.+float(rand())/RAND_MAX*5)*16;
133
134 fBuffer.resize(0);
135
136 for (int i=0; i<kNumChannels; i++)
137 {
138 fChHeader[i].fStartCell = i*10;
139
140 const vector<uint16_t> buf = fChHeader[i].HtoN();
141
142 fBuffer.insert(fBuffer.end(), buf.begin(), buf.end());
143 fBuffer.insert(fBuffer.end(), fChHeader[i].fRegionOfInterest, 0x42+fHeader.fEventCounter*100);
144
145 fHeader.fPackageLength += sizeof(ChannelHeader)/2;
146 fHeader.fPackageLength += fChHeader[i].fRegionOfInterest;
147 }
148
149 fBuffer.push_back(htons(FAD::kDelimiterEnd));
150
151 const vector<uint16_t> h = fHeader.HtoN();
152
153 fBuffer.insert(fBuffer.begin(), h.begin(), h.end());
154
155 if (fCommandSocket)
156 AsyncWrite(this, ba::buffer(ba::const_buffer(fBuffer.data(), fBuffer.size()*2)));
157 else
158 {
159 if (fSockets.size()==0)
160 return;
161
162 fSocket++;
163 fSocket %= fSockets.size();
164
165 AsyncWrite(fSockets[fSocket].get(), ba::buffer(ba::const_buffer(fBuffer.data(), fBuffer.size()*2)));
166 }
167 }
168
169 void TriggerSendData(const boost::system::error_code &ec)
170 {
171 if (!is_open())
172 {
173 // For example: Here we could schedule a new accept if we
174 // would not want to allow two connections at the same time.
175 return;
176 }
177
178 if (ec==ba::error::basic_errors::operation_aborted)
179 return;
180
181 // Check whether the deadline has passed. We compare the deadline
182 // against the current time since a new asynchronous operation
183 // may have moved the deadline before this actor had a chance
184 // to run.
185 if (fTriggerSendData.expires_at() > ba::deadline_timer::traits_type::now())
186 return;
187
188 // The deadline has passed.
189 if (fTriggerEnabled)
190 SendData();
191
192 AsyncWait(fTriggerSendData, fHeader.fTriggerGeneratorPrescaler, &tcp_connection::TriggerSendData);
193 }
194
195 void HandleReceivedData(const boost::system::error_code& error, size_t bytes_received)
196 {
197 // Do not schedule a new read if the connection failed.
198 if (bytes_received==0)
199 {
200 // Close the connection
201 close();
202 return;
203 }
204
205 // No command received yet
206 if (fCommand.size()==0)
207 {
208 transform(fBufCommand.begin(), fBufCommand.begin()+bytes_received/2,
209 fBufCommand.begin(), ntohs);
210
211 switch (fBufCommand[0])
212 {
213 case kCmdDrsEnable:
214 case kCmdDrsEnable+0x100:
215 fHeader.Enable(FAD::EventHeader::kDenable, fBufCommand[0]==kCmdDrsEnable);
216 cout << "-> DrsEnable " << fBoardId << " " << (fBufCommand[0]==kCmdDrsEnable) << endl;
217 break;
218
219 case kCmdDwrite:
220 case kCmdDwrite+0x100:
221 fHeader.Enable(FAD::EventHeader::kDwrite, fBufCommand[0]==kCmdDwrite);
222 cout << "-> Dwrite " << fBoardId << " " << (fBufCommand[0]==kCmdDwrite) << endl;
223 break;
224
225 case kCmdTriggerLine:
226 case kCmdTriggerLine+0x100:
227 cout << "-> Trigger line " << fBoardId << " " << (fBufCommand[0]==kCmdTriggerLine) << endl;
228 fTriggerEnabled = fBufCommand[0]==kCmdTriggerLine;
229 fHeader.Enable(FAD::EventHeader::kTriggerLine, fTriggerEnabled);
230 break;
231
232 case kCmdSclk:
233 case kCmdSclk+0x100:
234 cout << "-> Sclk " << fBoardId << endl;
235 fHeader.Enable(FAD::EventHeader::kSpiSclk, fBufCommand[0]==kCmdSclk);
236 break;
237
238 case kCmdSrclk:
239 case kCmdSrclk+0x100:
240 cout << "-> Drclk " << fBoardId << endl;
241 break;
242
243 case kCmdRun:
244 case kCmdRun+0x100:
245 fStartTime = Time(Time::utc).UnixTime();
246 cout << "-> Run " << fBoardId << endl;
247 break;
248
249 case kCmdBusy:
250 case kCmdBusy+0x100:
251 cout << "-> Busy " << fBoardId << " " << (fBufCommand[0]==kCmdBusy) << endl;
252 fHeader.Enable(FAD::EventHeader::kBusy, fBufCommand[0]==kCmdBusy);
253 break;
254
255 case kCmdSocket:
256 case kCmdSocket+0x100:
257 cout << "-> Socket " << fBoardId << " " << (fBufCommand[0]==kCmdSocket) << endl;
258 fCommandSocket = fBufCommand[0]==kCmdSocket;
259 fHeader.Enable(FAD::EventHeader::kSock17, !fCommandSocket);
260 break;
261
262 case kCmdContTrigger:
263 case kCmdContTrigger+0x100:
264 if (fBufCommand[0]==kCmdContTrigger)
265 AsyncWait(fTriggerSendData, 0, &tcp_connection::TriggerSendData);
266 else
267 fTriggerSendData.cancel();
268 fHeader.Enable(FAD::EventHeader::kContTrigger, fBufCommand[0]==kCmdContTrigger);
269 cout << "-> ContTrig " << fBoardId << " " << (fBufCommand[0]==kCmdContTrigger) << endl;
270 break;
271
272 case kCmdResetEventCounter:
273 cout << "-> ResetId " << fBoardId << endl;
274 fHeader.fEventCounter = 0;
275 break;
276
277 case kCmdSingleTrigger:
278 cout << "-> Trigger " << fBoardId << endl;
279 SendData();
280 break;
281
282 case kCmdWriteExecute:
283 cout << "-> Execute " << fBoardId << endl;
284 memcpy(fHeader.fDac, fRam.fDac, sizeof(fHeader.fDac));
285 for (int i=0; i<kNumChannels; i++)
286 fChHeader[i].fRegionOfInterest = fRamRoi[i];
287 fHeader.fRunNumber = fRam.fRunNumber;
288 break;
289
290 case kCmdWriteRunNumberMSW:
291 fCommand = fBufCommand;
292 break;
293
294 case kCmdWriteRunNumberLSW:
295 fCommand = fBufCommand;
296 break;
297
298 default:
299 if (fBufCommand[0]>=kCmdWriteRoi && fBufCommand[0]<kCmdWriteRoi+kNumChannels)
300 {
301 fCommand.resize(2);
302 fCommand[0] = kCmdWriteRoi;
303 fCommand[1] = fBufCommand[0]-kCmdWriteRoi;
304 break;
305 }
306 if (fBufCommand[0]>= kCmdWriteDac && fBufCommand[0]<kCmdWriteDac+kNumDac)
307 {
308 fCommand.resize(2);
309 fCommand[0] = kCmdWriteDac;
310 fCommand[1] = fBufCommand[0]-kCmdWriteDac;
311 break;
312 }
313 if (fBufCommand[0]==kCmdWriteRate)
314 {
315 fCommand.resize(1);
316 fCommand[0] = kCmdWriteRate;
317 break;
318 }
319
320 cout << "Received b=" << bytes_received << ": " << error.message() << " (" << error << ")" << endl;
321 cout << "Hex:" << Converter::GetHex<uint16_t>(&fBufCommand[0], bytes_received) << endl;
322 return;
323 }
324
325 fBufCommand.resize(1);
326 AsyncRead(ba::buffer(fBufCommand));
327 return;
328 }
329
330 transform(fBufCommand.begin(), fBufCommand.begin()+bytes_received/2,
331 fBufCommand.begin(), ntohs);
332
333 switch (fCommand[0])
334 {
335 case kCmdWriteRunNumberMSW:
336 fRam.fRunNumber &= 0xffff;
337 fRam.fRunNumber |= fBufCommand[0]<<16;
338 cout << "-> Set RunNumber " << fBoardId << " MSW" << endl;
339 break;
340 case kCmdWriteRunNumberLSW:
341 fRam.fRunNumber &= 0xffff0000;
342 fRam.fRunNumber |= fBufCommand[0];
343 cout << "-> Set RunNumber " << fBoardId << " LSW" << endl;
344 break;
345 case kCmdWriteRoi:
346 cout << "-> Set " << fBoardId << " Roi[" << fCommand[1] << "]=" << fBufCommand[0] << endl;
347 //fChHeader[fCommand[1]].fRegionOfInterest = fBufCommand[0];
348 fRamRoi[fCommand[1]] = fBufCommand[0];
349 break;
350
351 case kCmdWriteDac:
352 cout << "-> Set " << fBoardId << " Dac[" << fCommand[1] << "]=" << fBufCommand[0] << endl;
353 fRam.fDac[fCommand[1]] = fBufCommand[0];
354 break;
355
356 case kCmdWriteRate:
357 cout << "-> Set " << fBoardId << " Rate =" << fBufCommand[0] << endl;
358 fHeader.fTriggerGeneratorPrescaler = fBufCommand[0];
359 break;
360 }
361
362 fCommand.resize(0);
363
364 fBufCommand.resize(1);
365 AsyncRead(ba::buffer(fBufCommand));
366 }
367
368public:
369 typedef boost::shared_ptr<tcp_connection> shared_ptr;
370
371 static shared_ptr create(ba::io_service& io_service, int boardid)
372 {
373 return shared_ptr(new tcp_connection(io_service, boardid));
374 }
375
376 void start()
377 {
378 // Ownership of buffer must be valid until Handler is called.
379
380 fTriggerEnabled=false;
381 fCommandSocket=true;
382
383 fHeader.fStartDelimiter = FAD::kDelimiterStart;
384 fHeader.fVersion = 0x104;
385 fHeader.fBoardId = (fBoardId%10) | ((fBoardId/10)<<8);
386 fHeader.fRunNumber = 0;
387 fHeader.fDNA = reinterpret_cast<uint64_t>(this);
388 fHeader.fTriggerGeneratorPrescaler = 100;
389 fHeader.fStatus = 0xf<<12 |
390 FAD::EventHeader::kDenable |
391 FAD::EventHeader::kDwrite |
392 FAD::EventHeader::kDcmLocked |
393 FAD::EventHeader::kDcmReady |
394 FAD::EventHeader::kSpiSclk;
395
396
397 fStartTime = Time(Time::utc).UnixTime();
398
399 for (int i=0; i<kNumChannels; i++)
400 {
401 fChHeader[i].fId = (i%9) | ((i/9)<<4);
402 fChHeader[i].fRegionOfInterest = 0;
403 }
404
405 // Emit something to be written to the socket
406 fBufCommand.resize(1);
407 AsyncRead(ba::buffer(fBufCommand));
408
409// AsyncWait(fTriggerDynData, 1, &tcp_connection::SendDynData);
410
411// AsyncWrite(ba::buffer(ba::const_buffer(&fHeader, sizeof(FTM::Header))));
412// AsyncWait(deadline_, 3, &tcp_connection::check_deadline);
413
414 }
415
416 vector<boost::shared_ptr<ba::ip::tcp::socket>> fSockets;
417
418 ~tcp_connection()
419 {
420 fTrigger.Remove(this);
421 fSockets.clear();
422 }
423
424 void handle_accept(boost::shared_ptr<ba::ip::tcp::socket> socket, int port, const boost::system::error_code&/* error*/)
425 {
426 cout << this << " Added one socket[" << fBoardId << "] " << socket->remote_endpoint().address().to_v4().to_string();
427 cout << ":"<< port << endl;
428 fSockets.push_back(socket);
429 }
430};
431
432Trigger tcp_connection::fTrigger;
433
434void Trigger::commandHandler()
435{
436 for (vector<tcp_connection*>::iterator it=vec.begin();
437 it!=vec.end(); it++)
438 {
439 (*it)->PostTrigger();
440 }
441}
442
443
444class tcp_server
445{
446 tcp::acceptor acc0;
447 tcp::acceptor acc1;
448 tcp::acceptor acc2;
449 tcp::acceptor acc3;
450 tcp::acceptor acc4;
451 tcp::acceptor acc5;
452 tcp::acceptor acc6;
453 tcp::acceptor acc7;
454
455 int fBoardId;
456
457public:
458 tcp_server(ba::io_service& ioservice, int port, int board) :
459 acc0(ioservice, tcp::endpoint(tcp::v4(), port)),
460 acc1(ioservice, tcp::endpoint(tcp::v4(), port+1)),
461 acc2(ioservice, tcp::endpoint(tcp::v4(), port+2)),
462 acc3(ioservice, tcp::endpoint(tcp::v4(), port+3)),
463 acc4(ioservice, tcp::endpoint(tcp::v4(), port+4)),
464 acc5(ioservice, tcp::endpoint(tcp::v4(), port+5)),
465 acc6(ioservice, tcp::endpoint(tcp::v4(), port+6)),
466 acc7(ioservice, tcp::endpoint(tcp::v4(), port+7)),
467 fBoardId(board)
468 {
469 // We could start listening for more than one connection
470 // here, but since there is only one handler executed each time
471 // it would not make sense. Before one handle_accept is not
472 // finished no new handle_accept will be called.
473 // Workround: Start a new thread in handle_accept
474 start_accept();
475 }
476
477private:
478 void start_accept(tcp_connection::shared_ptr dest, tcp::acceptor &acc)
479 {
480 boost::shared_ptr<ba::ip::tcp::socket> connection =
481 boost::shared_ptr<ba::ip::tcp::socket>(new ba::ip::tcp::socket(acc.io_service()));
482
483 acc.async_accept(*connection,
484 boost::bind(&tcp_connection::handle_accept,
485 dest, connection,
486 acc.local_endpoint().port(),
487 ba::placeholders::error));
488 }
489
490 void start_accept()
491 {
492 cout << "Start accept[" << fBoardId << "] " << acc0.local_endpoint().port() << "..." << flush;
493 tcp_connection::shared_ptr new_connection = tcp_connection::create(/*acceptor_.*/acc0.io_service(), fBoardId);
494
495 cout << new_connection.get() << " ";
496
497 // This will accept a connection without blocking
498 acc0.async_accept(*new_connection,
499 boost::bind(&tcp_server::handle_accept,
500 this,
501 new_connection,
502 ba::placeholders::error));
503
504 start_accept(new_connection, acc1);
505 start_accept(new_connection, acc2);
506 start_accept(new_connection, acc3);
507 start_accept(new_connection, acc4);
508 start_accept(new_connection, acc5);
509 start_accept(new_connection, acc6);
510 start_accept(new_connection, acc7);
511
512 cout << "start-done." << endl;
513 }
514
515 void handle_accept(tcp_connection::shared_ptr new_connection, const boost::system::error_code& error)
516 {
517 // The connection has been accepted and is now ready to use
518
519 // not installing a new handler will stop run()
520 cout << new_connection.get() << " Handle accept[" << fBoardId << "]["<<new_connection->fBoardId<<"]..." << flush;
521 if (!error)
522 {
523 new_connection->start();
524
525 // The is now an open connection/server (tcp_connection)
526 // we immediatly schedule another connection
527 // This allowed two client-connection at the same time
528 start_accept();
529 }
530 cout << "handle-done." << endl;
531 }
532};
533
534#include "Configuration.h"
535
536void SetupConfiguration(::Configuration &conf)
537{
538 const string n = conf.GetName()+".log";
539
540 po::options_description config("Program options");
541 config.add_options()
542 ("dns", var<string>("localhost"), "Dim nameserver host name (Overwites DIM_DNS_NODE environment variable)")
543 ("port,p", var<uint16_t>(4000), "")
544 ("num,n", var<uint16_t>(40), "")
545 ;
546
547 po::positional_options_description p;
548 p.add("port", 1); // The first positional options
549 p.add("num", 1); // The second positional options
550
551 conf.AddEnv("dns", "DIM_DNS_NODE");
552
553 conf.AddOptions(config);
554 conf.SetArgumentPositions(p);
555}
556
557int main(int argc, const char **argv)
558{
559 ::Configuration conf(argv[0]);
560
561 SetupConfiguration(conf);
562
563 po::variables_map vm;
564 try
565 {
566 vm = conf.Parse(argc, argv);
567 }
568#if BOOST_VERSION > 104000
569 catch (po::multiple_occurrences &e)
570 {
571 cerr << "Program options invalid due to: " << e.what() << " of '" << e.get_option_name() << "'." << endl;
572 return -1;
573 }
574#endif
575 catch (exception& e)
576 {
577 cerr << "Program options invalid due to: " << e.what() << endl;
578 return -1;
579 }
580
581 if (conf.HasVersion() || conf.HasPrint() || conf.HasHelp())
582 return -1;
583
584 Dim::Setup(conf.Get<string>("dns"));
585
586 DimServer::start("FAD");
587
588 //try
589 {
590 ba::io_service io_service;
591
592 const uint16_t n = conf.Get<uint16_t>("num");
593 uint16_t port = conf.Get<uint16_t>("port");
594
595 vector<shared_ptr<tcp_server>> servers;
596
597 for (int i=0; i<n; i++)
598 {
599 shared_ptr<tcp_server> server(new tcp_server(io_service, port, i));
600 servers.push_back(server);
601
602 port += 8;
603 }
604
605 // ba::add_service(io_service, &server);
606 // server.add_service(...);
607 //cout << "Run..." << flush;
608
609 // Calling run() from a single thread ensures no concurrent access
610 // of the handler which are called!!!
611 io_service.run();
612
613 //cout << "end." << endl;
614 }
615 /*catch (std::exception& e)
616 {
617 std::cerr << e.what() << std::endl;
618 }*/
619
620 return 0;
621}
622/* ====================== Buffers ===========================
623
624char d1[128]; ba::buffer(d1));
625std::vector<char> d2(128); ba::buffer(d2);
626boost::array<char, 128> d3; by::buffer(d3);
627
628// --------------------------------
629char d1[128];
630std::vector<char> d2(128);
631boost::array<char, 128> d3;
632
633boost::array<mutable_buffer, 3> bufs1 = {
634 ba::buffer(d1),
635 ba::buffer(d2),
636 ba::buffer(d3) };
637sock.read(bufs1);
638
639std::vector<const_buffer> bufs2;
640bufs2.push_back(boost::asio::buffer(d1));
641bufs2.push_back(boost::asio::buffer(d2));
642bufs2.push_back(boost::asio::buffer(d3));
643sock.write(bufs2);
644
645
646// ======================= Read functions =========================
647
648ba::async_read_until --> delimiter
649
650streambuf buf; // Ensure validity until handler!
651by::async_read(s, buf, ....);
652
653ba::async_read(s, ba:buffer(data, size), handler);
654 // Single buffer
655 boost::asio::async_read(s,
656 ba::buffer(data, size),
657 compl-func --> ba::transfer_at_least(32),
658 handler);
659
660 // Multiple buffers
661boost::asio::async_read(s, buffers,
662 compl-func --> boost::asio::transfer_all(),
663 handler);
664 */
665
666// ================= Others ===============================
667
668 /*
669 strand Provides serialised handler execution.
670 work Class to inform the io_service when it has work to do.
671
672
673io_service::
674dispatch Request the io_service to invoke the given handler.
675poll Run the io_service's event processing loop to execute ready
676 handlers.
677poll_one Run the io_service's event processing loop to execute one ready
678 handler.
679post Request the io_service to invoke the given handler and return
680 immediately.
681reset Reset the io_service in preparation for a subsequent run()
682 invocation.
683run Run the io_service's event processing loop.
684run_one Run the io_service's event processing loop to execute at most
685 one handler.
686stop Stop the io_service's event processing loop.
687wrap Create a new handler that automatically dispatches the wrapped
688 handler on the io_service.
689
690strand:: The io_service::strand class provides the ability to
691 post and dispatch handlers with the guarantee that none
692 of those handlers will execute concurrently.
693
694dispatch Request the strand to invoke the given handler.
695get_io_service Get the io_service associated with the strand.
696post Request the strand to invoke the given handler and return
697 immediately.
698wrap Create a new handler that automatically dispatches the
699 wrapped handler on the strand.
700
701work:: The work class is used to inform the io_service when
702 work starts and finishes. This ensures that the io_service's run() function will not exit while work is underway, and that it does exit when there is no unfinished work remaining.
703get_io_service Get the io_service associated with the work.
704work Constructor notifies the io_service that work is starting.
705
706*/
707
708
Note: See TracBrowser for help on using the repository browser.