trialANDerror
trialANDerror

Reputation: 1

Socket connection between Python and TCL

I've just started on socket programming. I want to have the client as TCL and server as Python. I tried the following few scripts, but I'm unable to make any progress.

Here are my scripts:

server.py:

import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((socket.gethostname(), 45000))
s.listen(5)
while True:
    clt, adr=s.accept()
    print(adr)
    clt.send(bytes("Socket programming in Python"))

client.tcl:

set host "127.0.0.1"
set port 45000 
set mysock [socket -myaddr $host -myport $port $host $port]
fconfigure $mysock -buffering none
gets $mysock line
close $mysock
puts "Message is $line"

However, the above TCL script just hangs and doesn't go beyond the gets command.

This is the equivalent Python client script which works:

client.py:

import socket 
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
print(s) 
s.connect((socket.gethostname(), 45000)) 
msg=s.recv(1024) 
print(msg.decode("utf-8")) 

Now based on few suggestions, to mimic the above python client script in TCL, I tried the following:

client_modified.tcl:

set s [socket [info hostname] 45000]
puts $s
fconfigure $s -translation binary
puts [encoding convertfrom utf-8 [read $s 28]]

I'm still not able to proceed beyond: "socket localhost 45000".

It gives me the error:

> tclsh client.tcl 
couldn't open socket: connection refused
    while executing
"socket [info hostname] 45000"
    invoked from within
"set s [socket [info hostname] 45000]"

Can anyone please guide here.

Upvotes: 0

Views: 1728

Answers (3)

trialANDerror
trialANDerror

Reputation: 1

It works with the following small edit(-async):

set s [socket -async [info hostname] 45000]
puts $s
fconfigure $s -translation binary
puts [encoding convertfrom utf-8 [read $s 28]]

Thanks for helping.

Upvotes: 0

Donal Fellows
Donal Fellows

Reputation: 137567

Given that:

import socket 
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
print(s) 
s.connect((socket.gethostname(), 45000)) 
msg=s.recv(1024) 
print(msg.decode("utf-8")) 

is a working Python client, the equivalent in Tcl is:

set s [socket [info hostname] 45000]
puts $s
fconfigure $s -translation binary
puts [encoding convertfrom utf-8 [read $s 28]]; # You're writing 28 bytes, not 1024

But I'd be more inclined to write:

set s [socket localhost 45000]; # For complex reasons, [info hostname] takes a long time on macOS
fconfigure $s -encoding utf-8
puts [read $s 28]

Note that if you're writing text data over a socket, you really should also write newlines to act as a message boundary. On the Tcl side, this would let you use gets instead of read; without the newline, Tcl's gets will merrily wait indefinitely for it to turn up when in the default blocking mode. (For binary-oriented protocols, you'd probably be sending the length of data first if that's variable.)

A more sophisticated version uses non-blocking mode. Unless you're really sure what you're doing, non-blocking mode should be used exclusively with fileevent callbacks:

set s [socket localhost 45000]
fconfigure $s -encoding utf-8 -blocking 0
fileevent $s readable {apply {{} {
    global s done
    puts [read $s 1024]
    # Remember to close that socket at EOF
    if {[eof $s]} {
        close $s
        set done "ok"; # Terminates the event loop running this
    }
}}}
vwait done; # Launch an event loop for processing the socket

If you write messages back to the server, you probably want to set -buffering none so that you don't have to fuss around with flush. And if you're doing a complex conversation, you'll probably want to use a coroutine to manage it; they've got features for complex programming (and some support libraries exist to help) that make doing complex protocols enormously simpler.

Upvotes: 1

Brad Lanam
Brad Lanam

Reputation: 5723

On the Tcl side, to open a client socket, do not specify -myport. This option specifies a particular port for the client connection to use. Since your code sets this to port 45000, which is already in use by the server, it fails.

set host "127.0.0.1"
set port 45000
try {
  set mysock [socket $host $port]
} on error { err res } {
  puts $res
}
try {
  chan configure $mysock -buffering line
  chan configure $mysock -encoding utf-8
} on error { err res } {
  puts $res
}
try {
  gets $mysock line
} on error { err res } {
  puts $res
}
puts "Message is $line"
close $mysock

I changed the python code to include a newline so that it would flush the message. Not knowing any python or how the python socket code works, I got lucky there.

import socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((socket.gethostname(), 45000))
s.listen(5)
while True:
    clt, adr=s.accept()
    print(adr)
    clt.send(bytes("Socket programming in Python\n"))

Upvotes: 0

Related Questions