-
Notifications
You must be signed in to change notification settings - Fork 6
/
sockets.w
2211 lines (1930 loc) · 82.1 KB
/
sockets.w
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\def\title{NATIVE SOCKETS (VER 2.0)}
\def\topofcontents{\null\vfill
\centerline{\titlefont Native Scheme Sockets for Chez Scheme}
\vskip 15pt
\centerline{(Version 2.0)}
\vfill}
\def\botofcontents{\vfill
\noindent
Copyright $\copyright$ 2012 Aaron W. Hsu $\langle\.{[email protected]}\rangle$
\smallskip\noindent
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
\smallskip\noindent
THE SOFTWARE IS PROVIDED ``AS IS'' AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
}
@** Introduction.
This is a socket library for Chez Scheme which attempts to remain
as faithful as possible to BSD sockets while still maintaining a
normal operation that will be familiar to Scheme programmers.
Procedures are documented inline at the top of their main
definitions.
The current implementation uses records instead of magic numbers.
Hopefully this keeps the system a little more portable.
Only Internet and Local/Unix domain sockets are built-in,
though this library supports additional types through its
data hierarchy.
The program itself is layed out like so:
@p
@<Foreign code utilities@>
@<Foreign constants@>
@<Foreign functions@>
@<Foreign code initialization@>
@<Datatype definitions@>
@<Socket constants@>
@<Internal procedure definitions@>
@<External procedure definitions@>
@<Register pre-defined socket domains@>
@ Because we are working at the system level, we need to know the
operating system on which we are working. Practically speaking,
when it comes to sockets, this really comes down to a question of
whether we are working on Windows or now.
@c () => (windows? if-windows?)
@<Foreign code utilities@>=
(meta define (windows?) (memq (machine-type) '(i3nt ti3nt)))
(define-syntax (if-windows? x)
(syntax-case x ()
[(_ c a) (if (windows?) #'c #'a)]))
@ The following is an R6RS library that encapsulate this library
for use by external R6RS programs.
@(sockets.sls@>=@q)
(library (arcfide sockets)
(export make-socket socket? socket-fd socket-domain socket-type socket-protocol
socket-option? make-socket-option socket-option
define-socket-option-type
make-tcp-option make-udp-option make-raw-option make-ip-option
tcp-option? udp-option? raw-option? ip-option?
socket-address? socket-address
unix-address? make-unix-address unix-address-path
internet-address? make-internet-address internet-address-ip
internet-address-port string->internet-address
internet-address->string string->ipv4
make-address-info address-info? address-info-domain
address-info-type address-info-protocol address-info-address
get-address-info
address-info/canonical-name address-info/numeric-host
address-info/passive
create-socket make-socket-domain make-socket-type
socket-domain/unix socket-domain/local
socket-domain/internet socket-type/stream socket-type/datagram
socket-type/sequence-packet socket-type/raw socket-type/random
register-socket-domain!
make-socket-protocol socket-protocol?
protocol-entry-name protocol-entry-aliases protocol-entry-value
socket-protocol/auto next-protocol-entry
get-protocol-by-name get-protocol-by-constant
open-protocol-database close-protocol-database
bind-socket listen-socket accept-socket connect-socket
close-socket shutdown-socket shutdown-method? make-shutdown-method
shutdown-method/read shutdown-method/write shutdown-method/read&write
send-to-socket send-to/dont-route send-to/out-of-band
make-send-to-option
receive-from-socket receive-from/out-of-band receive-from/peek
receive-from/wait-all receive-from/dont-wait
make-receive-from-option
socket-maximum-connections
get-socket-option set-socket-option! set-socket-nonblocking!
socket-nonblocking?
make-socket-condition socket-condition?
socket-condition-who socket-condition-syscall socket-condition-type
socket-condition-message socket-error socket-raise/unless)
(import (chezscheme))
(include "sockets.ss"))
@* Uncompleted/Planned Features.
The following is a development to-do list of intended features
and possible improvements.
\medskip{\parindent=0.5in
\item{1)} Consider IP field name replaced with ADDRESS
\item{2)} Consider name change of RECEIVE-FROM-SOCKET
\item{3)} Consider name change of ACCEPT-SOCKET
\item{4)} Better handling of paths in UNIX sockets
\par}\medskip
Even though this library is mostly general, there are some exceptions
where I have included some features by default that aren't really
possible to access throughout the entire gambit of what machines and
systems on which we expect this library to run. Most notably, Microsoft
Windows does not support UNIX sockets.
To handle these situtations, the following procedure is run whenever
there is a feature that is unsupported on a particular system.
@c () => (unsupported-feature)
@<Foreign code utilities@>=
(define (unsupported-feature feature)
(error feature "this feature is not supported on this platform"))
@** Datatypes. The next few sections define the various socket
datatypes. Sockets are file descriptors which usually have integer
representations. The |socket| datatype also defines fields for easily
determining the domain, the type, and the protocol of the socket.
The |fd| field should be a socket field descriptor, which is a positive
integer. The |domain|, |type|, and |protocol| fields all contain
constants which return true for |socket-domain?|, |socket-type?|,
and |socket-protocol?|, respectively. This functionality could be
introspected from the field descriptor, but it is more convenient to
store this information directly.
@c () => (make-socket socket? socket
socket-nonblocking? socket-nonblocking?-set!
socket-fd socket-domain socket-type socket-protocol)
@<Datatype definitions@>=
(define-record-type socket
(fields fd domain type protocol (mutable nonblocking?))
(protocol
(lambda (n)
(lambda (fd domain type protocol)
(n fd domain type protocol #f)))))
@* 2 Socket Options.
Socket options form a hierarchy of depth two that identify different settings
for sockets.
The |level| field should be a number identifying the
highlevel setting group, and |id| should be a number identifying the
setting itself, as described in |getsockopt(2)|.
|valid?| is a predicate that returns true for valid setting values,
and false otherwise.
|make-socket-option| called without a level will default to a socket
api level option, otherwise it expects a proper option level.
@c () => (socket-option? make-socket-option socket-option
socket-option-foreign-size socket-option-foreign-maker
socket-option-foreign-converter socket-option-id socket-option-level)
@<Datatype definitions@>=
(define-record-type socket-option
(fields level id foreign-size foreign-maker foreign-converter)
(protocol
(lambda (p)
(case-lambda
[(id size maker converter)
(p $sol-socket id size maker converter)]
[(id size maker converter level)
(p level id size maker converter)]))))
@ To define some basic socket option levels, we have a form:
\medskip\verbatim
(define-socket-option-type <name> <level>)
!endverbatim
\medskip
\noindent Where |<level>| is an integer as detailed in |setsockopt(2)|.
This will bind |name|, |make-name|, and |name?|.
@c () => (define-socket-option-type)
@<Datatype definitions@>=
(define-syntax define-socket-option-type
(syntax-rules ()
[(_ name level)
(define-record-type name (parent socket-option)
(protocol
(lambda (n)
(lambda (id size maker converter)
((n id size maker converter level))))))]))
@ We currently define child option types for the protocols |tcp(7)|,
|udp(7)|, and |raw(7)|. I have added the ip option as well because ip
options are often applicable to the above.
@c () =>
(make-tcp-option make-udp-option make-raw-option make-ip-option
tcp-option? udp-option? raw-option? ip-option?
define-socket-option-type
tcp-option udp-option raw-option ip-option)
@<Datatype definitions@>=
(define-socket-option-type tcp-option $ipproto-tcp)
(define-socket-option-type udp-option $ipproto-udp)
(define-socket-option-type raw-option $ipproto-raw)
(define-socket-option-type ip-option $ipproto-ip)
@* 2 Socket Addresses.
Other than sockets, one must also have a means by which to
address other hosts. Socket Addresses represent the destinations or
origins of transmissions.
All socket address data types are subtypes of |socket-address|es.
@c () => (socket-address? socket-address socket-address-converter)
@<Datatype definitions@>=
(define-record-type socket-address (fields converter))
@ When passing socket addresses to foreign procedures,
we must first convert these Scheme datatypes to a proper foreign socket
address structure.
To do this, every child of the |socket-address| type
must provide a converter.
This should be defined by default, and it should not be necessary
for the user to specify the converter.
The converter should accept the socket address type
for which it is defined as its sole argument.
It should then return a bytevector that represents the
foreign structure.
Internally, when we need to communicate with a foreign
procedure that expects a socket address, we use the following
wrapper |socket-address->foreign| and if we need to convert an unknown
address type into a Scheme data structure, we use
|foreign->socket-address|.
When constructing a bytevector container for foreign code,
it is useful to know the size a structure should be before
creating it. This is defined as |foreign-address-size|,
but it is defined as part of the record definition for
socket domains below."
@c () =>
(socket-address->foreign foreign->socket-address)
@<Datatype definitions@>=
(define (socket-address->foreign sock-addr)
((socket-address-converter sock-addr) sock-addr))
(define (foreign->socket-address domain addr addr-len)
((socket-domain-extractor domain) addr addr-len))
@* 3 UNIX Socket Addresses.
UNIX domain sockets have addresses that are just paths, which in turn
are simply strings.
@c () =>
(unix-address? make-unix-address unix-address-path unix-address)
@<Datatype definitions@>=
(define-record-type unix-address
(parent socket-address)
(protocol
(lambda (n) (lambda (path) ((n unix-address->foreign) path))))
(fields path))
@ The protocol uses |unix-address->foreign| as the converter
for a unix address.
It returns a bytevector that is the equivalent layout
of the |sockaddr_un| structure.
Converting the foreign address back to a UNIX address
can be done by grabbing the vector elements of the range from
the start of the path to the first null.
@c () =>
(unix-address->foreign foreign->unix-address)
@<Datatype definitions@>=
(define (unix-address->foreign addr)
(if-windows?
(unsupported-feature 'unix-sockets)
(values (make-foreign-unix-address (unix-address-path addr))
size-of/sockaddr-un)))
(define (foreign->unix-address addr addr-len)
(if-windows?
(unsupported-feature 'unix-sockets)
(make-unix-address (foreign-unix-address-path addr))))
@* 3 IPV4 Internet Socket Addresses.
Internet addresses are represented by an IP address and a port
number. The highest eight bits of the ip address should be the first
octet of the ip address, and so forth. The port value is a 16-bit
unsigned integer.
@c () =>
(internet-address internet-address?
make-internet-address
internet-address-ip
internet-address-port)
@<Datatype definitions@>=
(define-record-type internet-address
(parent socket-address)
(protocol
(lambda (n)
(lambda (i p) ((n internet-address->foreign) i p))))
(fields ip port))
@ @c ()
=>
(internet-address->foreign foreign->internet-address)
@<Datatype definitions@>=
(define (internet-address->foreign addr)
(values
(make-foreign-ipv4-address
(internet-address-port addr)
(internet-address-ip addr))
size-of/sockaddr-in))
(define (foreign->internet-address addr addr-len)
(make-internet-address
(foreign-ipv4-address-ip addr)
(foreign-ipv4-address-port addr)))
@ IP addresses often come in the form of strings. So, let's define a
few procedures for handling strings as IPs.
Usually internet addresses are given as a colon delimited ip
address string and a port number. |string->internet-address| converts
this to a proper internet address structure.
@c () =>
(string->internet-address)
@<External procedure definitions@>=
(define (string->internet-address s)
(let-values ([(ip-string port-string) @<Split IPV4 address@>])
(let ([ip (and ip-string (string->ipv4 ip-string))]
[port (and port-string (string->number port-string))])
(assert (or (not ip) (>= 32 (bitwise-length ip))))
(assert (or (not port) (< 0 port 65536)))
(make-internet-address ip port))))
@ To parse the string, we assume that any valid internet address string
must have a single colon in it separating the two parts. If this is the
case, then we can take this and use the parts individually. On the other
hand, if we do not, then we consider it an invalid internet address
string.
@c (s)
@<Split IPV4 address@>=
(let ([val (split-string s #\:)])
(if (pair? val)
(values
(car val)
(and (pair? (cdr val)) (cadr val)))
(values #f #f)))
@ Another helper splits the ip address and converts it to a bytevector
in big endian or network byte order.
@c () => (string->ipv4)
@<External procedure definitions@>=
(define (string->ipv4 s)
(let ([bytes (map string->number (split-string s #\.))])
(assert (= 4 (length bytes)))
(fold-left
(lambda (s x)
(assert (<= 0 x 255))
(+ (bitwise-arithmetic-shift s 8) x))
0
bytes)))
@ Both of the above utilities rely on a string splitting function. We'll
define that here.
@c () => (split-string)
@<Internal procedure definitions@>=
(define (split-string s c)
(define (debuf buf) (list->string (reverse buf)))
(define (split lst res buf)
(cond
[(null? lst) (reverse (cons (debuf buf) res))]
[(char=? c (car lst)) (split (cdr lst) (cons (debuf buf) res) '())]
[else (split (cdr lst) res (cons (car lst) buf))]))
(if (fxzero? (string-length s))
'()
(split (string->list s) '() '())))
@ The reverse procedure |internet-address->string| is more
straightforward.
@c () =>
(internet-address->string)
@<External procedure definitions@>=
(define (internet-address->string addr)
(let ([ip (or (internet-address-ip addr) 0)]
[port (internet-address-port addr)])
(assert (or (not port) (< 0 port 65536)))
(assert (>= 32 (bitwise-length ip)))
(do ([ip ip (bitwise-arithmetic-shift ip -8)]
[i 0 (+ i 1)]
[res '() (cons (mod ip 256) res)])
[(= 4 i)
(fold-right
(lambda (x s)
(cond
[(string? s)
(string-append (number->string x) "." s)]
[(number? s)
(string-append (number->string x) ":" (number->string s))]
[else (number->string x)]))
port
res)])))
@* Socket Constants.
Procedures such as |create-socket| accept records which wrap
numeric constant values for passing into the FFI.
These constants are limited with what is built in to have the
widest acceptance and portability, but if the user wishes to use
more values, he can do so by using the appropriate make form.
This requires that the user know the Operating specific constant
value that should be used in the FFI.
Every constant type is a child of the |socket-constant| type.
@c () => (socket-constant make-socket-constant socket-constant?
socket-constant-value)
@<Datatype definitions@>=
(define-record-type socket-constant (fields (immutable value)))
@* Address Information.
The |get-address-info| procedure returns a list of |address-info|
structures that specify different means by which a host may be
contacted and a string representing the canonical name of the host if
this information was requested. Otherwise, the second returned value is
false. It corresponds to the |getaddrinfo(3)| UNIX system call, with a
few things modified to be more Schemely.
\medskip\verbatim
(get-address-info node service [domain type protocol])
=> addresses canonical-name
!endverbatim
\medskip
\noindent Each |address-info| structure identifies or associates an
address with a given domain, type, and protocol, which is enough to
create a socket and connect to that address using the right types.
@c () => (make-address-info address-info? address-info
address-info-domain address-info-type
address-info-protocol address-info-address)
@<Datatype definitions@>=
(define-record-type address-info (fields domain type protocol address))
@ The |address| field of an |address-info| record should be
an |internet-socket-address|. Also note that in the normal C
equivalent of this record type, |struct addrinfo|, there is also a
field for the canonical name. Because this is given only once, it does
not make sense to have a field for this in every structure that is
returned, so I have decided to place this as an additional value that is
returned by |get-address-info| instead, which makes more sense, since
it really is a separate thing to be returned.
The downside to this approach is that it requires two values to be
accepted when calling |get-address-info|, even if one does not care
about the canonical name. I am open to better approaches, but this does
not seem to be too inconvenient in practice.
|get-address-info| takes an optional set of hints, such as
the domain, type, protocol, or a set of flags that can be used to filter
out the results obtained from the call.
To grab the address we make our foreign buffers, call
|$getaddrinfo|, check for errors, and then convert the foreign
address foreign-integer to a Scheme structure.
@c ()
=> (get-address-info)
@<External procedure definitions@>=
(define get-address-info
(case-lambda
[(node service) (%get-address-info node service #f #f #f '())]
[(node service dom type proto . flags)
(%get-address-info node service dom type proto flags)]))
(define (%get-address-info node service domain type protocol flags)
@<Check get-address-info argument types@>
(let ([alp (make-foreign-pointer)]
[hints @<Build address information hints@>]
[service (or (and (string? service) service)
(number->string service 10))])
(let ([res ($getaddrinfo node service hints alp)])
(if (zero? res)
(values @<Convert foreign address information list@>
(foreign-address-info-canonical-name
(foreign-pointer-value alp)))
(error 'get-address-info
"getaddrinfo() failed with code"
`(code ,res)
($gai_strerror res))))))
@ The optional domain, type, and protocol
may be false or may correspond to some form of socket options. They are
used as hints for |get-address-info| in the same manner as the
corresponding hints structure for |getaddrinfo(2)|. The flags also
work in the same way, but they must all be values for which
|address-info-option?| returns true when applied to them.
|node| should be a hostname string, and |service| should be either
a service name string, integer number string identifying a valid port,
or a positive integer representing a valid port.
@c (node service domain type protocol flags)
@<Check get-address-info argument types@>=
(assert (or (not domain) (socket-domain? domain)))
(assert (or (not type) (socket-type? type)))
(assert (or (not protocol) (socket-protocol? protocol)))
(assert (for-all address-info-option? flags))
(assert (string? node))
(assert
(or
(string? service)
(and
(integer? service)
(positive? service)
(< 0 service 65536))))
@ There are a few built in address info options.
@c () =>
(address-info/canonical-name address-info/numeric-host
address-info/passive)
@<Socket constants@>=
(define address-info/canonical-name
(make-address-info-option %ai/canonname))
(define address-info/numeric-host
(make-address-info-option %ai/numerichost))
(define address-info/passive
(make-address-info-option %ai/passive))
@ Each of the above constants is an |address-info-option|. This is a
datatype to encapsulate the standard socket constants.
@c () =>
(make-address-info-option address-info-option address-info-option?)
@<Datatype definitions@>=
(define-record-type address-info-option (parent socket-constant))
@ We need to convert the hints given to use in Scheme terms and convert
them to foreign hints, which is its own structure.
@c (domain type protocol flags)
@<Build address information hints@>=
(make-foreign-address-info
(fold-left
(lambda (s v) (fxior s (socket-constant-value v)))
0 flags)
(or (and domain (socket-constant-value domain)) 0)
(or (and type (socket-constant-value type)) 0)
(or (and protocol (socket-constant-value protocol)) 0)
0 0 0 0)
@ The foreign |struct addrinfo| is a linked list of information
records. To convert these to real lists of |address-info| records, I use
the foreign accessors and loop over the linked list.
@c (alp)
@<Convert foreign address information list@>=
(define (get-address-info-entry alp)
(let ([dom (lookup-domain (foreign-address-info-domain alp))])
(if dom
(make-address-info
dom
(make-socket-type (foreign-address-info-type alp))
(make-socket-protocol (foreign-address-info-protocol alp))
(foreign->socket-address
dom
(foreign-address-info-address alp)
(foreign-address-info-address-length alp)))
#f)))
(do ([ptr (foreign-pointer-value alp) (foreign-address-info-next ptr)]
[res '()
(let ([entry (get-address-info-entry ptr)])
(if entry (cons entry res) res))])
[(zero? ptr) (reverse res)])
@** Socket Procedures.
There are a number of socket programming functions that we bind and
wrap here. The following table matches the foreign system call to the
Scheme procedure defined in this library.
\medskip
\centerline{\bf Scheme Procedures and System Call Equivalents}
\begingroup\tt
\settabs \+ \hskip 1.5in & \hskip 2in & \hskip 2in & \hfill \cr
\+ &\hrulefill &\hrulefill& \cr
\+ &create-socket & socket(2)& \cr
\+ &next-protocol-entry & getprotoent(2)& \cr
\+ &get-protocol-by-name & getprotobyname(2)& \cr
\+ &get-protocol-by-constant & getprotobynumber(2)& \cr
\+ &open-protocol-database & setprotoent(2)& \cr
\+ &close-protocol-database & endprotoent(2)& \cr
\+ &bind-socket & bind(2)& \cr
\+ &listen-socket & listen(2)& \cr
\+ &accept-socket & accept(2)& \cr
\+ &connect-socket & connect(2)& \cr
\+ &close-socket & close(2)& \cr
\+ &shutdown-socket & shutdown(2)& \cr
\+ &send-to-socket & sendto(2)& \cr
\+ &receive-from-socket & recvfrom(2)& \cr
\+ &socket-maximum-connections & SOMAXCONN& \cr
\+ &get-socket-option & getsockopt(2)& \cr
\+ &set-socket-option! & setsockopt(2)& \cr
\endgroup
@* 2 Creating Sockets.
Creating sockets is achieved through the |create-socket| procedure. The
datatypes for its arguments are described further down.
\medskip\verbatim
(create-socket domain type protocol) => socket
!endverbatim
After calling the foreign |socket(2)| call, we need to error out if
its a bad socket, but otherwise, we need to build the appropriate
struture and set any options. In this case, we default to nonblocking
sockets, while most BSD sockets systems start in blocking mode.
@c () => (create-socket)
@<External procedure definitions@>=
(define (create-socket domain type protocol)
@<Check |create-socket| arguments@>
(call-with-errno
(lambda ()
($socket (socket-constant-value domain)
(socket-constant-value type)
(socket-constant-value protocol)))
(lambda (ret err)
(if (= ret invalid-socket)
(socket-error 'create-socket 'socket err)
(let ([sock (make-socket ret domain type protocol)])
(set-socket-nonblocking! sock #t)
sock)))))
@ I do very simple argument checking of the |create-socket|
arguments.
@c (domain type protocol)
@<Check |create-socket| arguments@>=
(define who 'create-socket)
(unless (socket-domain? domain)
(error who "invalid socket domain" domain))
(unless (socket-type? type)
(error who "invalid socket type" type))
(unless (socket-protocol? protocol)
(error who "invalid socket protocol" protocol))
@ |create-socket| uses three different constant types for the
domain, type, and protocol of the socket.
Socket domains determine the family to which the socket belongs.
They also must embed an extracter and a size value so that
converting to and from foreign values can be done without
explicitly knowing the type of the domain beforehand.
@c () => (%socket-domain make-socket-domain socket-domain?
socket-domain-extractor foreign-address-size)
@<Datatype definitions@>=
(define-record-type (%socket-domain make-socket-domain socket-domain?)
(parent socket-constant)
(fields
(immutable extractor socket-domain-extractor)
(immutable addr-size foreign-address-size)))
@ We predefine UNIX/Local and Internet IPV4 domain types.
@c () =>
(socket-domain/unix socket-domain/local socket-domain/internet)
@<Socket constants@>=
(define socket-domain/unix
(make-socket-domain
%socket-domain/unix
foreign->unix-address
size-of/addr-un))
(define socket-domain/local
(make-socket-domain
%socket-domain/local
foreign->unix-address
size-of/addr-un))
(define socket-domain/internet
(make-socket-domain
%socket-domain/internet
foreign->internet-address
size-of/addr-in))
@ Socket domains sometimes need to be grabbed by just their
internal value.
We set up a database to hold the registered domains and
allow for additional domains to be registered.
@c () => (register-socket-domain! socket-domain-db)
@<External procedure definitions@>=
(define socket-domain-db
(make-parameter '()))
(define (register-socket-domain! domain)
(assert (socket-domain? domain))
(let* ([val (socket-constant-value domain)]
[res (assv val (socket-domain-db))])
(if res
(set-cdr! res domain)
(socket-domain-db
(cons (cons val domain)
(socket-domain-db))))))
@ We register only the two necessary ones right now.
@c ()
@<Register pre-defined socket domains@>=
(register-socket-domain! socket-domain/unix)
(register-socket-domain! socket-domain/internet)
@ We'll want to be able to look these domains up by number.
@c () => (lookup-domain)
@<External procedure definitions@>=
(define (lookup-domain val)
(let ([res (assv val (socket-domain-db))])
(and res (cdr res))))
@ Socket types determine the nature of the data stream that
transmits over the socket. See the |socket(2)| man page for
more details.
@c () => (%socket-type make-socket-type socket-type?)
@<Datatype definitions@>=
(define-record-type (%socket-type make-socket-type socket-type?)
(parent socket-constant))
@ @c ()
=> (socket-type/stream socket-type/datagram
socket-type/sequence-packet socket-type/raw
socket-type/random)
@<Socket constants@>=
(define socket-type/stream
(make-socket-type %socket-type/stream))
(define socket-type/datagram
(make-socket-type %socket-type/datagram))
(define socket-type/sequence-packet
(make-socket-type %socket-type/sequence-packet))
(define socket-type/raw
(make-socket-type %socket-type/raw))
(define socket-type/random
(make-socket-type %socket-type/random))
@ Dealing with protocol numbers is slightly different,
since these entries are found in a database that can change,
rather than in some header file. The datatype declaration is still the
same, though. Generally, it is fine to use an automatically chosen protocol
number, so the user will not usually need to use the more complicated
database searching tools in the next sections. Instead, we define a
default protocol here for automatic protocol selection.
@c () => (make-socket-protocol socket-protocol?)
@<Datatype definitions@>=
(define-record-type
(%socket-protocol make-socket-protocol socket-protocol?)
(parent socket-constant))
@ @c () => (socket-protocol/auto)
@<Socket constants@>=
(define socket-protocol/auto (make-socket-protocol 0))
@ Protocols can be retreived by the |getproto*| family of
functions. These functions return |protocol-entry| structures.
For protocol entries, we expect the value to be a protocol constant.
Each of the general protocol retreival functions that utilize
|foreign->protocol-entry| will return false when they are at the end
of the protocols database or if there was an error.
The |protocol-entry| structure encapsulates the important elements of
each record in the protocol database.
@c () => (make-protocol-entry protocol-entry? protocol-entry
protocol-entry-name protocol-entry-aliases protocol-entry-value)
@<Datatype definitions@>=
(define-record-type protocol-entry (fields name aliases value))
@ We use a simple convert that allows us to take a foreign entry and turn
it into a |protocol-entry|. This is used in all of the protocol accessor
functions that need to return some protocol.
@c () => (foreign->protocol-entry)
@<Datatype definitions@>=
(define (foreign->protocol-entry x)
(make-protocol-entry
(foreign-protocol-entry-name x)
(foreign-protocol-entry-aliases x)
(foreign-protocol-entry-protocol x)))
@ We follow the standard procedure layout for navigating through
the protocol database. There is an iterator that allows us to traverse
through the protocol database, as well as procedures for closing and
opening the database. These are rarely used, though, and most of the
time a protocol will be retrieved by name or by constant, and there
are two procedures that specifically enable this in the library.
@c () => (next-protocol-entry get-protocol-by-name get-protocol-by-constant
open-protocol-database close-protocol-database)
@<External procedure definitions@>=
(define (next-protocol-entry)
(if-windows?
(unsupported-feature 'next-protocol-entry)
(foreign->protocol-entry ($getprotoent))))
(define (get-protocol-by-name name)
(foreign->protocol-entry ($getprotobyname name)))
(define (get-protocol-by-constant proto)
(foreign->protocol-entry
($getprotobynumber (socket-constant-value proto))))
(define (open-protocol-database keep-alive?)
(if-windows?
(unsupported-feature 'open-protocol-database)
($setprotoent keep-alive?)))
(define (close-protocol-database)
(if-windows?
(unsupported-feature 'close-protocol-database)
($endprotoent)))
@* 2 Binding and listening to sockets.
Binding sockets works with a fairly direct mapping from the traditional
BSD sockets interface, so there isn't much to say here. You bind a given
socket to a given address. 'Nuff said.
\medskip\verbatim
(bind-socket socket address)
!endverbatim
\medskip
\noindent Unless there has been some tragic error, the return value of
this function is unspecified.
@c () => (bind-socket)
@<External procedure definitions@>=
(define (bind-socket sock addr)
(let-values ([(foreign-addr foreign-size)
(socket-address->foreign addr)])
(call-with-errno
(lambda () ($bind (socket-fd sock) foreign-addr foreign-size))
(lambda (ret err)
(foreign-free foreign-addr)
(when (= ret $socket-error)
(socket-error 'bind-socket 'bind err))))))
@ Listening to sockets corresponds directly to the |listen(2)| system
call. It's behavior is the same. The queue length should be a positive
integer not greater than the maximum number of allowed connections.
\medskip\verbatim
(listen-socket socket queue-length)
!endverbatim
\medskip
\noindent |listen-socket| does not return a value.
@c () => (listen-socket)
@<External procedure definitions@>=
(define (listen-socket sock queue-length)
(call-with-errno (lambda () ($listen (socket-fd sock) queue-length))
(lambda (ret err)
(when (= ret $socket-error)
(socket-error 'listen-socket 'listen err)))))
@* 2 Accepting Connections to Sockets.
Because of the interesting interface of |accept(2)| we can't directly
map the interface to Scheme without making a lot of people twitch.
Instead, I take advantage of multiple return values and have
|accept-socket| return two values. The first value is a socket suitable
for talking with the connecting client. The second value returned is the
connecting client's address record.
\medskip\verbatim
(accept-socket socket) => socket address
!endverbatim
\medskip
\noindent Accept also behaves slightly differently depending on whether
the listening socket is blocking or non-blocking. For a blocking socket,
this function will block operation until it receives some connection. In
this case, the only thing you should receive in the normal case is a
socket in the first return value, and a proper address record in the
second. If, however, the listening socket is set to non-blocking, then
accept will return immediately even if there is no existing connection.
If |accept-socket| returns without having a connection to hand over, the
first return value will be false, and the second will be a condition
record indicating the type of error that was returned, such as the
EAGAIN or EWOULDBLOCK error conditions. This will give you some more
information about how to proceed, but not much. It will not return a
condition if the condition would be a true error condition. In this
case, it will raise the error and not return.
Because Chez Scheme will block the GC whenever a foreign function is
running, we have to do some special work to disable the foreign thread
before running a block IO operation like |accept(2)|. However, we don't
need to do that overhead if we are dealing with purely nonblocking
sockets. So, before going to the foreign side, check and call the
appropriate function accordingly.
@c () => (accept-socket)
@<External procedure definitions@>=
(define (accept-socket sock)
(let ([size (foreign-address-size (socket-domain sock))])
(let ([addr (foreign-alloc size)]
[addr-len (make-foreign-size-buffer size)])
(call-with-errno
(lambda ()
((if (socket-nonblocking? sock) $accept $accept-blocking)
(socket-fd sock) addr addr-len))
(lambda (ret err)
(if (= ret invalid-socket)
@<Return intelligently from non-blocking errors@>
@<Build socket and address, then return@>))))))
@ The |accept(2)| system call returns an error state even when the
error is something we intended, such as the case with the EAGAIN and
EWOULDBLOCK errors for non-blocking sockets. This doesn't make sense on
a Scheme interface, so instead, we'll catch the situations where the
errors are mundane and return these through the normal return channels,
and only raise a real error for real errors.
@c (err)
@<Return intelligently from non-blocking errors@>=
(values
#f
(socket-raise/unless 'accept-socket 'accept err
$error-again $error-would-block))
@ In the normal cases, we just need to translate the socket and
extract out the address information.
@c (sock addr addr-len ret)
@<Build socket and address, then return@>=
(values
(make-socket ret
(socket-domain sock)
(socket-type sock)
(socket-protocol sock))
(let ([res (foreign->socket-address
(socket-domain sock) addr addr-len)])
(foreign-free addr)
(foreign-free addr-len)
res))
@* 2 Connecting to the world.
We use |connect-socket| to connect a socket to an endpoint indicated by
the given address.
\medskip\verbatim
(connect-socket socket address) => #t or condition
!endverbatim
\medskip
\noindent Normally a connection succeeds or fails. When it succeeds
|connect-socket| returns true. When it fails, it will raise an error.
However, if that error happens to be an in-progress message, then we
don't raise an error and just return the condition to you.
Since |connect-socket| is also a potentially blocking operation like
|accpet-socket| we use the same technique to choose whether or not to
call the special blocking optimized version of |connect(2)| which
disables the foreign thread before calling |connect(2)|.
This function corresponds to the |connect(2)| system call.
@c () => (connect-socket)
@<External procedure definitions@>=
(define (connect-socket sock addr)
(let-values ([(fa fa-len) (socket-address->foreign addr)])
(call-with-errno
(lambda ()
((if (socket-nonblocking? sock)
$connect
$connect-blocking)
(socket-fd sock) fa fa-len))
(lambda (ret err)
(foreign-free fa)
(or (not (= ret $socket-error))
(socket-raise/unless 'connect-socket
'connect
err
$error-in-progress
$error-would-block))))))
@* 2 Closing and shutting down sockets.
"The normal |close(2)| system call works fine for closing down sockets,
which are just file descriptors. We have a very light wrapping around
this system call.
\medskip\verbatim
(close-socket socket)
!endverbatim
\medskip
\noindent |close-socket| does not return a value.
@c () => (close-socket)