IO::Socket::Async — 비동기 소켓

IO::Socket::Async — 비동기 소켓

IO::Socket::Async비동기 소켓 을 제공해요. 서버 쪽과 클라이언트 쪽 모두에서 쓸 수 있죠.

다음은 포트 3333에서 듣는 간단한 "hello world" HTTP 서버 예시예요.

react {
    whenever IO::Socket::Async.listen('0.0.0.0', 3333) -> $conn {
        await $conn.print: qq:heredoc/END/;
            HTTP/1.1 200 OK
            Content-Type: text/html; charset=UTF-8
            Content-Encoding: UTF-8

            <html>
            <body>
                <h1>Incoming request:</h1>
            <pre>
            END
        whenever $conn.Supply.lines -> $line {
            $conn.say: $line;
            if $line ~~ "" {
                await $conn.print: "</pre></body></html>";
                $conn.close;
            }
            LAST { say "closed connection"; }
        }
    }
    CATCH {
        default {
            say .^name, ': ', .Str;
            say "handled in $?LINE";
        }
    }
}

그리고 이 서버에 연결해 서버가 응답한 내용을 출력하는 클라이언트는 이렇게 생겼어요.

await IO::Socket::Async.connect('127.0.0.1', 3333).then( -> $promise {
    given $promise.result {
        .print("Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n");
        react {
            whenever .Supply() -> $v {
                $v.print;
                done;
            }
        }
        .close;
    }
});

IO::Socket::AsyncUDP 메시지도 보내고 받을 수 있어요. 받은 모든 데이터를 출력하는 UDP 서버 예시는 이렇죠.

my $socket = IO::Socket::Async.bind-udp('localhost', 3333);

react {
    whenever $socket.Supply -> $v {
        if $v.chars > 0 {
            say $v;
        }
    }
}

그리고 이에 대응하는 클라이언트는 이렇게 될 수 있어요.

my $socket = IO::Socket::Async.udp();
await $socket.print-to('localhost', 3333, "Hello, Raku!");

이런 소켓에서 발생할 수 있는 문제(예: 포트가 이미 사용 중인 경우)를 구체적으로 처리하려면 CATCH phaser를 넣을 수 있어요.

react {
    whenever IO::Socket::Async.listen('0.0.0.0', 3000) -> $conn {
        whenever $conn.Supply.lines -> $line {
            $conn.print: qq:heredoc/END/;
                HTTP/1.1 200 OK
                Content-Type: text/html; charset=UTF-8
                Content-Encoding: UTF-8

                <html>
                <body>
                    <h1>Hello World!</h1>
                    <p>{ $line }</p>
                </body>
                </html>
                END
            $conn.close;
        }
        QUIT {
            default {
                say .^name, '→ ', .Str;
                say "handled in line $?LINE";
            }
        }
    }

}
# Will print this, if address 3000 is already in use:
# X::AdHoc→ address already in use
# handled in 23

CATCH 같은 다른 phaser를 쓸 때와의 주요 차이는, 이런 종류의 예외가 whenever 블록 안에서 잡히고, 프로그램을 종료할지 말지를 당신이 통제할 수 있다는 점이에요.

출처: Raku Documentation — IO::Socket::Async

본문

IO::Socket::Async는 직접 생성할 수 없어요. TCP 연결에는 connectlisten, UDP 데이터에는 udpbind-udp를 써서 클라이언트나 서버를 각각 만들어야 해요.

method connect

method connect(Str $host, Int $port --> Promise)

$host$port로 지정된 TCP 서버에 연결을 시도하고, 연결된 IO::Socket::Async로 keep되거나 연결할 수 없으면 broken되는 Promise를 반환해요.

method connect-path

method connect-path(Str $path --> Promise)

$path로 지정된 유닉스 도메인 스트림 소켓에 연결을 시도하고, 연결된 IO::Socket::Async로 keep되거나 연결할 수 없으면 broken되는 Promise를 반환해요.

method listen

method listen(Str $host, Int $port --> Supply)

지정된 $host$port리스닝 소켓을 만들고, 수락된 클라이언트 IO::Socket::Async들이 방출될 Supply를 반환해요. 이 Supply를 tap하면 클라이언트 연결을 받기 시작해요. 운영체제가 포트를 알아서 정해주길 원한다면 $port0으로 설정할 수도 있어요.

반환된 supply에 tap 메서드를 호출해서 얻는 IO::Socket::Async::ListenSocket은 실제 리스닝 TCP 소켓을 나타내며, close 메서드로 닫을 수 있어요. $port0이었다면, socket-port 메서드로 소켓이 최종적으로 사용한 포트를 알 수 있어요.

method listen-path

method listen-path(Str $path)

지정된 $path유닉스 도메인 스트림 리스닝 소켓을 만들고, 수락된 클라이언트 IO::Socket::Async들이 방출될 Supply를 반환해요. 이 Supply를 tap하면 클라이언트 연결을 받기 시작해요.

반환된 supply에 tap 메서드를 호출해서 얻는 IO::Socket::Async::ListenSocket은 실제 리스닝 TCP 소켓을 나타내며, close 메서드로 닫을 수 있어요.

method udp

method udp(IO::Socket::Async:U: :$broadcast --> IO::Socket::Async)

print-towrite-to로 UDP 메시지를 보내도록 설정된, 초기화된 IO::Socket::Async 클라이언트 객체를 반환해요. :broadcast 부사는 SO_BROADCAST 옵션을 켜서 소켓이 브로드캐스트 주소로 패킷을 보낼 수 있게 해줘요.

method bind-udp

method bind-udp(IO::Socket::Async:U: Str() $host, Int() $port, :$broadcast --> IO::Socket::Async)

지정된 $host$port로 보내진 UDP 메시지를 받도록 설정된, 초기화된 IO::Socket::Async 서버 객체를 반환해요. TCP 소켓에서의 listen과 동일한 역할이죠. :broadcast 부사를 지정하면 브로드캐스트 주소로 보내진 메시지도 받을 수 있어요.

method print

method print(IO::Socket::Async:D: Str $str --> Promise)

connectlisten을 통해 간접적으로 얻은 IO::Socket::Async$str을 보내려 시도하고, 보낸 바이트 수로 keep되거나 보내는 중 오류가 나면 broken되는 Promise를 반환해요.

method print-to

method print-to(IO::Socket::Async:D: Str() $host, Int() $port, Str() $str --> Promise)

udp 메서드로 만든 UDP 소켓을 위한 print의 동등물이에요. $str의 UDP 메시지를 지정된 $host$port로 보내려 시도하고, 데이터가 성공적으로 보내지면 keep되거나 데이터를 보낼 수 없으면 broken되는 Promise를 반환해요. 브로드캐스트 주소로 보내려면 소켓 생성 시 :broadcast 플래그를 지정했어야 해요.

method write

method write(IO::Socket::Async:D: Blob $b --> Promise)

connectlisten을 통해 간접적으로 얻은 IO::Socket::Async$b의 바이트를 보내려 시도하고, 보낸 바이트 수로 keep되거나 보내는 중 오류가 나면 broken되는 Promise를 반환해요.

method write-to

method write-to(IO::Socket::Async:D: Str() $host, Int() $port, Blob $b --> Promise)

udp 메서드로 만든 UDP 소켓을 위한 write의 동등물이에요. Blob $b의 바이트로 이루어진 UDP 메시지를 지정된 $host$port로 보내려 시도하고, 데이터가 성공적으로 보내지면 keep되거나 데이터를 보낼 수 없으면 broken되는 Promise를 반환해요. 브로드캐스트 주소로 보내려면 소켓 생성 시 :broadcast 플래그를 지정했어야 해요.

method Supply

method Supply(:$bin, :$buf = buf8.new --> Supply)

연결된 IO::Socket::Async에서 도착하는 데이터를 얻기 위해 tap할 수 있는 Supply를 반환해요. 기본적으로 데이터는 문자(characters)로 방출되지만, :bin 부사를 주면 대신 바이트 Buf가 방출돼요. 이 경우 :buf 명명 인자로 자신의 Buf를 제공할 수도 있어요.

UDP 소켓은 문자 모드에서 각 패킷을 하나의 완전한 메시지로 취급하고 디코딩해요. 디코딩 오류가 발생하면 Supplyquit해요.

반면 TCP 소켓은 들어오는 패킷을 스트림의 일부로 취급하고, 바이트를 스트리밍 디코더에 넣은 뒤 디코더가 준비됐다고 판단한 문자들을 방출해요. Raku에서 문자열은 그래핌(grapheme) 수준으로 동작하므로, 완전한 그래핌만 방출돼요. 예를 들어 UTF-8 인코딩을 쓰는데 패킷의 마지막 바이트가 a로 디코딩됐다면, 다음 패킷에 a와 함께 하나의 그래핌을 이룰 결합 문자가 올 수 있기 때문에 이 값은 방출되지 않아요. 제어 문자(예: \n)는 항상 그래핌 경계 역할을 하므로, 줄바꿈이나 null 바이트를 종결자로 쓰는 텍스트 기반 프로토콜은 별도 처리할 필요가 없어요. TCP 소켓도 디코딩 오류 시 quit해요.

method close

method close(IO::Socket::Async:D: )

listen Supplyconnect Promise에서 얻은 연결된 클라이언트 IO::Socket::Async를 닫아요.

listen이 만든 실제 리스닝 소켓을 닫으려면 IO::Socket::Async::ListenSocketclose하면 돼요. 예시는 listen을 참고하세요.

method socket-host

method socket-host(--> Str)

이 소켓의 로컬 끝의 IP 주소를 반환해요.

method peer-host

method peer-host(--> Str)

이 소켓의 원격 끝의 IP 주소를 반환해요.

method socket-port

method socket-port(--> Int)

이 소켓의 로컬 끝의 포트를 반환해요.

method peer-port

method peer-port(--> Int)

이 소켓의 원격 끝의 포트를 반환해요.

method native-descriptor

method native-descriptor(--> Int)

이 소켓의 파일 디스크립터 를 반환해요.