TIL. 소켓통신으로 채팅만들기

hyuko·2023년 2월 9일
0

소켓통신에 대해 간단히 배웠다

  • 그렇다는건 우리가 이제 소켓통신을 이용해서 간단한 어플같은 것을 만들어 볼 것이다.
  1. 윈도우빌더를 이용해서 화면을 구성한다.
  2. 구성한 화면은 클라이언트, 이 화면이 켜지기 위한 조건으로 서버를 구성한다.
  3. 이 때 서버는 소켓통신을 이용한다.



우선 서버가 될 클래스를 정의한다.

public class ServerApplication{

    public static void main(String[] args) {
        ServerSocket serverSocket = null;

        try {
            serverSocket = new ServerSocket(9090);
            System.out.println("=======<<< 서버 실행 >>> =======");

            Socket socket = serverSocket.accept();

            OutputStream outputStream = socket.getOutputStream();
            PrintWriter writer = new PrintWriter(outputStream, true);
            writer.println("join");

            InputStream inputStream = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

            String welcomeMessage = reader.readLine();
            System.out.println(welcomeMessage);
            writer.println(welcomeMessage);

            while (true) {
                reader.readLine();
            }


        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (serverSocket != null) {
                try {
                    serverSocket.close();

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("=======<<< 서버 종료 >>> =======");
        }
    }
}

여기서 일단 우리는 단일 스레드로 진행을 한다.
기본 예제로 만든 것이기에 하나의 연결만 진행을 하도록합니다.
오늘은 채팅까지는 만들지 못하였고, 서버클래스를 정의하고
뷰를 만들어 연결까지 진행하였다.




ServerSocket serverSocket = null;
serverSocket = new ServerSocket(9090);
  • 여기서 ServerSocket은 서버가 가지는 소켓이다.
  • 이 소켓은 기본적으로 서버에 있는 것이기 때문에 포트번호만 생성당시 받는다.
  • 서버는 기본적으로 본인의 데스크탑이고 127.0.0.1의 주소를 가지고있다.

Socket socket = serverSocket.accept();
  • 소켓을 생성하는데 이 소켓은 서버에 클라이언트가 접속을 해야
    생기는 것이기에 accept()를 하게되면 클라이언트의 접속이 있기 전까지
    주구장창 기다리게 된다.

OutputStream outputStream = socket.getOutputStream();
PrintWriter writer = new PrintWriter(outPutStream, true);
writer.println("join")
  • 자 ! 여기서 밖으로 뿌려주는 스트림인 아웃풋 스트림에
    소켓의 겟 아웃풋 스트림을 대입을 해줍니다.

  • 아웃풋 스트림을 받는 writer를 만들고 println을 합니다.

  • 이 것을 해석하자면 각각의 소켓 지금은 단일 소켓생성이기 때문에
    하나의 소켓에 뿌려줄 말인데 join이라는 단어가 있는지 물어보는 것이다.

본래라면 여기까지 됬다면 클라이언트를 만들러 가야합니다.
왜냐하면 뿌려주는데 까지는 완료를 했고 그 것을 받는 클라이언트를 만들어
받은후 다시 서버에게 아웃풋을 해주는 행동을 해야하기 때문입니다.



클라이언트 생성


public class ChattingClient extends JFrame {
	
	private Socket socket;
	private String username;

	private JPanel contentPane;
	private JTextField ipInput;
	private JTextField portInput;
	private JTextField usernameInput;
	private JTextArea contentView;

	/**
	 * Launch the application.
	 */
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					ChattingClient frame = new ChattingClient();
					frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}

	/**
	 * Create the frame.
	 */
	public ChattingClient() {
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setBounds(100, 100, 450, 300);
		contentPane = new JPanel();
		contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));

		setContentPane(contentPane);
		contentPane.setLayout(null);
		
		ipInput = new JTextField();
		ipInput.setBounds(222, 18, 66, 26);
		contentPane.add(ipInput);
		ipInput.setColumns(10);
		
		portInput = new JTextField();
		portInput.setBounds(295, 18, 66, 26);
		contentPane.add(portInput);
		portInput.setColumns(10);
		
		usernameInput = new JTextField();
		usernameInput.setBounds(222, 56, 139, 26);
		contentPane.add(usernameInput);
		usernameInput.setColumns(10);
		
		JButton connectButton = new JButton("연결");
		connectButton.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				String ip = null;
				int port = 0;

				ip = ipInput.getText();
				port = Integer.parseInt(portInput.getText());

				try {
					socket = new Socket(ip, port);

					JOptionPane.showMessageDialog(null,
							socket.getInetAddress() + "서버 접속",
							"접속성공",
							JOptionPane.INFORMATION_MESSAGE);

					InputStream inputStream = socket.getInputStream();
					BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

					if(reader.readLine().equals("join")) {
						username = JOptionPane.showInputDialog(null,
								"사용자 이름을 입력하세요.",
								JOptionPane.INFORMATION_MESSAGE);

						OutputStream outputStream = socket.getOutputStream();
						PrintWriter writer = new PrintWriter(outputStream, true);

						writer.println(username + "님이 접속하였습니다.");

						String welcomeMessage = reader.readLine();
						contentView.append(welcomeMessage);
					}

				}catch (ConnectException e1) {

					JOptionPane.showMessageDialog(null,
							"서버 접속실 패",
							"접속실패",
							JOptionPane.INFORMATION_MESSAGE);

				} catch (UnknownHostException e2) {
					e2.printStackTrace();
				}catch (IOException ex) {
					ex.printStackTrace();
				}
			}
		});

		connectButton.setBounds(361, 18, 83, 29);
		contentPane.add(connectButton);
		
		JButton joinButton = new JButton("접속");
		joinButton.setBounds(360, 56, 76, 29);
		contentPane.add(joinButton);
		
		JScrollPane contentScroll = new JScrollPane();
		contentScroll.setBounds(20, 16, 176, 206);
		contentPane.add(contentScroll);
		
		contentView = new JTextArea();
		contentScroll.setViewportView(contentView);
		
		JScrollPane userListScroll = new JScrollPane();
		userListScroll.setBounds(220, 106, 204, 112);
		contentPane.add(userListScroll);
		
		JList userList = new JList();
		userListScroll.setViewportView(userList);
		
		JScrollPane messageScroll = new JScrollPane();
		messageScroll.setBounds(30, 234, 320, 32);
		contentPane.add(messageScroll);
		
		JTextArea messageInput = new JTextArea();
		messageScroll.setViewportView(messageInput);
		
		JButton sendButton = new JButton("전송");
		sendButton.setBounds(353, 230, 76, 36);
		contentPane.add(sendButton);
	}
}


연결이라는 버튼을 누르게 되면 서버와 연결되는 구조이고
연결이라는 버튼 이전에 인풋창 두개가 있다.
거기에는 연결될 주소와 포트번호를 받습니다.


connectButton.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				String ip = null;
				int port = 0;

				ip = ipInput.getText();
				port = Integer.parseInt(portInput.getText());

				try {
					socket = new Socket(ip, port);

					JOptionPane.showMessageDialog(null,
							socket.getInetAddress() + "서버 접속",
							"접속성공",
							JOptionPane.INFORMATION_MESSAGE);

					InputStream inputStream = socket.getInputStream();
					BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

					if(reader.readLine().equals("join")) {
						username = JOptionPane.showInputDialog(null,
								"사용자 이름을 입력하세요.",
								JOptionPane.INFORMATION_MESSAGE);

						OutputStream outputStream = socket.getOutputStream();
						PrintWriter writer = new PrintWriter(outputStream, true);

						writer.println(username + "님이 접속하였습니다.");

						String welcomeMessage = reader.readLine();
						contentView.append(welcomeMessage);
					}

				}catch (ConnectException e1) {

					JOptionPane.showMessageDialog(null,
							"서버 접속실 패",
							"접속실패",
							JOptionPane.INFORMATION_MESSAGE);

				} catch (UnknownHostException e2) {
					e2.printStackTrace();
				}catch (IOException ex) {
					ex.printStackTrace();
				}
			}
		});
  • 마우스가 클릭이 됬을 때 이벤트가 일어납니다.
  • 이제 연결되기 위해 ip와 port가 필요합니다.

여기서 중요한 문제 서버가 아닌 클라이언트!!

클라이언트는 소켓을 생성을 할 때 어디에 접속할 것인지에 대한
ip주소와 port번호 두개다 받아야 합니다.

왜냐하면 현재는 자신의 피시한대에서 하나의 소켓만 연결되는 구조에서
연결하는 것이기 때문에 왜그렇게 해야하는지 의아 하지만 보통 서버가
되는 컴퓨터하나에 각각의 유저들이 주소를 탐색해 들어오는 구조입니다.

이때 입력값으로 들어오는 값들을 ip와 port에 넣어줍니다.


InputStream inputStream = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

if(reader.readLine().equals("join")) {
		username = JOptionPane.showInputDialog(null,
						"사용자 이름을 입력하세요.",
					JOptionPane.INFORMATION_MESSAGE);

		OutputStream outputStream = socket.getOutputStream();
		PrintWriter writer = new PrintWriter(outputStream, true);

		writer.println(username + "님이 접속하였습니다.");

		String welcomeMessage = reader.readLine();
		contentView.append(welcomeMessage);
        }
  • 서버인 소켓에서 날린 아웃풋을 받아올 input을 열어줍니다.
  • 그 받은 input을 BufferedReader에 받아서 입력 값을 기다립니다.
  • 이 때 받아온 값이 join이라면 우선 JOptionPane의 메소드인
    인풋창을 띄워주는 showInputDialog를 실행해주고 인풋에 담은 값을
    username에 받아줍니다.

  • 그 후에는 클라이언트측에서 이제 서버측으로 아웃풋을 열어줍니다.
  • 보낼 메세지로는 받은 username과 접속하였다는 메세지를 남겨줍니다.

InputStream inputStream = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

String welcomeMessage = reader.readLine();
System.out.println(welcomeMessage);
writer.println(welcomeMessage);

while (true) {
	reader.readLine();
}
  • 다시 서버측으로 돌아와서 클라이언트 측에서 날린 아웃풋을 받을 인풋을 열어준다.

  • 그후 받은 값을 welcomeMesssage에 담고 콘솔에 프린트와 다시
    클라이언트 측에 welcomeMessage를 날려줍니다.

  • 여기서 while 무한루프를 돌면서 reader.readLine(); 을 해주는 것은 단건의 스레드기때문에 결과들이 들어오게되면 바로 서버가 종료되는 것을 막아주기 위함이다.


String welcomeMessage = reader.readLine();
						contentView.append(welcomeMessage);
  • 마지막으로 클라이언트에서 아까 열어놓은 통로로 welcomeMessage를 받아옵니다.
  • 그것을 contentView에 받은 메세지를 추가해서 보여주면 됩니다.


마지막으로

오늘 진행 한 것은 아주 간단히 단건 스레드로 뷰를 만들고
그 뷰와 소켓통신을 이용해서 주소와 포트번호를 입력받아 연결하는 것까지
진행을 하였습니다.

지금까지 자바학습중 가장 어렵고 심도있는 내용들이 많았던 만큼
많은 실습과 연습을 해야 할 것같습니다!!

profile
백엔드 개발자 준비중

0개의 댓글