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

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