File: socket.scm

package info (click to toggle)
scheme48 1.8%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 14,980 kB
  • ctags: 14,127
  • sloc: lisp: 76,272; ansic: 71,514; sh: 3,026; makefile: 637
file content (283 lines) | stat: -rw-r--r-- 9,502 bytes parent folder | download | duplicates (4)
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
; Copyright (c) 1993-2008 by Richard Kelsey and Jonathan Rees. See file COPYING.

; Server interface
;   (open-socket [socket-number]) -> socket
;   (close-socket socket)
;   (socket-accept socket) -> [input-port output-port]
;   (get-host-name) -> string
;   (socket-port-number socket) -> integer

; Client interface
;   (socket-client host-name socket-number) -> [input-port output-port]

; (get-host-by-name name) -> address
; (get-host-by-address address) -> name

; Old calls I would like to get rid off.
;   (socket-listen socket) -> [input-port output-port]
;   (socket-listen-channels socket) -> [input-channel output-channel]
;   (socket-client-channels host-name socket-number) -> [input-channels output-channels]

;--------------------
; Socket type
;
; A socket has a channel (for accepting connections) and a port number.
; These are only used for servers and udp sockets; clients don't need them.

(define-record-type socket :socket
  (really-make-socket type channel port-number condvar)
  socket?
  (type        socket-type)	; SOCKET, UPD-INPUT-SOCKET, UDP-OUTPUT-SOCKET
  (channel     socket-channel)
  (port-number socket-port-number)
  (condvar     socket-condvar))	; for blocking until a connection arrives

(define (make-socket type channel)
  (really-make-socket type
		      channel
		      (socket-number channel)
		      (make-condvar)))

(define-record-discloser :socket
  (lambda (s)
    `(,(socket-type s) ,(socket-port-number s))))

; Close the channel, notifying any waiters that this has happened.

(define (close-socket socket)
  (let ((channel (socket-channel socket))
	(close-channel (case (socket-type socket)
			 ((socket)           close-channel)
			 ((udp-input-socket) close-socket-input-channel)
			 (else               close-socket-output-channel))))
    (with-new-proposal (lose)
      (or (channel-maybe-commit-and-close channel close-channel)
	  (lose)))))

; Makes a server socket.

(define (open-socket . maybe-number)
  (let ((channel (new-socket #f #t)))
    (bind-socket channel (if (or (null? maybe-number)
				 (= (car maybe-number) 0))  ; old, crappy spec
			     #f
			     (car maybe-number)))
    (real-socket-listen channel)
    (make-socket 'socket channel)))

(define (socket-accept socket)
  (call-with-values
   (lambda ()
     (socket-listen-channels socket))
   (lambda (in out)
     (values (input-channel+closer->port in close-socket-input-channel)
	     (output-channel+closer->port out close-socket-output-channel)))))
  
(define socket-listen socket-accept)

(define (socket-listen-channels socket)
  (let ((input-channel (blocking-socket-op socket real-socket-accept)))
    (values input-channel
	    (dup-socket-channel input-channel))))

; Keep performing OP until it returns a non-#F value.  In between attempts we
; block on the socket's channel.

(define (blocking-socket-op socket op)
  (let ((channel (socket-channel socket))
	(condvar (socket-condvar socket)))
    (let loop ((retry? #f))
      (disable-interrupts!)
      (cond ((op channel retry?)
	     => (lambda (result)
		  (enable-interrupts!)
		  result))
	    (else
	     (wait-for-channel channel condvar)
	     (with-new-proposal (lose)
	       (maybe-commit-and-wait-for-condvar condvar))
	     (enable-interrupts!)
	     (loop #t))))))

; Connect to the socket and return input and output ports.

(define (socket-client host-name port-number)
  (call-with-values
   (lambda ()
     (socket-client-channels host-name port-number))
   (lambda (in out)
     (values (input-channel+closer->port in close-socket-input-channel)
	     (output-channel+closer->port out close-socket-output-channel)))))

; FreeBSD's connect() behaves oddly.  If you get told to wait, wait for select()
; to signal the all-clear, and then try to connect again, you get an `already
; connected' error.  To handle this we pass in a RETRY? flag.  If RETRY? is
; true the `already connected' error is ignored.

(define (socket-client-channels host-name port-number)
  (let ((channel (new-socket #f #f)))
    (let loop ((retry? #f))
      (disable-interrupts!)
      (let ((output-channel (real-socket-connect channel
						 (get-host-by-name host-name)
						 port-number
						 retry?)))
	(cond ((channel? output-channel)
	       (enable-interrupts!)
	       (values channel output-channel))
	      ((eq? output-channel #t)
	       (error "client socket already connected" host-name port-number))
	      (else
	       (let ((condvar (make-condvar)))
		 (wait-for-channel channel condvar)
		 (with-new-proposal (lose)
		   (maybe-commit-and-wait-for-condvar condvar))
		 (enable-interrupts!)
		 (loop #t))))))))

(define (get-host-by-xxx retval get-result)
  (if (pair? retval)
      (let ((result #f))
	(dynamic-wind ; we need to release the uid in case the thread gets killed
	    values
	    (lambda ()
	      (wait-for-external-event (car retval)))
	    (lambda ()
	      (set! result (get-result (cdr retval)))))
	result)
      retval))

(define (get-host-by-name name)
  (get-host-by-xxx (real-get-host-by-name (host-name->byte-vector name))
		   get-host-by-name-result))

(define (get-host-by-address address)
  (get-host-by-xxx (real-get-host-by-address address)
		   get-host-by-address-result))

;; #### This needs to be IDNA

(define (host-name->byte-vector host)
  (let* ((size (string-length host))
	 (b (make-byte-vector (+ size 1) 0)))
    (do ((i 0 (+ 1 i)))
	((= i size))
      (let ((code (char->integer (string-ref host i))))
	(if (< code 128)
	    (byte-vector-set! b i code)
	    (byte-vector-set! b i #x3f)))) ; ?
    b))

;----------------
; UDP stuff
;
; For UDP messages we need to specify the destination address and receive the
; sender's address.

(define-record-type udp-address :udp-address
  (udp-addresses-are-made-from-c-code)
  udp-address?
  (address  udp-address-address)	; byte vector
  (port     udp-address-port)		; port number
  (hostname real-udp-address-hostname set-udp-address-hostname!))	; string

(define (udp-address-hostname addr)
  (or (real-udp-address-hostname addr)
      (let ((name (get-host-by-address addr)))
	(set-udp-address-hostname! addr name)
	name)))

(define-record-discloser :udp-address
  (lambda (s)
    `(udp-address ,(udp-address-hostname s) ,(udp-address-port s))))

; Export the binding to C for type-checking and making udp-addresses.

(define-exported-binding "s48-udp-address-type" :udp-address)

; Open a UDP socket, returning the two sides.  If a socket port is specified
; it is given to the input half.

(define (open-udp-socket . maybe-port)
  (let* ((input-channel (new-socket #t #t))
	 (output-channel (dup-socket-channel input-channel)))
    (bind-socket input-channel
		 (if (null? maybe-port)
		     #f
		     (car maybe-port)))
    (values (make-socket 'udp-input-socket  input-channel)
	    (make-socket 'udp-output-socket output-channel))))

; Sending and receiving using UPD sockets.

(define (udp-send socket address buffer count)
  (if (not (and (socket? socket)
		(eq? (socket-type socket)
		     'udp-output-socket)))
      (call-error "not a UDP output socket" udp-send socket address buffer count))
  (blocking-socket-op socket
		      (lambda (channel retry?)
			(real-udp-send channel address buffer count))))

(define (udp-receive socket buffer)
  (if (not (and (socket? socket)
		(eq? (socket-type socket)
		     'udp-input-socket)))
      (call-error "not a UDP input socket" udp-receive socket buffer))
  (let ((got (blocking-socket-op socket
				 (lambda (channel retry?)
				   (real-udp-receive channel buffer)))))
    (values (car got) (cdr got))))

(define (lookup-udp-address name port)
  (real-lookup-udp-address (host-name->byte-vector name)
			   port))

(define (lookup-udp-address name port)
  (real-lookup-udp-address (get-host-by-name name)
			   port))
;----------------
; We need to explicitly close socket channels.

(define (close-socket-input-channel channel)
  (close-socket-half channel #t)
  (close-channel channel))

(define (close-socket-output-channel channel)
  (close-socket-half channel #f)
  (close-channel channel))

;----------------
; The C calls we use.  These are in c/unix/socket.c.

(import-lambda-definition new-socket (upd? input?) "s48_socket")
(import-lambda-definition bind-socket (socket number) "s48_bind")
(import-lambda-definition socket-number (socket) "s48_socket_number")
(import-lambda-definition real-socket-listen (socket) "s48_listen")
(import-lambda-definition real-socket-accept (socket retry?) "s48_accept")
(import-lambda-definition real-socket-connect (socket
					       address
					       port-number
					       retry?)
			  "s48_connect")
(import-lambda-definition dup-socket-channel (socket)
			  "s48_dup_socket_channel")
(import-lambda-definition close-socket-half (socket input?)
			  "s48_close_socket_half")
(import-lambda-definition real-get-host-by-name (name) "s48_get_host_by_name")
(import-lambda-definition get-host-by-name-result (event-uid)
			  "s48_get_host_by_name_result")
(import-lambda-definition real-get-host-by-address (address) "s48_get_host_by_address")
(import-lambda-definition get-host-by-address-result (event-uid)
			  "s48_get_host_by_address_result")
(import-lambda-definition get-host-name () "s48_get_host_name")

; UDP calls
(import-lambda-definition real-udp-send (socket address buffer count)
			  "s48_udp_send")
(import-lambda-definition real-udp-receive (socket buffer)
			  "s48_udp_receive")
(import-lambda-definition real-lookup-udp-address (address port)
			  "s48_lookup_udp_address")