I'm creating a tcp server who accept all the connection and execute incomming data has command line, but when i send "exit" to the tcpsocket, the process and the socket dont close properly
# main.cr
require "socket"
PORT = 2022
def handle_connection(socket)
Process.run("/bin/sh", input: socket, output: socket, error: socket)
end
server = TCPServer.new(PORT)
loop do
if socket = server.accept?
spawn handle_connection(socket)
else
break
end
end
for example, the following code work fine, after sending "exit" to STDIN, the shell is exiting, "process ending" is printedand the program close
channel = Channel(Nil).new
spawn do
Process.run("/bin/sh", input: STDIN, output: STDOUT, error: STDERR)
puts "process ending"
channel.send(nil)
end
channel.receive
for debuggin purpose i have tested this code too but "process ending" was never print until i manually close the tcp socket
# main.cr
require "socket"
PORT = 2022
def handle_connection(socket)
Process.run("/bin/sh", input: socket, output: socket, error: socket)
puts "process ending"
end
server = TCPServer.new(PORT)
loop do
if socket = server.accept?
spawn handle_connection(socket)
else
break
end
end
when i run main.cr, nc localhost 2022 and send "exit" i expect the socket close properly but he dont, and when i send more command the program raise an error
Unhandled exception in spawn: Error writing file: Broken pipe (Errno)
from /usr/lib/crystal/crystal/system/unix/file_descriptor.cr:79:13 in 'unbuffered_write'
from /usr/lib/crystal/io/buffered.cr:122:14 in 'write'
from /usr/lib/crystal/io.cr:1130:7 in 'copy'
from /usr/lib/crystal/process.cr:413:7 in 'copy_io'
from /usr/lib/crystal/process.cr:409:11 in 'copy_io:close_dst'
from /usr/lib/crystal/process.cr:298:17 in '->'
from /usr/lib/crystal/fiber.cr:255:3 in 'run'
from /usr/lib/crystal/fiber.cr:47:34 in '->'
from ???
This is a known issue with Crystal. There is an issue open here:
https://github.com/crystal-lang/crystal/issues/7810
Related
I have 2 elixir applications.
In one of them I create a TCP server which listens on the port 8080 for packets. I know that it is defined correctly, because I connected to it with telnet and everything was working fine.
The problem appears when I try to connect to this TCP server from my other Elixir application.
That's how I connect to it
host = 'localhost'
{:ok, socket} = :gen_tcp.connect(host, 8080, [])
Not sure about the options that I should specify tho.
When trying to connect to it I get in the logs of the application with TCP server:
00:21:11.235 [error] Task #PID<0.226.0> started from MessageBroker.Controller terminating
** (MatchError) no match of right hand side value: {:error, :closed}
(message_broker 0.1.0) lib/message_broker/network/controller.ex:110: MessageBroker.Controller.read_line/1
(message_broker 0.1.0) lib/message_broker/network/controller.ex:101: MessageBroker.Controller.serve/1
(elixir 1.12.3) lib/task/supervised.ex:90: Task.Supervised.invoke_mfa/2
(stdlib 3.12) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Function: #Function<0.126110026/0 in MessageBroker.Controller.loop_acceptor/1>
Args: []
At the line with
{:ok, data} = :gen_tcp.recv(socket, 0)
Any ideas, suggestions?
(MatchError) no match of right hand side value: {:error, :closed}
At the line with
{:ok, data} = :gen_tcp.recv(socket, 0)
The error message is saying that the function call :gen_tcp.recv(socket, 0) returned {:error, :closed} and that {:ok, data} does not match {:error, :closed}. If you call recv() on a socket that was closed by the other side, then recv() returns {:error, :closed}, and the other side will not be sending anymore data to that socket.
Not sure about the options that I should specify tho.
Yeah, those are pretty important. The basic tenet is that when you send data to a socket, you have no idea how many chunks the data will be split into. But, the client and server have to be able to know when to stop trying to read data because the end of the data has been reached. To solve the "indeterminate number of chunks problem", the client and server have to agree on some signal to mark the end of the data:
A newline?
The sender closes the socket?
Use an agreed upon number of bytes at the start of the data to specify the length of the data?
For #3, send() and recv() will automatically handle packaging and unpackaging the data for you if you simply tell gen_tcp how many bytes are necessary to specify the length of a message, for instance you can specify the option {:packet, 2}. If you do that, send() will automatically calculate the length of the data, then add 2 bytes to the front of the data containing the length of the data. Likewise, recv() will automatically read the first 2 bytes from the socket, then read the integer contained in those 2 bytes, say 64,999, then recv() will wait until it has read an additional 64,999 bytes from the socket, then it will return the whole 64,999 bytes of data.
How to send a message through TCP protocol to a TCP server using
gen_tcp in Elixir?
Here is an example of #1, where a newline is used to mark the end of the data:
TCP Server:
defmodule App1 do
def start_tcp_server do
port = 8080
{:ok, listen_socket} = :gen_tcp.listen(
port,
[
{:mode, :binary}, #Received data is delivered as a string (v. a list)
{:active, :false}, #Data sent to the socket will not be
#placed in the process mailbox, so you can't
#use a receive block to read it. Instead
#you must call recv() to read data directly
#from the socket.
{:packet, :line}, #recv() will read from the socket until
#a newline is encountered, then return.
#If a newline is not read from the socket,
#recv() will hang until it reads a newline
#from the socket.
{:reuseaddr, true} #Allows you to immediately restart the server
#with the same port, rather than waiting
#for the system to clean up and free up
#the port.
]
)
IO.puts "Listening on port #{port}...."
listen_loop(listen_socket)
end
defp listen_loop(listen_socket) do
{:ok, client_socket} = :gen_tcp.accept(listen_socket) #client_socket is created with the same options as listen_socket
handle_client(client_socket)
listen_loop(listen_socket)
end
defp handle_client(client_socket) do
case :gen_tcp.recv(client_socket, 0) do #Do not specify the number
#of bytes to read, instead write 0
#to indicate that the :packet option
#will take care of how many bytes to read.
{:ok, line} ->
#Echo back what was received:
IO.write("Server echoing back: #{line}")
:gen_tcp.send(client_socket, "Server received: #{line}") #line has a "\n" on the end
{:error, :closed} ->
{:ok, :client_disconnected}
end
end
end
TCP client:
defmodule App2 do
def send_data do
host = :localhost
port = 8080
{:ok, socket} = :gen_tcp.connect(host, port,
[
{:active, :false}, #Data sent to the socket will not be put in the process mailbox.
{:mode, :binary},
{:packet, :line} #Must be same as server.
]
)
:ok = :gen_tcp.send(socket, "Hi server!\n")
case :gen_tcp.recv(socket, 0) do
{:ok, line} ->
IO.puts ~s(Client got: "#{String.trim line}")
:ok = :gen_tcp.close(socket)
{:error, :closed} -> IO.puts("Server closed socket.")
end
end
end
Output in server window:
iex(1)> App1.start_tcp_server
Listening on port 8080....
Server echoing back: Hi server!
Output in client window:
ex(1)> App2.send_data
Client got: "Server received: Hi server!"
:ok
If you want to use {:error, :closed} to mark the end of the data, then you need to:
Specify the option {:packet, :raw} for both the client and the server socket.
After sending the data, close the socket by calling :gen_tcp.shutdown/2, which will ensure that all the data has been sent before closing the socket.
Loop over the recv(), saving each chunk returned by recv(), until the recv() returns {:error, :closed}, marking the end of the data, then your loop can return all the chunks it read. For instance:
defp get_data(socket, chunks) do
case :gen_tcp.recv(socket, 0) do #reads a chunk of indeterminate length from the socket
{:ok, chunk} ->
get_data(socket, [chunk | chunks])
{:error, :closed} ->
{:ok, Enum.reverse(chunks) }
end
end
You would call that function like this:
{:ok, data} = get_data(client_socket, [])
For more details on the gen_tcp options, see the following answer
Erlang client-server example using gen_tcp is not receiving anything
I'm writing a small program in Haskell which manipulates the commands arecordmidi and aplaymidi to record short improvisations on my digital piano through MIDI. I will press the R key, my program will create a new subprocess with the command arecordmidi. When I press R again, I want my recording to stop, by terminating the command arecordmidi.
How do I terminate the arecordmidi subprocess? If in a shell, CTRL+C would stop recording. This is what I want.
I'm using the following code to create the subprocess:
import System.Process
main = do
let rec_command = "arecordmidi -p \"CASIO USB-MIDI\" myRecording.midi"
process <- createProcess (shell rec_command)
-- I could try the following code, but the documentation of System.Process
-- says it's bad to use terminateProcess:
let (_, _, _, processHandle) = process
terminateProcess processHandle
terminateProcess sends a SIGTERM (terminate) signal to the process, which corresponds to the default behavior of the unix command kill, which generally is not what you want when trying to end a process nicely.
Ctrl+C sends the signal SIGINT (interrupt), which many applications handle by an orderly shutdown, and in your case probably results in the arecordmidi process saving outstanding data, and closing any pipes and files.
Looks like the way to send SIGINT with System.Process is with interruptProcessGroupOf.
TCP example
#async begin
server = listen(2000)
while true
sock = accept(server)
println("Hello World\n")
end
end
To close the connection, you need to call the close method:
close(sock)
How to stop the listener?
close(server) #LoadError: accept: software caused connection abort (ECONNABORTED)
Rather than keep commenting, here's what I think you were probably trying to do:
From the julia REPL:
julia> server = listen(2000)
Base.TCPServer(active)
julia> #async begin
while true
sock = accept(server)
print(readstring(sock))
end
end
From another terminal:
~ $ nc localhost 2000
Hello from the other terminal
[Ctrl-D] % i.e. signal end of file. this closes the connection
In the julia repl, you'll see "Hello from the other terminal" printed as soon as you send the EOF signal, but otherwise the julia prompt will continue as normal. If you repeat this process from the netcat terminal you'll see the message printed in the REPL again, because the socket keeps reactivating inside the while loop.
Ideally if you wanted to shut the whole thing down, you would first close(sock) and then close(server). But, you can't close the socket directly, because it's in the "while" loop and it keeps getting reactivated, and you don't have direct access to the variable "sock".
Therefore you can only close the server, fully expecting an error. So catch it in a try block instead
EDIT: sorry, my bad, the exception relates to the socket, not the server, so you need to wrap that in a try catch block inside your async block instead:
#async begin
while true
try
sock = accept(server)
print(readstring(sock))
catch ex
print("exiting while loop")
break
end
end
end
I make server socket at Raspberry PI and client socket at Android mobile device.
They can make connection each other and somehow there is an error at server socket side and the client side does not receive the message sent from the server side.
My server side program is
import socket
import time
import picamera
import sys
# Start a socket listening for connections on 192.168.0.17:8080
server_socket = socket.socket()
server_socket.bind(('192.168.0.17', 8080))
server_socket.listen(0)
print >> sys.stderr,'Listening'
# Accept a single connection and make a file-like object out of it
while True:
connection = server_socket.accept()[0].makefile('wb')
print >> sys.stderr, 'Connected with'
try:
print >> sys.stderr, 'going to write'
connection.write("linked")
connection.flush()
print >> sys.stderr, 'Sent message'
time.sleep(20)
finally:
print >> sys.stderr, 'Server socket is going to be closed'
connection.close()
server_socket.close()
print >> sys.stderr, 'Server socket is closed'
The print output shows that the server socket sent message, it is as follow
Listening
Connected with
going to write
Sent message
Server socket is going to be closed
Server socket is closed
The error is
Traceback (most recent call last):
File "/home/pi/PY_programs/camera_play.py", line 13, in <module>
connection = server_socket.accept()[0].makefile('wb')
File "/usr/lib/python2.7/socket.py", line 202, in accept
sock, addr = self._sock.accept()
File "/usr/lib/python2.7/socket.py", line 170, in _dummy
raise error(EBADF, 'Bad file descriptor')
error: [Errno 9] Bad file descriptor
What is wrong with this error and why I don't receive 'linked' message at the client.
Thanks
finally:
print >> sys.stderr, 'Server socket is going to be closed'
connection.close()
server_socket.close()
print >> sys.stderr, 'Server socket is closed'
Here you close the server socket too. Don't do that, you need your server_socket on the next iteration of your loop so it can accept a new client.
I'm trying to get multiple clients to connect to a server. What I've managed to do is connect one client to a server by using for the server:
main = withSocketsDo $ do
socket <- listenOn port
(handle, host, portno) <- accept socket
hSetBuffering handle LineBuffering
msg <- hGetLine handle
putStrLn $ "The client says: " ++ msg
hClose handle
sClose socket
putStrLn "Server is done."
and for the client:
main = withSocketsDo $ do
handle <- connectTo "localhost" port
hSetBuffering handle LineBuffering
hPutStrLn handle "Hello!"
hClose handle
These are clearly just for testing purposes ;)
Now, I've read that I need to use forkIO to enable multiple clients to connect to this one server. However I haven't been able to find how I should use forkIO or how to manage the several clients that will connect. Can someone explain to me what I should do?
Thanks in advance!
The key is that once you've accepted a connection using accept, you'll want to fork a new thread to handle the connection while the main thread goes back to listening. So something like this should do the trick.
main = withSocketsDo $ do
socket <- listenOn port
-- We want to accept multiple connections,
-- so we need to accept in a loop
forever $ do
(handle, host, portno) <- accept socket
-- But once we've accepted, we want to go off and handle the
-- connection in a separate thread
forkIO $ do
hSetBuffering handle LineBuffering
msg <- hGetLine handle
putStrLn $ "The client says: " ++ msg
hClose handle
Note that this way the server keeps running until you kill the process, which is common behavior for many servers. Implementing more graceful shutdown will require some cross-thread communication using MVars or STM.
Just as a general style comment, I'd split the reaction of the server to the client's connection into a separate function. This makes it easier to read in my opinion
main = withSocketsDo $ do
socket <- listenOn port
accept socket >>= handleClientRequest socket
handleClientRequest socket (handle, host, portno) = do
hSetBuffering handle LineBuffering
msg <- hGetLine handle
putStrLn $ "The client says: " ++ msg
hClose handle
sClose socket
putStrLn "Server is done."
now we probably want it to loop indefinitely, as that tends to be how most servers work. So we use forever (from Control.Monad)
main = withSocketsDo $ do
socket <- listenOn port
forever $ accept socket >>= handleClientRequest socket
handleClientRequest socket (handle, host, portno) = do
hSetBuffering handle LineBuffering
msg <- hGetLine handle
putStrLn $ "The client says: " ++ msg
hClose handle
sClose socket
putStrLn "Server is done."
and from here, the manner in which to use forkIO becomes quite clear
main = withSocketsDo $ do
socket <- listenOn port
forever $ accept socket >>= forkIO . (handleClientRequest socket)
handleClientRequest socket (handle, host, portno) = do
hSetBuffering handle LineBuffering
msg <- hGetLine handle
putStrLn $ "The client says: " ++ msg
hClose handle
sClose socket
putStrLn "Server is done."