As part of a networks course, we had to write 2 programs that would talk to one another over a TCP connection. The first program was the Broadcaster. It has to communicate with the client browser, serving a couple of HTML files and receiving the responses accordingly. The second program was the Bank. It has to receive a connection from the Broadcaster and maintains the client’s credit details. Once the client makes a payment on the Broadcaster’s website, we would have to check if the bank has the client’s records. And if it’s available with enough available credit, we make the deduction and send the client the files that were requested.
We could use any programming language that we wanted. I took this opportunity to use Ruby. It was my first time writing a networked application in Ruby and I had to look up how sockets in Ruby worked. We couldn’t make use of any third party gems.
We were provided with HTML files to serve to our client’s browser and I had to open a session with the browser, read the HTML file, send it across the network with the appropriate HTTP headers. It was a pretty interesting experience. With each response from the client’s browser, I had to perform different actions. Opening a new connection to the bank was interesting as I had to write my own messaging format that could be easily parsed by the Bank.
Parsing the messages from the client’s browser and the Bank was made easy with regular expressions.
Reading the messaging format was quite easy as I built the format around how the data was presented. With each connection and message, I made use of Ruby’s regular expressions to match the different requests.
#!/usr/bin/ruby -w
require 'socket'
unless ARGV.length == 3
$stderr.puts "Usage: #{$0} BROADCASTER_PORT BANK_IP BANK_PORT"
exit 1
end
broadcasterPort = ARGV[0].to_i
bankIP = ARGV[1]
bankPort = ARGV[2].to_i
puts "BROADCAST_PORT: #{broadcasterPort}"
puts "BANK_IP: #{bankIP}"
puts "BANK_PORT: #{bankPort}"
if broadcasterPort < 1024
$stderr.puts "Port number should not be less than 1024"
exit 1
end
server = TCPServer.new(broadcasterPort)
count = 0
prices = Hash.new
File.open("price.txt", 'r').each_line do |line|
count += 1
next if count == 1
show, price = line.chomp.split("\t")
show = show.gsub(/\s/,"")
prices.store(show,price)
end
puts "----- Initial Hash -----"
prices.each do |key,data|
puts "#{key}:#{data}"
end
puts "------------------------"
puts "Starting up Broadcaster..."
def Print200 session, f
session.print "HTTP/1.0 200 OK\nContent-Type: text/html\n\n"
PrintOut session, f
end
def Print404 session, f
session.print "HTTP/1.0 404 FILE NOT FOUND\nContent-Type: text/html\n\n"
PrintOut session, f
end
def PrintOut session, f
f.each do |line|
session.print "#{line}\n"
end
end
while session = server.accept
puts "Connection from #{session.peeraddr[2]} at #{session.peeraddr[3]}"
request = ""
recvLength = 1024
while (tmp = session.recv(recvLength))
request += tmp
break if tmp.length < recvLength
end
headerField = Hash.new
if request.match(/^(GET|POST) (.+) HTTP\/1.[01]\s*$/)
method, url = request.split(" ")
if method.match(/GET/)
url = url.gsub("/", "./")
file = "#{url}"
if File.directory?(file)
file = "index.html"
end
if file.match(/.html/)
if File.exist?(file)
f = File.open(file,'r')
Print200 session, f
f.close
else
f = File.open("404.html",'r')
Print404 session, f
f.close
end
elsif file.match(/downloads.zip/)
if File.exist?(file)
session.print "HTTP/1.0 200 OK\nContent-Type: application/zip\n\n"
data = IO.read("downloads.zip")
session.print data
system("rm downloads.zip")
end
else
f = File.open("404.html",'r')
Print404 session, f
f.close
end
elsif method.match(/POST/)
totalPrice = 0
parameters = ""
params = Hash.new
parameters = request.match(/((\w)+=(\d)+&)+.* /).to_s
parameters = parameters.gsub("&","\n")
parameters.each_line do |line|
key, data = line.chomp.split("=")
params.store(key,data)
end
if params['service'] == "CD"
prices.each_key do |key|
num = params["#{key}"].to_i
priceOf = prices["#{key}"].to_i
totalPrice += num.abs * priceOf
end
totalPrice += 10
else
prices.each_key do |key|
num = params["#{key}"].to_i
priceOf = prices["#{key}"].to_i
totalPrice += num * priceOf
end
end
puts "Connecting to Bank"
client = TCPSocket.new("#{bankIP}", bankPort)
client.puts "Broadcaster Connected..."
puts client.gets
puts "PURCHASE #{params["userfirst"]}-#{params["userfamily"]}-#{params["cardno"]} #{totalPrice}"
client.puts "PURCHASE #{params["userfirst"]}-#{params["userfamily"]}-#{params["cardno"]} #{totalPrice}"
response = client.gets
puts response
client.close
puts "Bank Connection Closed."
if response.match(/BANK (\d+) ([ \w]+)/)
errorCode, errorMsg = response.chomp.gsub("BANK ","").split(" ")
errorCode = errorCode.to_i
if errorCode == 0
f = File.open("insufficient.html",'r')
Print200 session, f
f.close
end
if errorCode == 2
f = File.open("nouser.html",'r')
Print200 session, f
f.close
end
if errorCode == 1
if params['service'] == "CD"
f = File.open("CD-Approved.html",'r')
Print200 session, f
f.close
else
params.each do |key,data|
if prices.has_key?("#{key}")
if data.to_i >= 1
system("zip downloads.zip shows/#{key.downcase}.txt")
end
end
end
f = File.open("approved.html", 'r')
Print200 session, f
f.close
end
end
else
session.print "HTTP/1.0 400 BAD REQUEST\nContent-Type: text/plain\n\nBAD REQUEST\n"
end
end
end
session.close
puts "Session Closed"
end
server.close
#!/usr/bin/ruby -w
require 'socket'
unless ARGV.length == 1
$stderr.puts "Usage: #{$0} BANK_PORT"
exit 1
end
port = ARGV[0].to_i
puts "BANK_PORT: #{port}"
if port < 1024
$stderr.puts "Port number should not be less than 1024"
exit 1
end
fileName = "credit.txt"
count = 0
accountBalance = Hash.new
File.open(fileName, 'r').each_line do |line|
count += 1
next if count == 1
firstName, lastName, cardNumber, availableCredit = line.chomp.split("\t")
accountBalance.store "#{firstName}-#{lastName}-#{cardNumber}", availableCredit
end
puts "----- Initial Hash -----"
accountBalance.each do |key,data|
print "#{key} #{data}\n"
end
puts "------------------------"
def UpdateBalance user, price, fileName
firstName, lastName, cardNumber = user.split("-")
f = File.open fileName, 'r'
w = File.open fileName, 'r+'
puts "----- #{fileName} -----"
f.each_line do |line|
if line.match /#{firstName}\t#{lastName}\t#{cardNumber}/
firstName, lastName, cardNumber, balance = line.chomp.split("\t")
balance = balance.to_i
balance -= price.to_i
puts "#{firstName}\t#{lastName}\t#{cardNumber}\t#{balance}"
w.write "#{firstName}\t#{lastName}\t#{cardNumber}\t#{balance}\n"
else
puts "#{line}"
w.write "#{line}"
end
end
puts "------- UPDATED --------"
f.close
w.close
end
server = TCPServer.new port
puts "Starting up Bank..."
while session = server.accept
puts "Connection from #{session.peeraddr[2]} at #{session.peeraddr[3]}"
session.puts "Bank Connected..."
request = session.gets
puts request
request = session.gets
puts request
if request.match(/^PURCHASE (.+) (\d+)$/)
user, price = request.gsub("PURCHASE ","").split(" ")
puts price
if accountBalance.has_key?("#{user}")
if price.to_i > accountBalance[user].to_i
session.puts "BANK 0 INSUFFICIENT FUNDS"
puts "BANK 0 INSUFFICIENT FUNDS"
else
session.puts "BANK 1 PURCHASE APPROVED"
puts "BANK 1 PURCHASE APPROVED"
UpdateBalance user, price, fileName
end
else
session.puts "BANK 2 NO RECORD"
puts "BANK 2 NO RECORD"
end
end
session.close
puts "Broadcaster Connection Closed."
end
server.close
Published on 11 Sep 2012 by Stanley Tan