F#とソケットプログラミングを勉強中なので両方を使って何か作ってみたい。
定番のEchoサーバと、コンソールで使えるチャットサーバも挑戦してみた。
Echoサーバ
サーバ
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
でクライアント起動。
何か文字列を入力して改行すると送信。
その後のサーバからの受信内容を表示する。
チャットサーバ
サーバ
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/