| 76 | | (defun socket-connect (host port &key (protocol :tcp) (element-type 'base-char) |
| | 76 | (defconstant *socket_sock_dgram* 2 |
| | 77 | "Connectionless, unreliable datagrams of fixed maximum length.") |
| | 78 | |
| | 79 | (defconstant *sockopt_so_rcvtimeo* |
| | 80 | #+(not linux) #x1006 |
| | 81 | #+linux 20 |
| | 82 | "Socket receive timeout") |
| | 83 | |
| | 84 | (fli:define-c-struct timeval |
| | 85 | (tv-sec :long) |
| | 86 | (tv-usec :long)) |
| | 87 | |
| | 88 | ;;; ssize_t |
| | 89 | ;;; recvfrom(int socket, void *restrict buffer, size_t length, int flags, |
| | 90 | ;;; struct sockaddr *restrict address, socklen_t *restrict address_len); |
| | 91 | (fli:define-foreign-function (%recvfrom "recvfrom" :source) |
| | 92 | ((socket :int) |
| | 93 | (buffer (:pointer (:unsigned :byte))) |
| | 94 | (length :int) |
| | 95 | (flags :int) |
| | 96 | (address (:pointer (:struct sockaddr))) |
| | 97 | (address-len (:pointer :int))) |
| | 98 | :result-type :int |
| | 99 | #+win32 :module |
| | 100 | #+win32 "ws2_32") |
| | 101 | |
| | 102 | ;;; ssize_t |
| | 103 | ;;; sendto(int socket, const void *buffer, size_t length, int flags, |
| | 104 | ;;; const struct sockaddr *dest_addr, socklen_t dest_len); |
| | 105 | (fli:define-foreign-function (%sendto "sendto" :source) |
| | 106 | ((socket :int) |
| | 107 | (buffer (:pointer (:unsigned :byte))) |
| | 108 | (length :int) |
| | 109 | (flags :int) |
| | 110 | (address (:pointer (:struct sockaddr))) |
| | 111 | (address-len :int)) |
| | 112 | :result-type :int |
| | 113 | #+win32 :module |
| | 114 | #+win32 "ws2_32") |
| | 115 | |
| | 116 | #-win32 |
| | 117 | (defun set-socket-receive-timeout (socket-fd seconds) |
| | 118 | "Set socket option: RCVTIMEO, argument seconds can be a float number" |
| | 119 | (declare (type integer socket-fd) |
| | 120 | (type number seconds)) |
| | 121 | (multiple-value-bind (sec usec) (truncate seconds) |
| | 122 | (fli:with-dynamic-foreign-objects ((timeout (:struct timeval))) |
| | 123 | (fli:with-foreign-slots (tv-sec tv-usec) timeout |
| | 124 | (setf tv-sec sec |
| | 125 | tv-usec (truncate (* 1000000 usec))) |
| | 126 | (if (zerop (setsockopt socket-fd |
| | 127 | *sockopt_sol_socket* |
| | 128 | *sockopt_so_rcvtimeo* |
| | 129 | (fli:copy-pointer timeout |
| | 130 | :type '(:pointer :void)) |
| | 131 | (fli:size-of '(:struct timeval)))) |
| | 132 | seconds))))) |
| | 133 | |
| | 134 | #+win32 |
| | 135 | (defun set-socket-receive-timeout (socket-fd seconds) |
| | 136 | "Set socket option: RCVTIMEO, argument seconds can be a float number. |
| | 137 | On win32, you must bind the socket before use this function." |
| | 138 | (declare (type integer socket-fd) |
| | 139 | (type number seconds)) |
| | 140 | (fli:with-dynamic-foreign-objects ((timeout :int)) |
| | 141 | (setf (fli:dereference timeout) |
| | 142 | (truncate (* 1000 seconds))) |
| | 143 | (if (zerop (setsockopt socket-fd |
| | 144 | *sockopt_sol_socket* |
| | 145 | *sockopt_so_rcvtimeo* |
| | 146 | (fli:copy-pointer timeout |
| | 147 | :type '(:pointer :char)) |
| | 148 | (fli:size-of :int))) |
| | 149 | seconds))) |
| | 150 | |
| | 151 | #-win32 |
| | 152 | (defmethod get-socket-receive-timeout (socket-fd) |
| | 153 | "Get socket option: RCVTIMEO, return value is a float number" |
| | 154 | (declare (type integer socket-fd)) |
| | 155 | (fli:with-dynamic-foreign-objects ((timeout (:struct timeval)) |
| | 156 | (len :int)) |
| | 157 | (getsockopt socket-fd |
| | 158 | *sockopt_sol_socket* |
| | 159 | *sockopt_so_rcvtimeo* |
| | 160 | (fli:copy-pointer timeout |
| | 161 | :type '(:pointer :void)) |
| | 162 | len) |
| | 163 | (fli:with-foreign-slots (tv-sec tv-usec) timeout |
| | 164 | (float (+ tv-sec (/ tv-usec 1000000)))))) |
| | 165 | |
| | 166 | #+win32 |
| | 167 | (defmethod get-socket-receive-timeout (socket-fd) |
| | 168 | "Get socket option: RCVTIMEO, return value is a float number" |
| | 169 | (declare (type integer socket-fd)) |
| | 170 | (fli:with-dynamic-foreign-objects ((timeout :int) |
| | 171 | (len :int)) |
| | 172 | (getsockopt socket-fd |
| | 173 | *sockopt_sol_socket* |
| | 174 | *sockopt_so_rcvtimeo* |
| | 175 | (fli:copy-pointer timeout |
| | 176 | :type '(:pointer :void)) |
| | 177 | len) |
| | 178 | (float (/ (fli:dereference timeout) 1000)))) |
| | 179 | |
| | 180 | (defun open-udp-socket (&key local-address local-port read-timeout) |
| | 181 | "Open a unconnected UDP socket. |
| | 182 | For binding on address ANY(*), just not set LOCAL-ADDRESS (NIL), |
| | 183 | for binding on random free unused port, set LOCAL-PORT to 0." |
| | 184 | (let ((socket-fd (comm::socket *socket_af_inet* *socket_sock_dgram* *socket_pf_unspec*))) |
| | 185 | (if socket-fd |
| | 186 | (progn |
| | 187 | (when read-timeout (set-socket-receive-timeout socket-fd read-timeout)) |
| | 188 | (if local-port |
| | 189 | (fli:with-dynamic-foreign-objects ((client-addr (:struct sockaddr_in))) |
| | 190 | (initialize-sockaddr_in client-addr *socket_af_inet* |
| | 191 | local-address local-port "udp") |
| | 192 | (if (bind socket-fd |
| | 193 | (fli:copy-pointer client-addr :type '(:struct sockaddr)) |
| | 194 | (fli:pointer-element-size client-addr)) |
| | 195 | ;; success, return socket fd |
| | 196 | socket-fd |
| | 197 | (progn |
| | 198 | (close-socket socket-fd) |
| | 199 | (error "cannot bind")))) |
| | 200 | socket-fd)) |
| | 201 | (error "cannot create socket")))) |
| | 202 | |
| | 203 | (defun connect-to-udp-server (hostname service |
| | 204 | &key local-address local-port read-timeout) |
| | 205 | "Something like CONNECT-TO-TCP-SERVER" |
| | 206 | (let ((socket-fd (open-udp-socket :local-address local-address |
| | 207 | :local-port local-port |
| | 208 | :read-timeout read-timeout))) |
| | 209 | (if socket-fd |
| | 210 | (fli:with-dynamic-foreign-objects ((server-addr (:struct sockaddr_in))) |
| | 211 | ;; connect to remote address/port |
| | 212 | (initialize-sockaddr_in server-addr *socket_af_inet* hostname service "udp") |
| | 213 | (if (connect socket-fd |
| | 214 | (fli:copy-pointer server-addr :type '(:struct sockaddr)) |
| | 215 | (fli:pointer-element-size server-addr)) |
| | 216 | ;; success, return socket fd |
| | 217 | socket-fd |
| | 218 | ;; fail, close socket and return nil |
| | 219 | (progn |
| | 220 | (close-socket socket-fd) |
| | 221 | (error "cannot connect")))) |
| | 222 | (error "cannot create socket")))) |
| | 223 | |
| | 224 | ;; Register a special free action for closing datagram usocket when being GCed |
| | 225 | (defun usocket-special-free-action (object) |
| | 226 | (when (and (typep object 'datagram-usocket) |
| | 227 | (%open-p object)) |
| | 228 | (socket-close object))) |
| | 229 | |
| | 230 | (eval-when (:load-toplevel :execute) |
| | 231 | (hcl:add-special-free-action 'usocket-special-free-action)) |
| | 232 | |
| | 233 | (defun socket-connect (host port &key (protocol :stream) (element-type 'base-char) |
| 91 | | (:tcp (let ((hostname (host-to-hostname host)) |
| 92 | | (stream)) |
| 93 | | (setf stream |
| 94 | | (with-mapped-conditions () |
| 95 | | (comm:open-tcp-stream hostname port |
| 96 | | :element-type element-type |
| 97 | | #-lispworks4 #-lispworks4 |
| 98 | | #-lispworks4 #-lispworks4 |
| 99 | | :local-address (when local-host (host-to-hostname local-host)) |
| 100 | | :local-port local-port |
| 101 | | #+(and (not lispworks4) (not lispworks5.0)) |
| 102 | | #+(and (not lispworks4) (not lispworks5.0)) |
| 103 | | :nodelay nodelay))) |
| 104 | | (if stream |
| 105 | | (make-stream-socket :socket (comm:socket-stream-socket stream) |
| 106 | | :stream stream) |
| 107 | | (error 'unknown-error)))) |
| 108 | | (:udp (let ((usocket (make-datagram-socket |
| 109 | | (if (and host port) |
| 110 | | (comm:connect-to-udp-server host port |
| 111 | | :errorp t |
| 112 | | :local-address local-host |
| 113 | | :local-port local-port) |
| 114 | | (comm:open-udp-socket :errorp t |
| 115 | | :local-address local-host |
| 116 | | :local-port local-port)) |
| 117 | | :connected-p t))) |
| 118 | | (hcl:flag-special-free-action usocket) |
| 119 | | usocket)))) |
| | 248 | ((:stream :tcp) |
| | 249 | (let ((hostname (host-to-hostname host)) |
| | 250 | (stream)) |
| | 251 | (setf stream |
| | 252 | (with-mapped-conditions () |
| | 253 | (comm:open-tcp-stream hostname port |
| | 254 | :element-type element-type |
| | 255 | #-lispworks4 #-lispworks4 |
| | 256 | #-lispworks4 #-lispworks4 |
| | 257 | :local-address (when local-host (host-to-hostname local-host)) |
| | 258 | :local-port local-port |
| | 259 | #+(and (not lispworks4) (not lispworks5.0)) |
| | 260 | #+(and (not lispworks4) (not lispworks5.0)) |
| | 261 | :nodelay nodelay))) |
| | 262 | (if stream |
| | 263 | (make-stream-socket :socket (comm:socket-stream-socket stream) |
| | 264 | :stream stream) |
| | 265 | (error 'unknown-error)))) |
| | 266 | ((:datagram :udp) |
| | 267 | (let ((usocket (make-datagram-socket |
| | 268 | (if (and host port) |
| | 269 | (connect-to-udp-server host port |
| | 270 | :local-address local-host |
| | 271 | :local-port local-port) |
| | 272 | (open-udp-socket :local-address local-host |
| | 273 | :local-port local-port)) |
| | 274 | :connected-p t))) |
| | 275 | (hcl:flag-special-free-action usocket) |
| | 276 | usocket)))) |
| 170 | | (setf (%closed-p socket) t)) |
| 171 | | |
| 172 | | ;; Register a special free action for closing datagram usocket when being GCed |
| 173 | | (defun usocket-special-free-action (object) |
| 174 | | (when (and (typep object 'datagram-usocket) |
| 175 | | (not (closed-p object))) |
| 176 | | (socket-close object))) |
| 177 | | |
| 178 | | (eval-when (:load-toplevel :execute) |
| 179 | | (hcl:add-special-free-action 'usocket-special-free-action)) |
| | 327 | (setf (%open-p socket) nil)) |
| | 328 | |
| | 329 | (defvar *message-send-buffer* |
| | 330 | (make-array +max-datagram-packet-size+ |
| | 331 | :element-type '(unsigned-byte 8) |
| | 332 | :allocation :static)) |
| | 333 | |
| | 334 | (defvar *message-send-lock* (mp:make-lock)) |
| | 335 | |
| | 336 | (defun send-message (socket-fd buffer &optional (length (length buffer)) host service) |
| | 337 | "Send message to a socket, using sendto()/send()" |
| | 338 | (declare (type integer socket-fd) |
| | 339 | (type sequence buffer)) |
| | 340 | (let ((message *message-send-buffer*)) |
| | 341 | (fli:with-dynamic-foreign-objects ((client-addr (:struct sockaddr_in)) |
| | 342 | (len :int |
| | 343 | #-(or lispworks3 lispworks4 lispworks5.0) |
| | 344 | :initial-element |
| | 345 | (fli:size-of '(:struct sockaddr_in)))) |
| | 346 | (fli:with-dynamic-lisp-array-pointer (ptr message :type '(:unsigned :byte)) |
| | 347 | (mp:with-lock (*message-send-lock*) |
| | 348 | (replace message buffer :end2 length) |
| | 349 | (if (and host service) |
| | 350 | (progn |
| | 351 | (initialize-sockaddr_in client-addr *socket_af_inet* host service "udp") |
| | 352 | (%sendto socket-fd ptr (min length +max-datagram-packet-size+) 0 |
| | 353 | (fli:copy-pointer client-addr :type '(:struct sockaddr)) |
| | 354 | (fli:dereference len))) |
| | 355 | (%send socket-fd ptr (min length +max-datagram-packet-size+) 0))))))) |
| 183 | | (comm:send-message s buffer length address port))) |
| | 359 | (send-message s buffer length address port))) |
| | 360 | |
| | 361 | (defvar *message-receive-buffer* |
| | 362 | (make-array +max-datagram-packet-size+ |
| | 363 | :element-type '(unsigned-byte 8) |
| | 364 | :allocation :static)) |
| | 365 | |
| | 366 | (defvar *message-receive-lock* (mp:make-lock)) |
| | 367 | |
| | 368 | (defun receive-message (socket-fd &optional buffer (length (length buffer)) |
| | 369 | &key read-timeout (max-buffer-size +max-datagram-packet-size+)) |
| | 370 | "Receive message from socket, read-timeout is a float number in seconds. |
| | 371 | |
| | 372 | This function will return 4 values: |
| | 373 | 1. receive buffer |
| | 374 | 2. number of receive bytes |
| | 375 | 3. remote address |
| | 376 | 4. remote port" |
| | 377 | (declare (type integer socket-fd) |
| | 378 | (type sequence buffer)) |
| | 379 | (let ((message *message-receive-buffer*) |
| | 380 | old-timeout) |
| | 381 | (fli:with-dynamic-foreign-objects ((client-addr (:struct sockaddr_in)) |
| | 382 | (len :int |
| | 383 | #-(or lispworks3 lispworks4 lispworks5.0) |
| | 384 | :initial-element |
| | 385 | (fli:size-of '(:struct sockaddr_in)))) |
| | 386 | (fli:with-dynamic-lisp-array-pointer (ptr message :type '(:unsigned :byte)) |
| | 387 | ;; setup new read timeout |
| | 388 | (when read-timeout |
| | 389 | (setf old-timeout (get-socket-receive-timeout socket-fd)) |
| | 390 | (set-socket-receive-timeout socket-fd read-timeout)) |
| | 391 | (mp:with-lock (*message-receive-lock*) |
| | 392 | (let ((n (%recvfrom socket-fd ptr max-buffer-size 0 |
| | 393 | (fli:copy-pointer client-addr :type '(:struct sockaddr)) |
| | 394 | len))) |
| | 395 | ;; restore old read timeout |
| | 396 | (when (and read-timeout (/= old-timeout read-timeout)) |
| | 397 | (set-socket-receive-timeout socket-fd old-timeout)) |
| | 398 | (if (plusp n) |
| | 399 | (values (if buffer |
| | 400 | (replace buffer message |
| | 401 | :end1 (min length max-buffer-size) |
| | 402 | :end2 (min n max-buffer-size)) |
| | 403 | (subseq message 0 (min n max-buffer-size))) |
| | 404 | (min n max-buffer-size) |
| | 405 | (ntohl (fli:foreign-slot-value |
| | 406 | (fli:foreign-slot-value client-addr |
| | 407 | 'sin_addr |
| | 408 | :object-type '(:struct sockaddr_in) |
| | 409 | :type '(:struct in_addr) |
| | 410 | :copy-foreign-object nil) |
| | 411 | 's_addr |
| | 412 | :object-type '(:struct in_addr))) |
| | 413 | (ntohs (fli:foreign-slot-value client-addr |
| | 414 | 'sin_port |
| | 415 | :object-type '(:struct sockaddr_in) |
| | 416 | :type '(:unsigned :short) |
| | 417 | :copy-foreign-object nil))) |
| | 418 | (values nil n 0 0)))))))) |