TCP/IP simple: single login

Objective


In this topic, we will develop a simple login application based on Client-Server model with TCP/IP protocol. The program is constructed on the MVC model for both client and server sides.

The application will be organized as follow:
– At the server side, the server manages a database which contains a table containing the user data, including username and password for each one
– The server has a graphical interface to manipulate the server socket such as start, stop, and show the connections from clients.
– At the client side, There are two graphical interfaces: one is the main interface to manipulate the connection to the server: connect, disconnect, choose the function… Other enables user to enter his username/password to login


Class diagrams

At the third side, there are entity classes (They could be implemented in a separated project and then, exported to a .jar file to be imported into the client project and the server project):

  • User: this is an model class which contains at least two attributes: username, and password. This class is commonly used on both client and server side.
  • IPAddress: the entity containing the information about the host and the port of a server


At the client side, there are following classes (some relationships among classes is omitted, these classes could also be implemented in a separated TCP client project):

  • SimpleClientMainFrm: the main client interface to manipulate the connection to the server: connect, disconnect, choose the function…
  • SimpleLoginFrm: a graphical view class enabling user to enter username/password to login and then, displaying the results of login to the user
  • ClientCtr: a control class which gets username/password form view class and send them to server to check whether it is correct, and then receives the results from the server to send to view class to display.



At the server side, there are some other classes (these classes could also be implemented in a separated TCP server project) :

  • At the DAO level, there are two classes of DAO and UserDAO
  • At the business control level, there are also two classes: SimpleServerCtr to manage the server socket. ServerListening is a Thread inherited class to listening the connection from client.
  • At the view level, there is the ServerMainFrm to manipulate the server socket: open, close, and display information a bout it.

The sequence of activities is represented as following sequence diagram:

  • Step 1-10 : the server opens server socket at the server side
  • Step 11-18: a client connect to the server.
  • Step 19-38: client user choose the login function, the client send the user object to server, server checks in the DB and return the results to the client, the client show it to the user.


Complete code

Class: User.java

package model;
import java.io.Serializable;

public class User implements Serializable{
	private static final long serialVersionUID = 20210811010L;
	private int id;
	private String username;
	private String password;
	private String name;
	private String position;
	
	public User() {
		super();
	}

	public User(String username, String password, String name, String position) {
		super();
		this.username = username;
		this.password = password;
		this.name = name;
		this.position = position;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPosition() {
		return position;
	}

	public void setPosition(String position) {
		this.position = position;
	}
}

Class: IPAddress.java

package model;

import java.io.Serializable;

public class IPAddress implements Serializable{
	private static final long serialVersionUID = 20210811012L;
	private String host;
	private int port;
	
	public IPAddress() {
		super();
	}

	public IPAddress(String host, int port) {
		super();
		this.host = host;
		this.port = port;
	}

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}
}

Class: SimpleClientCtr.java

package tcp.client.control;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;

import model.IPAddress;
import tcp.client.control.ClientCtr.ClientListening;
import tcp.client.view.SimpleClientMainFrm;


public class SimpleClientCtr {
	private Socket mySocket;
	private SimpleClientMainFrm view;
	private IPAddress serverAddress = new IPAddress("localhost",8888);	// default server host and port
	
	public SimpleClientCtr(SimpleClientMainFrm view){
		super();
		this.view = view;
	}
	
	public SimpleClientCtr(SimpleClientMainFrm view,  IPAddress serverAddr) {
		super();
		this.view = view;
		this.serverAddress = serverAddr;
	}



	public boolean openConnection(){		
		try {
			mySocket = new Socket(serverAddress.getHost(), serverAddress.getPort()); 
            view.showMessage("Connected to the server at host: " + serverAddress.getHost() + ", port: " + serverAddress.getPort());
        } catch (Exception e) {
        	e.printStackTrace();
        	view.showMessage("Error when connecting to the server!");
        	return false;
        }
		return true;
	}
	
	public boolean sendData(Object obj){
		try {
            ObjectOutputStream oos = new ObjectOutputStream(mySocket.getOutputStream());
			oos.writeObject(obj);			
			
        } catch (Exception e) {
        	//e.printStackTrace();
        	view.showMessage("Error when sending data to the server!");
            return false;
        }
		return true;
	}
	
	
	public Object receiveData(){
		Object result = null;
		try {
			ObjectInputStream ois = new ObjectInputStream(mySocket.getInputStream());
			result = ois.readObject();
        } catch (Exception e) {
        	//e.printStackTrace();
        	view.showMessage("Error when receiving data from the server!");
            return null;
        }
		return result;
	}
	
	public boolean closeConnection(){
		 try {
			 if(mySocket !=null) {
				 mySocket.close();
				 view.showMessage("Disconnected from the server!");
			 }			
         } catch (Exception e) {
        	 //e.printStackTrace();
        	 view.showMessage("Error when disconnecting from the server!");
             return false;
         }
		 return true;
	}
}

Class: SimpleClientMainFrm.java

package tcp.client.view;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import model.IPAddress;
import tcp.client.control.SimpleClientCtr;

public class SimpleClientMainFrm extends JFrame implements ActionListener{
	private JMenuBar mnbMain;
	private JMenu mnUser;
	private JMenuItem mniLogin;
	
	private JTextField txtServerHost, txtServerPort;
	private JButton btnConnect, btnDisconnect;	
	private JTextArea mainText;
	private SimpleClientCtr myControl;
	
	public SimpleClientMainFrm(){
		super("TCP client view");		
		
		JPanel mainPanel = new JPanel();
		mainPanel.setLayout(null);
		
		mnbMain = new JMenuBar();
		mnUser = new JMenu("User");
		mniLogin = new JMenuItem("Login");
		mniLogin.addActionListener(this);
		mnUser.add(mniLogin);
		mnbMain.add(mnUser);		
		this.setJMenuBar(mnbMain);
		mniLogin.setEnabled(false);
		
		
		JLabel lblTitle = new JLabel("Client TCP/IP");
		lblTitle.setFont(new java.awt.Font("Dialog", 1, 20));
		lblTitle.setBounds(new Rectangle(150, 20, 200, 30));
		mainPanel.add(lblTitle, null);
		
		JLabel lblHost = new JLabel("Server host:");
		lblHost.setBounds(new Rectangle(10, 70, 150, 25));
		mainPanel.add(lblHost, null);
		
		txtServerHost = new JTextField(50);
		txtServerHost.setBounds(new Rectangle(170, 70, 150, 25));
		mainPanel.add(txtServerHost,null);
		
		JLabel lblPort = new JLabel("Server port:");
		lblPort.setBounds(new Rectangle(10, 100, 150, 25));
		mainPanel.add(lblPort, null);
		
		txtServerPort = new JTextField(50);
		txtServerPort.setBounds(new Rectangle(170, 100, 150, 25));
		mainPanel.add(txtServerPort,null);
		
		btnConnect = new JButton("Connect");
		btnConnect.setBounds(new Rectangle(10, 150, 150, 25));
		btnConnect.addActionListener(this);
		mainPanel.add(btnConnect,null);
		
		btnDisconnect = new JButton("Disconnect");
		btnDisconnect.setBounds(new Rectangle(170, 150, 150, 25));
		btnDisconnect.addActionListener(this);
		btnDisconnect.setEnabled(false);
		mainPanel.add(btnDisconnect,null);
		
		  		
		JScrollPane jScrollPane1 = new JScrollPane();
		mainText = new JTextArea("");
		jScrollPane1.setBounds(new Rectangle(10, 200, 610, 240));
		mainPanel.add(jScrollPane1, BorderLayout.CENTER);
		jScrollPane1.getViewport().add(mainText, null);
		//mainPanel.add(mainText,null);
		
		this.setContentPane(mainPanel);
		this.pack();	
		this.setSize(new Dimension(640, 480));
		this.setResizable(false);
		this.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);		
		this.addWindowListener( new WindowAdapter(){
		   public void windowClosing(WindowEvent e) {
		      if(myControl != null) {
		    	  myControl.closeConnection();
		      }
		     System.exit(0);
		     }
		  });
	}

	
	@Override
	public void actionPerformed(ActionEvent ae) {
		// TODO Auto-generated method stub
		if(ae.getSource() instanceof JButton) {
			JButton btn = (JButton)ae.getSource();
			if(btn.equals(btnConnect)) {// connect button
				if(!txtServerHost.getText().isEmpty() && (txtServerHost.getText().trim().length() > 0) &&
						!txtServerPort.getText().isEmpty() && (txtServerPort.getText().trim().length() > 0)) {//custom port
					int port = Integer.parseInt(txtServerPort.getText().trim());
					myControl = new SimpleClientCtr(this, new IPAddress(txtServerHost.getText().trim(), port));
								// new ClientCtr(this, port);
				}else {
					myControl = new SimpleClientCtr(this);
				}
				if(myControl.openConnection()) {
					btnDisconnect.setEnabled(true);
					btnConnect.setEnabled(false);
					mniLogin.setEnabled(true);
				}else {
					if(myControl != null) {
						myControl.closeConnection();
						myControl = null;
					}				
					btnDisconnect.setEnabled(false);
					btnConnect.setEnabled(true);
					mniLogin.setEnabled(false);
				}
			}else if(btn.equals(btnDisconnect)) {// disconnect button
				if(myControl != null) {
					myControl.closeConnection();
					myControl = null;
				}				
				btnDisconnect.setEnabled(false);
				btnConnect.setEnabled(true);
				mniLogin.setEnabled(false);
			}
		}else if(ae.getSource() instanceof JMenuItem) {
			JMenuItem mni = (JMenuItem)ae.getSource();
			if(mni.equals(mniLogin)) {// login function
				SimpleLoginFrm clv = new SimpleLoginFrm(myControl);
				clv.setVisible(true);
			}
		}
	}
	
	public void showMessage(String s){
		  mainText.append("\n"+s);
		  mainText.setCaretPosition(mainText.getDocument().getLength());
	}
		
	public static void main(String[] args) {
		SimpleClientMainFrm view = new SimpleClientMainFrm();
		view.setVisible(true);
	}
}

Class: SimpleLoginFrm.java

package tcp.client.view;

import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;

import model.User;
import tcp.client.control.SimpleClientCtr;

public class SimpleLoginFrm extends JFrame implements ActionListener{
	private JTextField txtUsername;
	private JPasswordField txtPassword;
	private JButton btnLogin;
	private SimpleClientCtr mySocket;
	
	public SimpleLoginFrm(SimpleClientCtr socket){
		super("TCP Login MVC");		
		mySocket = socket;
		
		txtUsername = new JTextField(15);
		txtPassword = new JPasswordField(15);
		txtPassword.setEchoChar('*');
		btnLogin = new JButton("Login");
		
		JPanel content = new JPanel();
		content.setLayout(new FlowLayout());
		content.add(new JLabel("Username:"));
		content.add(txtUsername);
		content.add(new JLabel("Password:"));
		content.add(txtPassword);
		content.add(btnLogin);
		btnLogin.addActionListener(this);
		  		
		this.setContentPane(content);
		this.pack();		
		this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
	}
	

	public void actionPerformed(ActionEvent e) {
		if((e.getSource() instanceof JButton) && (((JButton)e.getSource()).equals(btnLogin))) {
			//pack the entity
			User user = new User();
			user.setUsername(txtUsername.getText());
			user.setPassword(txtPassword.getText());
			
			//sending data
			mySocket.sendData(user);
			
			//receive data
			Object obj = mySocket.receiveData();
			if(obj instanceof String) {
				String result = (String)obj;
				if(result.equals("ok")) {
					JOptionPane.showMessageDialog(this, "Login succesfully!");
					this.dispose();
				}
				else {
					JOptionPane.showMessageDialog(this, "Incorrect username and/or password!");
				}
			}
		}
	}
}

Class: DAO.java

package jdbc.dao;

import java.sql.Connection;
import java.sql.DriverManager;

public class DAO {
	public static Connection con;
	
	public DAO(){
		if(con == null){
			String dbUrl = "jdbc:mysql://localhost:3307/hotel?autoReconnect=true&useSSL=false";
			String dbClass = "com.mysql.jdbc.Driver";

			try {
				Class.forName(dbClass);
				con = DriverManager.getConnection (dbUrl, "root", "xxx");
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
}

Class: UserDAO.java

package jdbc.dao;

import java.sql.PreparedStatement;
import java.sql.ResultSet;

import model.User;

public class UserDAO extends DAO{
	
	public UserDAO() {
		super();
	}
	
	public boolean checkLogin(User user) {
		boolean result = false;
		String sql = "SELECT  id, name, position FROM tblUser WHERE username = ? AND password = ?";
		try {
			PreparedStatement ps = con.prepareStatement(sql);
			ps.setString(1, user.getUsername());
			ps.setString(2, user.getPassword());
			
			ResultSet rs = ps.executeQuery();
			if(rs.next()) {
				user.setId(rs.getInt("id"));
				user.setName(rs.getString("name"));
				user.setPosition(rs.getString("position"));
				result = true;
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		return result;
	}
}

Class: SimpleServerCtr.java

package tcp.server.control;

import java.io.EOFException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

import jdbc.dao.UserDAO;
import model.IPAddress;
import model.User;
import tcp.server.control.ServerCtr.ServerListening;
import tcp.server.view.ServerMainFrm;

public class SimpleServerCtr {
	private ServerMainFrm view;
	private ServerSocket myServer;
	private ServerListening myListening;
	private IPAddress myAddress = new IPAddress("localhost",8888);	//default server host and port
	
	public SimpleServerCtr(ServerMainFrm view){
		this.view = view;
		openServer();	
	}
	
	public SimpleServerCtr(ServerMainFrm view, int serverPort){
		this.view = view;
		myAddress.setPort(serverPort);
		openServer();
	}
	
	
	private void openServer(){
		try {
			myServer = new ServerSocket(myAddress.getPort());
			myListening = new ServerListening();
			myListening.start();
			myAddress.setHost(InetAddress.getLocalHost().getHostAddress());
			view.showServerInfor(myAddress);
			//System.out.println("server started!");
			view.showMessage("TCP server is running at the port " + myAddress.getPort() +"...");
		}catch(Exception e) {
			e.printStackTrace();;
		}
	}
	
	public void stopServer() {
		try {
			myListening.stop();
			myServer.close();
			view.showMessage("TCP server is stopped!");
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * The class to listen the connections from client, avoiding the blocking of accept connection
	 *
	 */
	class ServerListening extends Thread{// without it, the server side would be blocked until a connection appears
		
		public ServerListening() {
			super();
		}
		
		public void run() {
			view.showMessage("server is listening... ");
			try {
				Socket clientSocket = myServer.accept();
				view.showMessage("A client connected to the server!");
				while(true) { // without it, the server could serve only once
					ObjectInputStream ois = new ObjectInputStream(clientSocket.getInputStream());
					ObjectOutputStream oos = new ObjectOutputStream(clientSocket.getOutputStream());

					Object o = ois.readObject();
					if(o instanceof User){
						User user = (User)o;
						UserDAO ud = new UserDAO();
						if(ud.checkLogin(user)){
							oos.writeObject("ok");
						}
						else
							oos.writeObject("false");					
					}
				}
			}catch (EOFException | SocketException e) {				
				//e.printStackTrace();
				view.showMessage("The client disconnected to the server!");
				this.stop();
			}catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}

Class: ServerMainFrm.java

package tcp.server.view;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

import model.IPAddress;
import tcp.server.control.ServerCtr;
import tcp.server.control.SimpleServerCtr;

public class ServerMainFrm extends JFrame implements ActionListener{
	private JTextField txtServerHost, txtServerPort;
	private JButton btnStartServer, btnStopServer;	
	private JTextArea mainText;
	//private ServerCtr myServer;
	private SimpleServerCtr myServer;
	
	public ServerMainFrm(){
		super("TCP server view");
		
		
		JPanel mainPanel = new JPanel();
		mainPanel.setLayout(null);
		
		JLabel lblTitle = new JLabel("Server TCP/IP");
		lblTitle.setFont(new java.awt.Font("Dialog", 1, 20));
		lblTitle.setBounds(new Rectangle(150, 15, 200, 30));
		mainPanel.add(lblTitle, null);
		
		JLabel lblHost = new JLabel("Server host:");
		lblHost.setBounds(new Rectangle(10, 70, 150, 25));
		mainPanel.add(lblHost, null);
		
		txtServerHost = new JTextField(50);
		txtServerHost.setBounds(new Rectangle(170, 70, 150, 25));
		txtServerHost.setText("localhost");
		txtServerHost.setEditable(false);
		mainPanel.add(txtServerHost,null);
		
		JLabel lblPort = new JLabel("Server port:");
		lblPort.setBounds(new Rectangle(10, 100, 150, 25));
		mainPanel.add(lblPort, null);
		
		txtServerPort = new JTextField(50);
		txtServerPort.setBounds(new Rectangle(170, 100, 150, 25));
		mainPanel.add(txtServerPort,null);
		
		btnStartServer = new JButton("Start server");
		btnStartServer.setBounds(new Rectangle(10, 150, 150, 25));
		btnStartServer.addActionListener(this);
		mainPanel.add(btnStartServer,null);
		
		btnStopServer = new JButton("Stop server");
		btnStopServer.setBounds(new Rectangle(170, 150, 150, 25));
		btnStopServer.addActionListener(this);
		btnStopServer.setEnabled(false);
		mainPanel.add(btnStopServer,null);
		
		  		
		JScrollPane jScrollPane1 = new JScrollPane();
		mainText = new JTextArea("");
		jScrollPane1.setBounds(new Rectangle(10, 200, 610, 240));
		mainPanel.add(jScrollPane1, BorderLayout.CENTER);
		jScrollPane1.getViewport().add(mainText, null);
		//mainPanel.add(mainText,null);
		
		this.setContentPane(mainPanel);
		this.pack();		
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setSize(new Dimension(640, 480));
		this.setResizable(false);
	}

	public void showServerInfor(IPAddress addr) {
		txtServerHost.setText(addr.getHost());
		txtServerPort.setText(addr.getPort()+"");
	}
	
	@Override
	public void actionPerformed(ActionEvent ae) {
		// TODO Auto-generated method stub
		if(ae.getSource() instanceof JButton) {
			JButton clicked = (JButton)ae.getSource();
			if(clicked.equals(btnStartServer)) {// start button
				if(!txtServerPort.getText().isEmpty() && (txtServerPort.getText().trim().length() > 0)) {//custom port
					int port = Integer.parseInt(txtServerPort.getText().trim());
					myServer = new SimpleServerCtr(this, port);
								// new ServerCtr(this, port);
				}else {// default port
					myServer = new SimpleServerCtr(this); 
								//new ServerCtr(this);
				}
				btnStopServer.setEnabled(true);
				btnStartServer.setEnabled(false);
			}else if(clicked.equals(btnStopServer)) {// stop button
				if(myServer != null) {
					myServer.stopServer();
					myServer = null;
				}				
				btnStopServer.setEnabled(false);
				btnStartServer.setEnabled(true);
				txtServerHost.setText("localhost");
			}
		}
	}
	
	public void showMessage(String s){
		  mainText.append("\n"+s);
		  mainText.setCaretPosition(mainText.getDocument().getLength());
	}
	
	public static void main(String[] args) {
		ServerMainFrm view = new ServerMainFrm();
		view.setVisible(true);
	}	
}

Limits

  • In this example, the server could accept only one client connection because there is no repeat to accept() in the listening thread.
  • This limit will be avoided in the full example tutorial of TCP/IP

Leave a comment