1715735312

Creating a simple multi-user chat with ruby


Requirements: ruby, socket gem <br> Connect: netcat or telnet. I saw a post here submitted about 5 months ago and I remembered a project i did a while ago to practice some io and networking features in ruby. I found this project online and it seemed pretty easy so I thought id follow along. If i find that post ill link it in the comments but for now ill walk your through the project and explain what is happening so that you can follow along with me. For those wondering the server that we are making needs to be running at all times for everything to work as it handles the chat aswell as the client side. This has the benefit that the client computers dont need to have this source code on their computer too, they just need netcat or some alternative to connect to your service. No need to distribute :) ``` require 'socket' server = TCPServer.new 6969 connected_clients = [] ``` First of we are importing the socket gem to be used. This is what is going to handle the io of the port that we create. Line 2 is making a server object that we will later use methods on. The value is equivalent to the returned value of the "TCPServer.new 6969" once it becomes evaluated. Good thing is: its evaluated immediately. TCPserver.new is a method that you inherit from socket and will work nicely with the ruby standard library. The numbers behind it are the port value that we are going to open and accept request to/from. Line 3 is an array of currently connected users. We need to keep track of them so that we can use methods on the users later, most for joining and leaving. The last bit that will be outside of our main program loop will be a quick check to make sure that a name isnt already in use aswell as the speak method which will handle the posting of text to all of the other users feed. If you not aware by now, methods are used for everything and are essentially equivalent to what you would call a function in other languages. ``` def valid_nickname?(nickname, connected_clients) connected_clients.none? {|client| client.username == nickname} end def speak(msg, client, all_clients) all_clients.each do |other_client| next if other_client == client other_client.socket.puts msg end end ``` The arguments: nickname, client, etc will be passed into those methods from the main program loop. I will break down the main loop into parts so please dont copy because it will not have the end tags in the right spots if at all. I will have the full code listed at the bottom so you wont get a bunch of errors. ``` loop do Thread.start(server.accept) do |client| client.puts "Welcome to my chat server! What is your nickname?" ``` Threads are part of the ruby that allows it to be able to do multiple tasks at once rather then having to wait for 1 task to finish to move onto the next step. This is useful because we dont know how many people will submit chats at once, aswell as how many users there will be. Each users needs to be able to have their methods available to them immediately aswell as their chat feed and having a line of tasks that need to be done first is just not a good way to handle it. ``` nickname = client.gets next if nickname.nil? nickname = nickname.chomp while !(valid_nickname?(nickname, connected_clients)) do client.puts "Sorry that username is already taken, please choose another" nickname = client.gets next if nickname.nil? nickname = nickname.chomp end ``` First thing we want to do is make a nick name so that we know who we are talking too. the nickname object is only available to the user that it is working with. It preforms like an instance variable and cant be accessed by other users. Next we check to see if the name is taken already by using the method that we created at the beginning of the project (scroll up).<br> Line 4: The ? appended to the end is waiting for a truthy response from the valid_nickname method. If it fails it'll request you pick something else. This is just ruby shorthand. ``` connected_client = ConnectedClient.new(client, nickname) # Tell this client the names of the other clients other_client_names = connected_clients.map(&:username) client.puts "You are connected with #{connected_clients.length} other users: [#{other_client_names.join(',')}]" # Tell the other clients the name of this client speak("*#{nickname} has joined the chat*", connected_client, connected_clients) connected_clients << connected_client ``` That user that we just made is doing to be added to the connected_clients array. We pass the info that we grabbed from the last section (nickname) to the rest of the users with a message alerting everyone that someone has joined the chat. It then list all the other users in the chat. ``` while line = client.gets line = line.chomp speak("<#{nickname}> #{line}", connected_client, connected_clients) end ``` This part is very simple. It takes any text that is entered and passes it to the speak method that we made at the beginning of the program. Then adds it to the feed that will populate it on all of the other users clients. ``` # Tell the other clients that this client has left connected_clients.delete(connected_client) speak("*#{nickname} has left the chat*", connected_client, connected_clients) end end ``` This does the same thing as the join feature except it will remove the user from the connected_client array and let everyone know you left. Full source: ``` require 'socket' server = TCPServer.new 6969 connected_clients = [] ConnectedClient = Struct.new(:socket, :username) def valid_nickname?(nickname, connected_clients) connected_clients.none? {|client| client.username == nickname} end def speak(msg, client, all_clients) all_clients.each do |other_client| next if other_client == client other_client.socket.puts msg end end loop do Thread.start(server.accept) do |client| client.puts "Welcome to my chat server! What is your nickname?" nickname = client.gets next if nickname.nil? # Check if nickname is valid nickname = nickname.chomp while !(valid_nickname?(nickname, connected_clients)) do client.puts "Sorry that username is already taken, please choose another" nickname = client.gets next if nickname.nil? nickname = nickname.chomp end connected_client = ConnectedClient.new(client, nickname) # Tell this client the names of the other clients other_client_names = connected_clients.map(&:username) client.puts "You are connected with #{connected_clients.length} other users: [#{other_client_names.join(',')}]" # Tell the other clients the name of this client speak("*#{nickname} has joined the chat*", connected_client, connected_clients) connected_clients << connected_client # Listen for input while line = client.gets line = line.chomp speak("<#{nickname}> #{line}", connected_client, connected_clients) end # Tell the other clients that this client has left connected_clients.delete(connected_client) speak("*#{nickname} has left the chat*", connected_client, connected_clients) end end ``` To connect use ```netcat localhost 6969``` or whatever your port is. If you would like to connect to it from outside your lan you will need to setup port forwarding on your router aswell as get a static ip from your isp. You can find guides on how to do that elsewhere. Good luck! If you have any issues i will be in the ruby chat. Also please make sure the server is running before trying to connect.

(0) Comments