Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

F#でソケットプログラミング入門

More than 1 year has passed since last update.

F#とソケットプログラミングを勉強中なので両方を使って何か作ってみたい。
定番のEchoサーバと、コンソールで使えるチャットサーバも挑戦してみた。

Echoサーバ

https://github.com/dyoshikawa/fsharp-echo-server

サーバ

open System.IO
open System.Net
open System.Net.Sockets
open System.Text

[<EntryPoint>]
let main _argv =
    let endPoint = new IPEndPoint(IPAddress.Any, 8080)
    let server = new Socket(AddressFamily.InterNetwork,
                            SocketType.Stream,
                            ProtocolType.Tcp)
    server.Bind(endPoint)
    server.Listen(10)

    printfn "server started (%s)" <| server.LocalEndPoint.ToString()

    let rec serve () =
        let client = server.Accept()
        printfn "client accepted (%s)" <| client.RemoteEndPoint.ToString()

        let rec read () =
            let buffer = [| for _ in 1..10 -> byte(0) |]
            let len = client.Receive(buffer)
            if len = 0 then
                printfn "接続が切れました"
                client.Close()
                ()
            else
                printfn "%s" <| Encoding.Default.GetString(buffer, 0, len)
                client.Send(buffer) |> ignore
                read ()
        read ()

        client.Close()

        serve ()

    serve () |> ignore

    0

クライアント

open System.IO
open System.Net
open System.Net.Sockets
open System.Text

[<EntryPoint>]
let main _argv =
    printfn "送信する文字列を入力して下さい"
    let input = stdin.ReadLine()

    let socket = new Socket(AddressFamily.InterNetwork,
                            SocketType.Stream,
                            ProtocolType.Tcp)

    let endPoint = new IPEndPoint(IPAddress.Loopback, 8080)

    printfn "接続開始"
    socket.Connect(endPoint)

    let sendBuf = Encoding.Default.GetBytes(input)
    let sendBufLen = sendBuf.Length

    printfn "送信開始"
    let sendResult = socket.Send(sendBuf)
    if sendResult = -1 then
        printfn "送信失敗"
        socket.Close()
        1
    else
        printfn "送信完了"
        printfn "受信開始"
        let memory = new MemoryStream()
        let rec receive (total : int) : unit =
            if total >= sendBufLen then
                printfn "受信完了"
                printfn "接続終了"
                socket.Close()
                ()
            else
                let buffer = [| for _ in 1..10 -> byte(0) |]
                let len = socket.Receive(buffer)
                memory.Write(buffer, 0, len)
                receive (total + len)
        receive 0
        printfn "受信: %s" <| Encoding.Default.GetString(memory.GetBuffer())
        socket.Close()
        0

動作確認

dotnet run --project Server

でサーバを起動。

dotnet run --project Client

でクライアント起動。
何か文字列を入力して改行すると送信。
その後のサーバからの受信内容を表示する。

チャットサーバ

https://github.com/dyoshikawa/fsharp-tcp-chat

サーバ

open System.IO
open System.Net
open System.Net.Sockets
open System.Text

[<EntryPoint>]
let main _argv =
    printfn "接続先を入力して下さい"
    let inputIp = stdin.ReadLine()

    let socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

    let ip =
        match inputIp with
        | "" -> IPAddress.Loopback
        | _ -> IPAddress.Parse(inputIp)

    let endPoint = IPEndPoint(ip, 8080)

    printfn "接続開始"
    socket.Connect(endPoint)
    printfn "接続成功"
    printfn "チャット開始"

    async {
        let rec receive(): unit =
            let buf =
                [| for _ in 1 .. 10 -> byte (0) |]

            let len = socket.Receive(buf)
            printf "%s" <| Encoding.Default.GetString(buf)
            receive()
        receive()
    }
    |> Async.Start

    let rec sendText(): int =
        let inputText = stdin.ReadLine()

        let sendBuf =
            Encoding.Default.GetBytes(sprintf "%s: %s\r\n" (socket.LocalEndPoint.ToString()) inputText)

        let sendResult = socket.Send(sendBuf)
        if sendResult = -1 then
            printfn "送信失敗"
            socket.Close()
            1
        else
            sendText()
    sendText()

複数のクライアントからの受信監視をマルチスレッドで実現している。
すべてのクライアント情報を突っ込んだ配列をスレッド間で共有したかったため、スレッドセーフなBlockingCollectionを使用。

クライアント

open System.Collections.Concurrent
open System.Net
open System.Net.Sockets
open System.Text

let sendBufToClients (clients: BlockingCollection<Socket>) (own: Socket) (buf: byte []): unit =
    for c in clients do
        if c.Equals(own) then ()
        else c.Send(buf) |> ignore

[<EntryPoint>]
let main _argv =
    let endPoint = IPEndPoint(IPAddress.Any, 8080)
    let socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
    socket.Bind(endPoint)
    socket.Listen(10)

    printfn "チャットサーバ開始 (%s)" <| socket.LocalEndPoint.ToString()

    let clients = new BlockingCollection<Socket>()

    let rec launch() =
        let client = socket.Accept()
        clients.Add(client)

        let newClientMsg =
            sprintf "入室 %s\r\n"
            <| client.RemoteEndPoint.ToString()
        printfn "%s" newClientMsg
        sendBufToClients clients client <| Encoding.Default.GetBytes newClientMsg

        async {
            let rec read() =
                let buffer =
                    [| for _ in 1 .. 10 -> byte (0) |]

                let len = client.Receive(buffer)
                if len = 0 then
                    let exitClientMsg =
                        sprintf "退室 %s\r\n"
                        <| client.RemoteEndPoint.ToString()
                    printfn "%s" exitClientMsg
                    sendBufToClients clients client <| Encoding.Default.GetBytes exitClientMsg
                    client.Close()
                    ()
                else
                    printf "%s" <| Encoding.Default.GetString(buffer, 0, len)
                    sendBufToClients clients client buffer
                    read()
            read()
        }
        |> Async.Start

        launch()

    launch() |> ignore

    0

動作確認

dotnet run --project Server

でサーバを起動。

dotnet run --project Client

でクライアントを起動。
サーバに接続し、発言が入力待ちになるので、文字を入力して改行でメッセージを送信する。
サーバ側でメッセージを受け取り、他のクライアントへ送信する。

参考

TCP/IPソケットプログラミング C言語編
https://webbibouroku.com/Blog/Article/socket-server
http://karoten512.hatenablog.com/entry/2018/03/21/234156
https://ameblo.jp/tasoh/entry-10274850055.html
https://smdn.jp/programming/netfx/tips/echo_server/

dyoshikawa
広島でクジラTシャツを着て仕事しています
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away