eventmachine 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/ext/cmain.cpp +27 -5
- data/ext/ed.cpp +298 -49
- data/ext/ed.h +97 -52
- data/ext/em.cpp +68 -9
- data/ext/em.h +2 -1
- data/ext/eventmachine.h +4 -1
- data/ext/project.h +1 -1
- data/ext/rubymain.cpp +25 -1
- data/lib/eventmachine.rb +95 -1
- metadata +2 -2
data/ext/cmain.cpp
CHANGED
@@ -51,10 +51,10 @@ evma_release_library
|
|
51
51
|
|
52
52
|
extern "C" void evma_release_library()
|
53
53
|
{
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
if (!EventMachine)
|
55
|
+
throw std::runtime_error ("not initialized");
|
56
|
+
delete EventMachine;
|
57
|
+
EventMachine = NULL;
|
58
58
|
}
|
59
59
|
|
60
60
|
|
@@ -105,6 +105,18 @@ extern "C" const char *evma_create_tcp_server (const char *address, int port)
|
|
105
105
|
return EventMachine->CreateTcpServer (address, port);
|
106
106
|
}
|
107
107
|
|
108
|
+
/*************************
|
109
|
+
evma_open_datagram_socket
|
110
|
+
*************************/
|
111
|
+
|
112
|
+
extern "C" const char *evma_open_datagram_socket (const char *address, int port)
|
113
|
+
{
|
114
|
+
if (!EventMachine)
|
115
|
+
throw std::runtime_error ("not initialized");
|
116
|
+
return EventMachine->OpenDatagramSocket (address, port);
|
117
|
+
}
|
118
|
+
|
119
|
+
|
108
120
|
|
109
121
|
/****************************
|
110
122
|
evma_send_data_to_connection
|
@@ -117,6 +129,17 @@ extern "C" int evma_send_data_to_connection (const char *binding, const char *da
|
|
117
129
|
return ConnectionDescriptor::SendDataToConnection (binding, data, data_length);
|
118
130
|
}
|
119
131
|
|
132
|
+
/******************
|
133
|
+
evma_send_datagram
|
134
|
+
******************/
|
135
|
+
|
136
|
+
extern "C" int evma_send_datagram (const char *binding, const char *data, int data_length, const char *address, int port)
|
137
|
+
{
|
138
|
+
if (!EventMachine)
|
139
|
+
throw std::runtime_error ("not initialized");
|
140
|
+
return DatagramDescriptor::SendDatagram (binding, data, data_length, address, port);
|
141
|
+
}
|
142
|
+
|
120
143
|
|
121
144
|
/*********************
|
122
145
|
evma_close_connection
|
@@ -143,4 +166,3 @@ extern "C" void evma_stop_machine()
|
|
143
166
|
|
144
167
|
|
145
168
|
|
146
|
-
|
data/ext/ed.cpp
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*****************************************************************************
|
2
2
|
|
3
|
-
$Id: ed.cpp
|
3
|
+
$Id: ed.cpp 2363 2006-04-23 14:32:40Z francis $
|
4
4
|
|
5
5
|
File: ed.cpp
|
6
6
|
Date: 06Apr06
|
@@ -122,6 +122,19 @@ bool EventableDescriptor::ShouldDelete()
|
|
122
122
|
}
|
123
123
|
|
124
124
|
|
125
|
+
/**********************************
|
126
|
+
EventableDescriptor::ScheduleClose
|
127
|
+
**********************************/
|
128
|
+
|
129
|
+
void EventableDescriptor::ScheduleClose (bool after_writing)
|
130
|
+
{
|
131
|
+
if (after_writing)
|
132
|
+
bCloseAfterWriting = true;
|
133
|
+
else
|
134
|
+
bCloseNow = true;
|
135
|
+
}
|
136
|
+
|
137
|
+
|
125
138
|
/******************************************
|
126
139
|
ConnectionDescriptor::ConnectionDescriptor
|
127
140
|
******************************************/
|
@@ -152,8 +165,14 @@ STATIC: ConnectionDescriptor::SendDataToConnection
|
|
152
165
|
|
153
166
|
int ConnectionDescriptor::SendDataToConnection (const char *binding, const char *data, int data_length)
|
154
167
|
{
|
155
|
-
|
156
|
-
|
168
|
+
// TODO: This is something of a hack, or at least it's a static method of the wrong class.
|
169
|
+
ConnectionDescriptor *cd = dynamic_cast <ConnectionDescriptor*> (Bindable_t::GetObject (binding));
|
170
|
+
if (cd)
|
171
|
+
return cd->SendOutboundData (data, data_length);
|
172
|
+
DatagramDescriptor *ds = dynamic_cast <DatagramDescriptor*> (Bindable_t::GetObject (binding));
|
173
|
+
if (ds)
|
174
|
+
return ds->SendOutboundData (data, data_length);
|
175
|
+
return -1;
|
157
176
|
}
|
158
177
|
|
159
178
|
|
@@ -163,13 +182,10 @@ STATIC: ConnectionDescriptor::CloseConnection
|
|
163
182
|
|
164
183
|
void ConnectionDescriptor::CloseConnection (const char *binding, bool after_writing)
|
165
184
|
{
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
else
|
171
|
-
cd->bCloseNow = true;
|
172
|
-
}
|
185
|
+
// TODO: This is something of a hack, or at least it's a static method of the wrong class.
|
186
|
+
EventableDescriptor *ed = dynamic_cast <EventableDescriptor*> (Bindable_t::GetObject (binding));
|
187
|
+
if (ed)
|
188
|
+
ed->ScheduleClose (after_writing);
|
173
189
|
}
|
174
190
|
|
175
191
|
|
@@ -180,23 +196,24 @@ ConnectionDescriptor::SendOutboundData
|
|
180
196
|
|
181
197
|
int ConnectionDescriptor::SendOutboundData (const char *data, int length)
|
182
198
|
{
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
199
|
+
// Highly naive and incomplete implementation.
|
200
|
+
// There's no throttle for runaways (which should abort only this connection
|
201
|
+
// and not the whole process), and no coalescing of small pages.
|
202
|
+
// (Well, not so bad, small pages are coalesced in ::Write)
|
203
|
+
|
204
|
+
if (bCloseNow || bCloseAfterWriting)
|
205
|
+
return 0;
|
206
|
+
|
207
|
+
if (!data && (length > 0))
|
208
|
+
throw std::runtime_error ("bad outbound data");
|
209
|
+
char *buffer = (char *) malloc (length + 1);
|
210
|
+
if (!buffer)
|
211
|
+
throw std::runtime_error ("no allocation for outbound data");
|
212
|
+
memcpy (buffer, data, length);
|
213
|
+
buffer [length] = 0;
|
214
|
+
OutboundPages.push_back (OutboundPage (buffer, length));
|
215
|
+
OutboundDataSize += length;
|
216
|
+
return length;
|
200
217
|
}
|
201
218
|
|
202
219
|
|
@@ -276,7 +293,12 @@ void ConnectionDescriptor::Read()
|
|
276
293
|
total_bytes_read += r;
|
277
294
|
LastRead = gCurrentLoopTime;
|
278
295
|
|
279
|
-
|
296
|
+
// Add a null-terminator at the the end of the buffer
|
297
|
+
// that we will send to the callback.
|
298
|
+
// DO NOT CHANGE THIS. We want to explicitly allow users
|
299
|
+
// to be able to depend on this behavior, so they will have
|
300
|
+
// the option to do some things faster.
|
301
|
+
readbuffer [r] = 0;
|
280
302
|
|
281
303
|
if (EventCallback)
|
282
304
|
(*EventCallback)(GetBinding().c_str(), EventMachine_t::CONNECTION_READ, readbuffer, r);
|
@@ -465,37 +487,37 @@ void AcceptorDescriptor::Read()
|
|
465
487
|
for (int i=0; i < 10; i++) {
|
466
488
|
int sd = accept (GetSocket(), (struct sockaddr*)&pin, &addrlen);
|
467
489
|
if (sd == -1) {
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
490
|
+
// This breaks the loop when we've accepted everything on the kernel queue,
|
491
|
+
// up to 10 new connections. But what if the *first* accept fails?
|
492
|
+
// Does that mean anything serious is happening, beyond the situation
|
493
|
+
// described in the note above?
|
472
494
|
break;
|
473
495
|
}
|
474
496
|
|
475
|
-
|
476
|
-
|
477
|
-
|
497
|
+
// Set the newly-accepted socket non-blocking.
|
498
|
+
// On Windows, this may fail because, weirdly, Windows inherits the non-blocking
|
499
|
+
// attribute that we applied to the acceptor socket into the accepted one.
|
478
500
|
int val = fcntl (sd, F_GETFL, 0);
|
479
501
|
if (fcntl (sd, F_SETFL, val | O_NONBLOCK) == -1) {
|
480
|
-
|
481
|
-
|
482
|
-
|
502
|
+
shutdown (sd, 1);
|
503
|
+
close (sd);
|
504
|
+
continue;
|
483
505
|
}
|
484
506
|
|
485
507
|
|
486
|
-
|
487
|
-
|
488
|
-
|
508
|
+
// Disable slow-start (Nagle algorithm). Eventually make this configurable.
|
509
|
+
int one = 1;
|
510
|
+
setsockopt (sd, IPPROTO_TCP, TCP_NODELAY, (const char*) &one, sizeof(one));
|
489
511
|
|
490
512
|
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
513
|
+
ConnectionDescriptor *cd = new ConnectionDescriptor (sd);
|
514
|
+
if (!cd)
|
515
|
+
throw std::runtime_error ("no newly accepted connection");
|
516
|
+
if (EventCallback) {
|
517
|
+
(*EventCallback) (GetBinding().c_str(), EventMachine_t::CONNECTION_ACCEPTED, cd->GetBinding().c_str(), cd->GetBinding().size());
|
518
|
+
}
|
519
|
+
assert (MyEventMachine);
|
520
|
+
MyEventMachine->Add (cd);
|
499
521
|
}
|
500
522
|
|
501
523
|
}
|
@@ -524,5 +546,232 @@ void AcceptorDescriptor::Heartbeat()
|
|
524
546
|
|
525
547
|
|
526
548
|
|
549
|
+
/**************************************
|
550
|
+
DatagramDescriptor::DatagramDescriptor
|
551
|
+
**************************************/
|
552
|
+
|
553
|
+
DatagramDescriptor::DatagramDescriptor (int sd):
|
554
|
+
EventableDescriptor (sd)
|
555
|
+
{
|
556
|
+
}
|
557
|
+
|
558
|
+
|
559
|
+
/***************************************
|
560
|
+
DatagramDescriptor::~DatagramDescriptor
|
561
|
+
***************************************/
|
562
|
+
|
563
|
+
DatagramDescriptor::~DatagramDescriptor()
|
564
|
+
{
|
565
|
+
// Run down any stranded outbound data.
|
566
|
+
for (size_t i=0; i < OutboundPages.size(); i++)
|
567
|
+
OutboundPages[i].Free();
|
568
|
+
}
|
569
|
+
|
570
|
+
|
571
|
+
/************************
|
572
|
+
DatagramDescriptor::Read
|
573
|
+
************************/
|
574
|
+
|
575
|
+
void DatagramDescriptor::Read()
|
576
|
+
{
|
577
|
+
int sd = GetSocket();
|
578
|
+
assert (sd != -1);
|
579
|
+
|
580
|
+
// This is an extremely large read buffer.
|
581
|
+
// In many cases you wouldn't expect to get any more than 4K.
|
582
|
+
char readbuffer [16 * 1024];
|
583
|
+
|
584
|
+
for (int i=0; i < 10; i++) {
|
585
|
+
// Don't read just one buffer and then move on. This is faster
|
586
|
+
// if there is a lot of incoming.
|
587
|
+
// But don't read indefinitely. Give other sockets a chance to run.
|
588
|
+
|
589
|
+
struct sockaddr_in sin;
|
590
|
+
socklen_t slen = sizeof (sin);
|
591
|
+
bzero (&sin, slen);
|
592
|
+
|
593
|
+
int r = recvfrom (sd, readbuffer, sizeof(readbuffer) - 1, 0, (struct sockaddr*)&sin, &slen);
|
594
|
+
//cerr << "<R:" << r << ">";
|
595
|
+
|
596
|
+
// In UDP, a zero-length packet is perfectly legal.
|
597
|
+
if (r >= 0) {
|
598
|
+
LastRead = gCurrentLoopTime;
|
599
|
+
|
600
|
+
// Add a null-terminator at the the end of the buffer
|
601
|
+
// that we will send to the callback.
|
602
|
+
// DO NOT CHANGE THIS. We want to explicitly allow users
|
603
|
+
// to be able to depend on this behavior, so they will have
|
604
|
+
// the option to do some things faster.
|
605
|
+
readbuffer [r] = 0;
|
606
|
+
|
607
|
+
|
608
|
+
// Set up a "temporary" return address so that callers can "reply" to us
|
609
|
+
// from within the callback we are about to invoke. That means that ordinary
|
610
|
+
// calls to "send_data_to_connection" (which is of course misnamed in this
|
611
|
+
// case) will result in packets being sent back to the same place that sent
|
612
|
+
// us this one.
|
613
|
+
// There is a different call (evma_send_datagram) for cases where the caller
|
614
|
+
// actually wants to send a packet somewhere else.
|
615
|
+
|
616
|
+
bzero (&ReturnAddress, sizeof(ReturnAddress));
|
617
|
+
memcpy (&ReturnAddress, &sin, slen);
|
618
|
+
|
619
|
+
if (EventCallback)
|
620
|
+
(*EventCallback)(GetBinding().c_str(), EventMachine_t::CONNECTION_READ, readbuffer, r);
|
621
|
+
|
622
|
+
}
|
623
|
+
else {
|
624
|
+
// Basically a would-block, meaning we've everything there is to read.
|
625
|
+
break;
|
626
|
+
}
|
627
|
+
|
628
|
+
}
|
629
|
+
|
630
|
+
|
631
|
+
}
|
632
|
+
|
633
|
+
|
634
|
+
/*************************
|
635
|
+
DatagramDescriptor::Write
|
636
|
+
*************************/
|
637
|
+
|
638
|
+
void DatagramDescriptor::Write()
|
639
|
+
{
|
640
|
+
/* It's possible for a socket to select writable and then no longer
|
641
|
+
* be writable by the time we get around to writing. The kernel might
|
642
|
+
* have used up its available output buffers between the select call
|
643
|
+
* and when we get here. So this condition is not an error.
|
644
|
+
* This code is very reminiscent of ConnectionDescriptor::_WriteOutboundData,
|
645
|
+
* but differs in the that the outbound data pages (received from the
|
646
|
+
* user) are _message-structured._ That is, we send each of them out
|
647
|
+
* one message at a time.
|
648
|
+
* TODO, we are currently suppressing the EMSGSIZE error!!!
|
649
|
+
*/
|
650
|
+
|
651
|
+
int sd = GetSocket();
|
652
|
+
assert (sd != -1);
|
653
|
+
|
654
|
+
assert (OutboundPages.size() > 0);
|
655
|
+
|
656
|
+
// Send out up to 10 packets, then cycle the machine.
|
657
|
+
for (int i = 0; i < 10; i++) {
|
658
|
+
if (OutboundPages.size() <= 0)
|
659
|
+
break;
|
660
|
+
OutboundPage *op = &(OutboundPages[0]);
|
661
|
+
|
662
|
+
int s = sendto (sd, op->Buffer, op->Length, 0, (struct sockaddr*)&(op->From), sizeof(op->From));
|
663
|
+
int e = errno;
|
664
|
+
|
665
|
+
OutboundDataSize -= op->Length;
|
666
|
+
op->Free();
|
667
|
+
OutboundPages.pop_front();
|
668
|
+
|
669
|
+
if (s == -1) {
|
670
|
+
if ((e != EINPROGRESS) && (e != EWOULDBLOCK) && (e != EINTR)) {
|
671
|
+
Close();
|
672
|
+
break;
|
673
|
+
}
|
674
|
+
}
|
675
|
+
}
|
676
|
+
}
|
677
|
+
|
678
|
+
|
679
|
+
/**********************************
|
680
|
+
DatagramDescriptor::SelectForWrite
|
681
|
+
**********************************/
|
682
|
+
|
683
|
+
bool DatagramDescriptor::SelectForWrite()
|
684
|
+
{
|
685
|
+
return (GetOutboundDataSize() > 0);
|
686
|
+
}
|
687
|
+
|
688
|
+
|
689
|
+
/************************************
|
690
|
+
DatagramDescriptor::SendOutboundData
|
691
|
+
************************************/
|
692
|
+
|
693
|
+
int DatagramDescriptor::SendOutboundData (const char *data, int length)
|
694
|
+
{
|
695
|
+
// This is an exact clone of ConnectionDescriptor::SendOutboundData.
|
696
|
+
// That means it needs to move to a common ancestor.
|
697
|
+
|
698
|
+
if (bCloseNow || bCloseAfterWriting)
|
699
|
+
return 0;
|
700
|
+
|
701
|
+
if (!data && (length > 0))
|
702
|
+
throw std::runtime_error ("bad outbound data");
|
703
|
+
char *buffer = (char *) malloc (length + 1);
|
704
|
+
if (!buffer)
|
705
|
+
throw std::runtime_error ("no allocation for outbound data");
|
706
|
+
memcpy (buffer, data, length);
|
707
|
+
buffer [length] = 0;
|
708
|
+
OutboundPages.push_back (OutboundPage (buffer, length, ReturnAddress));
|
709
|
+
OutboundDataSize += length;
|
710
|
+
return length;
|
711
|
+
}
|
712
|
+
|
713
|
+
|
714
|
+
/****************************************
|
715
|
+
DatagramDescriptor::SendOutboundDatagram
|
716
|
+
****************************************/
|
717
|
+
|
718
|
+
int DatagramDescriptor::SendOutboundDatagram (const char *data, int length, const char *address, int port)
|
719
|
+
{
|
720
|
+
// This is an exact clone of ConnectionDescriptor::SendOutboundData.
|
721
|
+
// That means it needs to move to a common ancestor.
|
722
|
+
// TODO: Refactor this so there's no overlap with SendOutboundData.
|
723
|
+
|
724
|
+
if (bCloseNow || bCloseAfterWriting)
|
725
|
+
return 0;
|
726
|
+
|
727
|
+
if (!address || !*address || !port)
|
728
|
+
return 0;
|
729
|
+
|
730
|
+
sockaddr_in pin;
|
731
|
+
unsigned long HostAddr;
|
732
|
+
|
733
|
+
HostAddr = inet_addr (address);
|
734
|
+
if (HostAddr == -1) {
|
735
|
+
hostent *hp = gethostbyname (address);
|
736
|
+
if (!hp)
|
737
|
+
return 0;
|
738
|
+
HostAddr = ((in_addr*)(hp->h_addr))->s_addr;
|
739
|
+
}
|
740
|
+
|
741
|
+
memset (&pin, 0, sizeof(pin));
|
742
|
+
pin.sin_family = AF_INET;
|
743
|
+
pin.sin_addr.s_addr = HostAddr;
|
744
|
+
pin.sin_port = htons (port);
|
745
|
+
|
746
|
+
|
747
|
+
|
748
|
+
if (!data && (length > 0))
|
749
|
+
throw std::runtime_error ("bad outbound data");
|
750
|
+
char *buffer = (char *) malloc (length + 1);
|
751
|
+
if (!buffer)
|
752
|
+
throw std::runtime_error ("no allocation for outbound data");
|
753
|
+
memcpy (buffer, data, length);
|
754
|
+
buffer [length] = 0;
|
755
|
+
OutboundPages.push_back (OutboundPage (buffer, length, pin));
|
756
|
+
OutboundDataSize += length;
|
757
|
+
return length;
|
758
|
+
}
|
759
|
+
|
760
|
+
|
761
|
+
/****************************************
|
762
|
+
STATIC: DatagramDescriptor::SendDatagram
|
763
|
+
****************************************/
|
764
|
+
|
765
|
+
int DatagramDescriptor::SendDatagram (const char *binding, const char *data, int length, const char *address, int port)
|
766
|
+
{
|
767
|
+
DatagramDescriptor *dd = dynamic_cast <DatagramDescriptor*> (Bindable_t::GetObject (binding));
|
768
|
+
if (dd)
|
769
|
+
return dd->SendOutboundDatagram (data, length, address, port);
|
770
|
+
else
|
771
|
+
return -1;
|
772
|
+
}
|
773
|
+
|
774
|
+
|
775
|
+
|
527
776
|
|
528
777
|
|
data/ext/ed.h
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*****************************************************************************
|
2
2
|
|
3
|
-
$Id: ed.h
|
3
|
+
$Id: ed.h 2363 2006-04-23 14:32:40Z francis $
|
4
4
|
|
5
5
|
File: ed.h
|
6
6
|
Date: 06Apr06
|
@@ -41,38 +41,40 @@ class EventableDescriptor: public Bindable_t
|
|
41
41
|
EventableDescriptor (int);
|
42
42
|
virtual ~EventableDescriptor();
|
43
43
|
|
44
|
-
|
45
|
-
|
44
|
+
int GetSocket() {return MySocket;}
|
45
|
+
void Close();
|
46
46
|
|
47
47
|
virtual void Read() = 0;
|
48
48
|
virtual void Write() = 0;
|
49
49
|
virtual void Heartbeat() = 0;
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
// These methods tell us whether the descriptor
|
52
|
+
// should be selected or polled for read/write.
|
53
|
+
virtual bool SelectForRead() = 0;
|
54
|
+
virtual bool SelectForWrite() = 0;
|
55
55
|
|
56
|
-
|
56
|
+
// are we scheduled for a close, or in an error state, or already closed?
|
57
57
|
bool ShouldDelete();
|
58
|
-
|
59
|
-
|
58
|
+
// Do we have any data to write? This is used by ShouldDelete.
|
59
|
+
virtual int GetOutboundDataSize() {return 0;}
|
60
60
|
|
61
|
-
|
61
|
+
void ScheduleClose (bool after_writing);
|
62
62
|
|
63
|
-
|
64
|
-
enum {
|
65
|
-
PendingConnectTimeout = 4 // can easily be made an instance variable
|
66
|
-
};
|
63
|
+
void SetEventCallback (void (*cb)(const char*, int, const char*, int));
|
67
64
|
|
68
|
-
|
65
|
+
protected:
|
66
|
+
enum {
|
67
|
+
PendingConnectTimeout = 4 // can easily be made an instance variable
|
68
|
+
};
|
69
|
+
|
70
|
+
void (*EventCallback)(const char*, int, const char*, int);
|
69
71
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
time_t CreatedAt;
|
73
|
+
time_t LastRead;
|
74
|
+
time_t LastWritten;
|
75
|
+
int MySocket;
|
76
|
+
bool bCloseNow;
|
77
|
+
bool bCloseAfterWriting;
|
76
78
|
};
|
77
79
|
|
78
80
|
|
@@ -83,66 +85,109 @@ class ConnectionDescriptor
|
|
83
85
|
|
84
86
|
class ConnectionDescriptor: public EventableDescriptor
|
85
87
|
{
|
86
|
-
|
87
|
-
|
88
|
-
|
88
|
+
public:
|
89
|
+
ConnectionDescriptor (int);
|
90
|
+
virtual ~ConnectionDescriptor();
|
89
91
|
|
90
|
-
|
91
|
-
|
92
|
+
static int SendDataToConnection (const char*, const char*, int);
|
93
|
+
static void CloseConnection (const char*, bool);
|
92
94
|
|
93
|
-
|
95
|
+
int SendOutboundData (const char*, int);
|
94
96
|
|
95
|
-
|
97
|
+
void SetConnectPending (bool f) { bConnectPending = f; }
|
96
98
|
|
97
99
|
virtual void Read();
|
98
100
|
virtual void Write();
|
99
101
|
virtual void Heartbeat();
|
100
102
|
|
101
|
-
|
102
|
-
|
103
|
+
virtual bool SelectForRead();
|
104
|
+
virtual bool SelectForWrite();
|
103
105
|
|
104
|
-
|
105
|
-
|
106
|
+
// Do we have any data to write? This is used by ShouldDelete.
|
107
|
+
virtual int GetOutboundDataSize() {return OutboundDataSize;}
|
106
108
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
109
|
+
protected:
|
110
|
+
struct OutboundPage {
|
111
|
+
OutboundPage (const char *b, int l, int o=0): Buffer(b), Length(l), Offset(o) {}
|
112
|
+
void Free() {if (Buffer) free ((char*)Buffer); }
|
113
|
+
const char *Buffer;
|
114
|
+
int Length;
|
115
|
+
int Offset;
|
116
|
+
};
|
115
117
|
|
116
|
-
|
117
|
-
|
118
|
+
protected:
|
119
|
+
bool bConnectPending;
|
118
120
|
|
119
|
-
|
120
|
-
|
121
|
+
deque<OutboundPage> OutboundPages;
|
122
|
+
int OutboundDataSize;
|
121
123
|
|
122
124
|
private:
|
123
125
|
void _WriteOutboundData();
|
124
126
|
};
|
125
127
|
|
126
128
|
|
129
|
+
/************************
|
130
|
+
class DatagramDescriptor
|
131
|
+
************************/
|
132
|
+
|
133
|
+
class DatagramDescriptor: public EventableDescriptor
|
134
|
+
{
|
135
|
+
public:
|
136
|
+
DatagramDescriptor (int);
|
137
|
+
virtual ~DatagramDescriptor();
|
138
|
+
|
139
|
+
virtual void Read();
|
140
|
+
virtual void Write();
|
141
|
+
virtual void Heartbeat() {}
|
142
|
+
|
143
|
+
virtual bool SelectForRead() {return true;}
|
144
|
+
virtual bool SelectForWrite();
|
145
|
+
|
146
|
+
int SendOutboundData (const char*, int);
|
147
|
+
int SendOutboundDatagram (const char*, int, const char*, int);
|
148
|
+
|
149
|
+
// Do we have any data to write? This is used by ShouldDelete.
|
150
|
+
virtual int GetOutboundDataSize() {return OutboundDataSize;}
|
151
|
+
|
152
|
+
static int SendDatagram (const char*, const char*, int, const char*, int);
|
153
|
+
|
154
|
+
|
155
|
+
protected:
|
156
|
+
struct OutboundPage {
|
157
|
+
OutboundPage (const char *b, int l, struct sockaddr_in f, int o=0): Buffer(b), Length(l), From(f), Offset(o) {}
|
158
|
+
void Free() {if (Buffer) free ((char*)Buffer); }
|
159
|
+
const char *Buffer;
|
160
|
+
int Length;
|
161
|
+
int Offset;
|
162
|
+
struct sockaddr_in From;
|
163
|
+
};
|
164
|
+
|
165
|
+
deque<OutboundPage> OutboundPages;
|
166
|
+
int OutboundDataSize;
|
167
|
+
|
168
|
+
struct sockaddr_in ReturnAddress;
|
169
|
+
};
|
170
|
+
|
171
|
+
|
127
172
|
/************************
|
128
173
|
class AcceptorDescriptor
|
129
174
|
************************/
|
130
175
|
|
131
176
|
class AcceptorDescriptor: public EventableDescriptor
|
132
177
|
{
|
133
|
-
|
134
|
-
|
135
|
-
|
178
|
+
public:
|
179
|
+
AcceptorDescriptor (EventMachine_t*, int);
|
180
|
+
virtual ~AcceptorDescriptor();
|
136
181
|
|
137
182
|
virtual void Read();
|
138
183
|
virtual void Write();
|
139
184
|
virtual void Heartbeat();
|
140
185
|
|
141
|
-
|
142
|
-
|
186
|
+
virtual bool SelectForRead() {return true;}
|
187
|
+
virtual bool SelectForWrite() {return false;}
|
143
188
|
|
144
|
-
|
145
|
-
|
189
|
+
protected:
|
190
|
+
EventMachine_t *MyEventMachine;
|
146
191
|
};
|
147
192
|
|
148
193
|
|
data/ext/em.cpp
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*****************************************************************************
|
2
2
|
|
3
|
-
$Id: em.cpp
|
3
|
+
$Id: em.cpp 2363 2006-04-23 14:32:40Z francis $
|
4
4
|
|
5
5
|
File: ed.cpp
|
6
6
|
Date: 06Apr06
|
@@ -271,7 +271,7 @@ const char *EventMachine_t::ConnectToServer (const char *server, int port)
|
|
271
271
|
* not the Solaris-style, and returns zero with the error code in
|
272
272
|
* the error parameter.
|
273
273
|
* Return the binding-text of the newly-created pending connection,
|
274
|
-
|
274
|
+
* or NULL if there was a problem.
|
275
275
|
*/
|
276
276
|
|
277
277
|
if (!server || !*server || !port)
|
@@ -351,13 +351,13 @@ EventMachine_t::CreateTcpServer
|
|
351
351
|
|
352
352
|
const char *EventMachine_t::CreateTcpServer (const char *server, int port)
|
353
353
|
{
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
354
|
+
/* Create a TCP-acceptor (server) socket and add it to the event machine.
|
355
|
+
* Return the binding of the new acceptor to the caller.
|
356
|
+
* This binding will be referenced when the new acceptor sends events
|
357
|
+
* to indicate accepted connections.
|
358
|
+
*/
|
359
359
|
|
360
|
-
|
360
|
+
const char *output_binding = NULL;
|
361
361
|
|
362
362
|
struct sockaddr_in sin;
|
363
363
|
|
@@ -423,7 +423,7 @@ const char *EventMachine_t::CreateTcpServer (const char *server, int port)
|
|
423
423
|
if (!ad)
|
424
424
|
throw std::runtime_error ("unable to allocate acceptor");
|
425
425
|
Add (ad);
|
426
|
-
|
426
|
+
output_binding = ad->GetBinding().c_str();
|
427
427
|
}
|
428
428
|
|
429
429
|
return output_binding;
|
@@ -435,6 +435,65 @@ const char *EventMachine_t::CreateTcpServer (const char *server, int port)
|
|
435
435
|
}
|
436
436
|
|
437
437
|
|
438
|
+
/**********************************
|
439
|
+
EventMachine_t::OpenDatagramSocket
|
440
|
+
**********************************/
|
441
|
+
|
442
|
+
const char *EventMachine_t::OpenDatagramSocket (const char *address, int port)
|
443
|
+
{
|
444
|
+
const char *output_binding = NULL;
|
445
|
+
|
446
|
+
int sd = socket (AF_INET, SOCK_DGRAM, 0);
|
447
|
+
if (sd == -1)
|
448
|
+
goto fail;
|
449
|
+
// from here on, early returns must close the socket!
|
450
|
+
|
451
|
+
|
452
|
+
struct sockaddr_in sin;
|
453
|
+
memset (&sin, 0, sizeof(sin));
|
454
|
+
sin.sin_family = AF_INET;
|
455
|
+
sin.sin_port = htons (port);
|
456
|
+
|
457
|
+
|
458
|
+
if (address && *address) {
|
459
|
+
sin.sin_addr.s_addr = inet_addr (address);
|
460
|
+
if (sin.sin_addr.s_addr == -1) {
|
461
|
+
hostent *hp = gethostbyname (address);
|
462
|
+
if (hp == NULL)
|
463
|
+
goto fail;
|
464
|
+
sin.sin_addr.s_addr = ((in_addr*)(hp->h_addr))->s_addr;
|
465
|
+
}
|
466
|
+
}
|
467
|
+
else
|
468
|
+
sin.sin_addr.s_addr = htonl (INADDR_ANY);
|
469
|
+
|
470
|
+
|
471
|
+
// Set the new socket nonblocking.
|
472
|
+
{
|
473
|
+
int val = fcntl (sd, F_GETFL, 0);
|
474
|
+
if (fcntl (sd, F_SETFL, val | O_NONBLOCK) == -1)
|
475
|
+
goto fail;
|
476
|
+
}
|
477
|
+
|
478
|
+
if (bind (sd, (struct sockaddr*)&sin, sizeof(sin)) != 0)
|
479
|
+
goto fail;
|
480
|
+
|
481
|
+
{ // Looking good.
|
482
|
+
DatagramDescriptor *ds = new DatagramDescriptor (sd);
|
483
|
+
if (!ds)
|
484
|
+
throw std::runtime_error ("unable to allocate datagram-socket");
|
485
|
+
Add (ds);
|
486
|
+
output_binding = ds->GetBinding().c_str();
|
487
|
+
}
|
488
|
+
|
489
|
+
return output_binding;
|
490
|
+
|
491
|
+
fail:
|
492
|
+
if (sd != -1)
|
493
|
+
close (sd);
|
494
|
+
return NULL;
|
495
|
+
}
|
496
|
+
|
438
497
|
|
439
498
|
|
440
499
|
/*******************
|
data/ext/em.h
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*****************************************************************************
|
2
2
|
|
3
|
-
$Id: em.h
|
3
|
+
$Id: em.h 2362 2006-04-23 13:57:09Z francis $
|
4
4
|
|
5
5
|
File: ed.h
|
6
6
|
Date: 06Apr06
|
@@ -49,6 +49,7 @@ class EventMachine_t
|
|
49
49
|
const char *InstallOneshotTimer (int);
|
50
50
|
const char *ConnectToServer (const char *, int);
|
51
51
|
const char *CreateTcpServer (const char *, int);
|
52
|
+
const char *OpenDatagramSocket (const char *, int);
|
52
53
|
|
53
54
|
void Add (EventableDescriptor*);
|
54
55
|
|
data/ext/eventmachine.h
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
/*****************************************************************************
|
2
2
|
|
3
|
-
$Id: eventmachine.h
|
3
|
+
$Id: eventmachine.h 2363 2006-04-23 14:32:40Z francis $
|
4
4
|
|
5
5
|
File: eventmachine.h
|
6
6
|
Date: 15Apr06
|
@@ -37,9 +37,12 @@ extern "C" {
|
|
37
37
|
const char *evma_install_oneshot_timer (int seconds);
|
38
38
|
const char *evma_connect_to_server (const char *server, int port);
|
39
39
|
const char *evma_create_tcp_server (const char *address, int port);
|
40
|
+
const char *evma_open_datagram_socket (const char *server, int port);
|
40
41
|
int evma_send_data_to_connection (const char *binding, const char *data, int data_length);
|
42
|
+
int evma_send_datagram (const char *binding, const char *data, int data_length, const char *address, int port);
|
41
43
|
void evma_close_connection (const char *binding, int after_writing);
|
42
44
|
void evma_stop_machine();
|
45
|
+
|
43
46
|
#if __cplusplus
|
44
47
|
}
|
45
48
|
#endif
|
data/ext/project.h
CHANGED
data/ext/rubymain.cpp
CHANGED
@@ -188,6 +188,17 @@ static VALUE t_send_data (VALUE self, VALUE signature, VALUE data, VALUE data_le
|
|
188
188
|
}
|
189
189
|
|
190
190
|
|
191
|
+
/***************
|
192
|
+
t_send_datagram
|
193
|
+
***************/
|
194
|
+
|
195
|
+
static VALUE t_send_datagram (VALUE self, VALUE signature, VALUE data, VALUE data_length, VALUE address, VALUE port)
|
196
|
+
{
|
197
|
+
int b = evma_send_datagram (StringValuePtr (signature), StringValuePtr (data), FIX2INT (data_length), StringValuePtr(address), FIX2INT(port));
|
198
|
+
return INT2NUM (b);
|
199
|
+
}
|
200
|
+
|
201
|
+
|
191
202
|
/******************
|
192
203
|
t_close_connection
|
193
204
|
******************/
|
@@ -212,6 +223,18 @@ static VALUE t_connect_server (VALUE self, VALUE server, VALUE port)
|
|
212
223
|
return rb_str_new2 (f);
|
213
224
|
}
|
214
225
|
|
226
|
+
/*****************
|
227
|
+
t_open_udp_socket
|
228
|
+
*****************/
|
229
|
+
|
230
|
+
static VALUE t_open_udp_socket (VALUE self, VALUE server, VALUE port)
|
231
|
+
{
|
232
|
+
const char *f = evma_open_datagram_socket (StringValuePtr(server), FIX2INT(port));
|
233
|
+
if (!f || !*f)
|
234
|
+
rb_raise (rb_eRuntimeError, "no datagram socket");
|
235
|
+
return rb_str_new2 (f);
|
236
|
+
}
|
237
|
+
|
215
238
|
|
216
239
|
|
217
240
|
/*****************
|
@@ -251,10 +274,11 @@ extern "C" void Init_rubyeventmachine()
|
|
251
274
|
rb_define_module_function (EmModule, "add_oneshot_timer", (VALUE(*)(...))t_add_oneshot_timer, 1);
|
252
275
|
rb_define_module_function (EmModule, "start_tcp_server", (VALUE(*)(...))t_start_server, 2);
|
253
276
|
rb_define_module_function (EmModule, "send_data", (VALUE(*)(...))t_send_data, 3);
|
277
|
+
rb_define_module_function (EmModule, "send_datagram", (VALUE(*)(...))t_send_datagram, 5);
|
254
278
|
rb_define_module_function (EmModule, "close_connection", (VALUE(*)(...))t_close_connection, 2);
|
255
279
|
rb_define_module_function (EmModule, "connect_server", (VALUE(*)(...))t_connect_server, 2);
|
280
|
+
rb_define_module_function (EmModule, "open_udp_socket", (VALUE(*)(...))t_open_udp_socket, 2);
|
256
281
|
rb_define_module_function (EmModule, "release_machine", (VALUE(*)(...))t_release_machine, 0);
|
257
|
-
rb_define_module_function (EmModule, "start_server", (VALUE(*)(...))t_release_machine, 0);
|
258
282
|
rb_define_module_function (EmModule, "stop", (VALUE(*)(...))t_stop, 0);
|
259
283
|
|
260
284
|
// Why am I missing the rb_ function that converts a class/module name to a VALUE?
|
data/lib/eventmachine.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# $Id: eventmachine.rb
|
1
|
+
# $Id: eventmachine.rb 2364 2006-04-23 16:11:13Z francis $
|
2
2
|
#
|
3
3
|
# Author:: blackhedd (gmail address: garbagecat20).
|
4
4
|
# Date:: 8 Apr 2006
|
@@ -174,6 +174,10 @@ module EventMachine
|
|
174
174
|
release_machine
|
175
175
|
end
|
176
176
|
|
177
|
+
# EventMachine#run_without_threads is semantically identical
|
178
|
+
# to EventMachine#run, but it runs somewhat faster.
|
179
|
+
# However, it must not be used in applications that spin
|
180
|
+
# Ruby threads.
|
177
181
|
def EventMachine::run_without_threads &block
|
178
182
|
EventMachine::run false, &block
|
179
183
|
end
|
@@ -494,6 +498,71 @@ module EventMachine
|
|
494
498
|
end
|
495
499
|
|
496
500
|
|
501
|
+
# EventMachine#open_datagram_socket is for support of UDP-based
|
502
|
+
# protocols. Its usage is similar to that of EventMachine#start_server.
|
503
|
+
# It takes three parameters: an IP address (which must be valid
|
504
|
+
# on the machine which executes the method), a port number,
|
505
|
+
# and an optional Module name which will handle the data.
|
506
|
+
# This method will create a new UDP (datagram) socket and
|
507
|
+
# bind it to the address and port that you specify.
|
508
|
+
# The normal callbacks (see EventMachine#start_server) will
|
509
|
+
# be called as events of interest occur on the newly-created
|
510
|
+
# socket, but there are some differences in how they behave.
|
511
|
+
#
|
512
|
+
# Connection#receive_data will be called when a datagram packet
|
513
|
+
# is received on the socket, but unlike TCP sockets, the message
|
514
|
+
# boundaries of the received data will be respected. In other words,
|
515
|
+
# if the remote peer sent you a datagram of a particular size,
|
516
|
+
# you may rely on Connection#receive_data to give you the
|
517
|
+
# exact data in the packet, with the original data length.
|
518
|
+
# Also observe that Connection#receive_data may be called with a
|
519
|
+
# <i>zero-length</i> data payload, since empty datagrams are permitted
|
520
|
+
# in UDP.
|
521
|
+
#
|
522
|
+
# Connection#send_data is available with UDP packets as with TCP,
|
523
|
+
# but there is an important difference. Because UDP communications
|
524
|
+
# are <i>connectionless,</i> there is no implicit recipient for the packets you
|
525
|
+
# send. Ordinarily you must specify the recipient for each packet you send.
|
526
|
+
# However, EventMachine
|
527
|
+
# provides for the typical pattern of receiving a UDP datagram
|
528
|
+
# from a remote peer, performing some operation, and then sending
|
529
|
+
# one or more packets in response to the same remote peer.
|
530
|
+
# To support this model easily, just use Connection#send_data
|
531
|
+
# in the code that you supply for Connection:receive_data.
|
532
|
+
# EventMachine will
|
533
|
+
# provide an implicit return address for any messages sent to
|
534
|
+
# Connection#send_data within the context of a Connection#receive_data callback,
|
535
|
+
# and your response will automatically go to the correct remote peer.
|
536
|
+
# (TODO: Example-code needed!)
|
537
|
+
#
|
538
|
+
# Observe that the port number that you supply to EventMachine#open_datagram_socket
|
539
|
+
# may be zero. In this case, EventMachine will create a UDP socket
|
540
|
+
# that is bound to an <i>ephemeral</i> (not well-known) port.
|
541
|
+
# This is not appropriate for servers that must publish a well-known
|
542
|
+
# port to which remote peers may send datagrams. But it can be useful
|
543
|
+
# for clients that send datagrams to other servers.
|
544
|
+
# If you do this, you will receive any responses from the remote
|
545
|
+
# servers through the normal Connection#receive_data callback.
|
546
|
+
# Observe that you will probably have issues with firewalls blocking
|
547
|
+
# the ephemeral port numbers, so this technique is most appropriate for LANs.
|
548
|
+
# (TODO: Need an example!)
|
549
|
+
#
|
550
|
+
# If you wish to send datagrams to arbitrary remote peers (not
|
551
|
+
# necessarily ones that have sent data to which you are responding),
|
552
|
+
# then see Connection#send_datagram.
|
553
|
+
#
|
554
|
+
def self::open_datagram_socket address, port, handler=nil
|
555
|
+
s = open_udp_socket address, port
|
556
|
+
klass = Class.new( Connection ) {
|
557
|
+
handler and include handler
|
558
|
+
}
|
559
|
+
c = klass.new s
|
560
|
+
@conns[s] = c
|
561
|
+
block_given? and yield c
|
562
|
+
c
|
563
|
+
end
|
564
|
+
|
565
|
+
|
497
566
|
private
|
498
567
|
def EventMachine::event_callback conn_binding, opcode, data
|
499
568
|
case opcode
|
@@ -666,6 +735,31 @@ class Connection
|
|
666
735
|
EventMachine::send_data @signature, data, data.length
|
667
736
|
end
|
668
737
|
|
738
|
+
|
739
|
+
# send_datagram is for sending UDP messages.
|
740
|
+
# This method may be called from any Connection object that refers
|
741
|
+
# to an open datagram socket (see EventMachine#open_datagram_socket).
|
742
|
+
# The method sends a UDP (datagram) packet containing the data you specify,
|
743
|
+
# to a remote peer specified by the IP address and port that you give
|
744
|
+
# as parameters to the method.
|
745
|
+
# Observe that you may send a zero-length packet (empty string).
|
746
|
+
# However, you may not send an arbitrarily-large data packet because
|
747
|
+
# your operating system will enforce a platform-specific limit on
|
748
|
+
# the size of the outbound packet. (Your kernel
|
749
|
+
# will respond in a platform-specific way if you send an overlarge
|
750
|
+
# packet: some will send a truncated packet, some will complain, and
|
751
|
+
# some will silently drop your request).
|
752
|
+
# On LANs, it's usually OK to send datagrams up to about 4000 bytes in length,
|
753
|
+
# but to be really safe, send messages smaller than the Ethernet-packet
|
754
|
+
# size (typically about 1400 bytes). Some very restrictive WANs
|
755
|
+
# will either drop or truncate packets larger than about 500 bytes.
|
756
|
+
#
|
757
|
+
def send_datagram data, recipient_address, recipient_port
|
758
|
+
data = data.to_s
|
759
|
+
EventMachine::send_datagram @signature, data, data.length, recipient_address, recipient_port
|
760
|
+
end
|
761
|
+
|
762
|
+
|
669
763
|
end
|
670
764
|
|
671
765
|
|
metadata
CHANGED