Connor Fuhrman
Connor Fuhrman

Reputation: 831

Julia Sockets TCP Connection Refused when running script, not REPL

I am following the simple TCP example found here: https://docs.julialang.org/en/v1/manual/networking-and-streams/#A-simple-TCP-example-1. The code can be seen below (I modified that from the link slightly):

using Sockets
using Printf

# listen will create a server waiting for incoming connections on the specified
# port (in this case it will be localhost::2000)
@async begin
           server = listen(2000)
           x :: Int = 1
           while true
               sock = accept(server)
               @printf "Connection number %d\n" x
               x += 1
           end
       end


for i = 1:10
    connect(2000)
end

When I execute the commands within the REPL it works correctly producing the following output:

julia> @async begin
                  server = listen(2000)
                  x :: Int = 1
                  while true
                      sock = accept(server)
                      @printf "Connection number %d\n" x
                      x += 1
                  end
              end
Task (runnable) @0x00000001169d06d0

julia> for i = 1:10
           connect(2000)
       end
Connection number 1
Connection number 2
Connection number 3
Connection number 4
Connection number 5
Connection number 6
Connection number 7
Connection number 9
Connection number 10

However, when I try to place these commands in a file:

using Sockets
using Printf

# listen will create a server waiting for incoming connections on the specified
# port (in this case it will be localhost::2000)
@async begin
           server = listen(2000)
           x :: Int = 1
           while true
               sock = accept(server)
               @printf "Connection number %d\n" x
               x += 1
           end
       end


for i = 1:10
    connect(2000)
end

and run using julia <scriptName> or (from within the REPL) include("<scriptName>") I get the following error message:

ERROR: LoadError: IOError: connect: connection refused (ECONNREFUSED)
Stacktrace:
 [1] wait_connected(::TCPSocket) at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.3/Sockets/src/Sockets.jl:520
 [2] connect at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.3/Sockets/src/Sockets.jl:555 [inlined]
 [3] connect at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.3/Sockets/src/Sockets.jl:541 [inlined]
 [4] connect at /Users/julia/buildbot/worker/package_macos64/build/usr/share/julia/stdlib/v1.3/Sockets/src/Sockets.jl:537 [inlined]
 [5] top-level scope at <pathToFile>:18
 [6] include at ./boot.jl:328 [inlined]
 [7] include_relative(::Module, ::String) at ./loading.jl:1105
 [8] include(::Module, ::String) at ./Base.jl:31
 [9] include(::String) at ./client.jl:424
 [10] top-level scope at REPL[1]:1
in expression starting at <pathToFile>:17

How would I run this program from a script? I am fairly new to Julia and to sockets so apologies if this is an easy question!!

Upvotes: 2

Views: 925

Answers (2)

You're getting an error because connect will fail if listen has not yet had time to run. As you discovered, introducing a small pause between your server and client sides fixes the issue; let's try and explain this in more details.

In the REPL-based, interactive version, as soon as the server task is created using @async, control goes back to the REPL, which performs blocking operations (such as waiting for new commands to be entered in the command line). This gives the scheduler an opportunity to yield control to the newly created task. The call to listen appears early in it, ensuring that whenever control reaches the client side again - and calls to connect are executed - the server is listening.

In the script version, the server task gets created and scheduled, but the scheduler does not have any opportunity to actually run it, since the main task does not perform any blocking call before running connect. Therefore no-one is listening to the socket when the connection is performed, and the call fails. However, any blocking call put in this place will give an opportunity for the scheduler to run the server task. This includes sleep (of arbitrarily small amounts of time, as you noted), but also any function performing I/O: a simple println("hello") would have worked too.

I would think a cleaner way to fix things would be to ensure that the call to listen is performed first, by running it synchronously beforehand:

using Sockets
using Printf

# listen will create a server waiting for incoming connections on the specified
# port (in this case it will be localhost::2000)
server = listen(2000)
@async begin
    x = 1
    while true
        sock = accept(server)
        @printf "Connection number %d\n" x
        x += 1
    end
end

for i = 1:10
    connect(2000)
end


Now you're left with another problem: when the client loop terminates, all calls to connect have been issued, but not necessarily all corresponding calls to accept have had time to run. This means that you're likely to get truncated output such as:

Connection number 1
Connection number 2
Connection number 3
Connection number

You'd need further coordination between your tasks to correct this, but I suspect this second problem might be only related to the MWE posted here, and might not appear in your real use case.

For example, presumably the server is meant to send something to the client. The read-write operations performed on sockets in this case naturally synchronizes both tasks:

using Sockets
using Printf

# listen will create a server waiting for incoming connections on the specified
# port (in this case it will be localhost::2000)
server = listen(2000)
@async begin
    x = 1
    while true
        sock = accept(server)
        msg = "connection number $x\n"
        print("Server sent:     $msg")
        write(sock, msg)
        x += 1
    end
end

for i = 1:10
    sock = connect(2000)
    msg = readline(sock)
    println("Client received: $msg")
end

The above example correctly yields a complete (untruncated) output:

Server sent:     Connection number 1
Client received: Connection number 1
Server sent:     Connection number 2
Client received: Connection number 2
...
Server sent:     Connection number 10
Client received: Connection number 10

Upvotes: 3

Connor Fuhrman
Connor Fuhrman

Reputation: 831

So I've figured this out I believe. The server and client either need to be separated between two files, OR, a pause command (seems to be okay with an arbitrarily small pause command) needs to be in between the server and client side:

#!/Applications/Julia-1.3.app/Contents/Resources/julia/bin/julia

using Sockets
using Printf

# listen will create a server waiting for incoming connections on the specified
# port (in this case it will be localhost::2000)
port = 2000
@async begin
    server = listen(IPv6(0),port)
    x::Int = 1
    while true
        sock = accept(server)
        @printf "Connection number %d\n" x
        x += 1
    end
end

sleep(1E-10)

for i = 1:10
    connect(port)
end

This is now working as expected!

Upvotes: 2

Related Questions