본문 바로가기
카테고리 없음

Socket과 Thread의 통신 프로그램 만들기

by SuldenLion 2024. 1. 6.
반응형

이번 포스팅에서는 Socket과 Thread를 활용한 통신 프로그램을 다뤄볼 것이다. 이전의 RMI 통신 프로그램과는 다른 방식이다. 만든 버전별로 통신 프로그램을 분석해볼 것인데, 그전에 앞서 다른 예제를 통해 대략적인 Thread 형식과 Socket 프로그램의 형식을 살펴보겠다.

 

우선, Core Java의 예제와 내용을 참조하여 Thread간 통신을 위한 파이프 사용을 살펴보겠다. 

Thread 간의 통신패턴은 아주 단순할때가 많다. Producer(생산자)라 불리는 하나의 Thread는 byte의 스트림을 만들어낸다. Consumer(소비자)라 불리는 다른 Thread는 해당 byte 스트림을 읽고 처리한다. 만약 읽기 위한 byte 스트림이 없을때는 Consumer Thread는 block된다. 만약 Producer가 Consumer의 처리하는 것보다 빨리 데이터를 많이 생성하면 Consumer Thread의 write 연산은 block된다. Java에서는 PipedInputStream PipedOutputStream이라는 통신 패턴을 구현한 일련의 편리한 클래스들을 가진다. (byte대신 유니코드 String 스트림을 생성할때는 PipedReader/PipedWriter 사용)

 

파이프를 사용하는 기본적인 이유는 각 Thread를 단순하게 유지하기 위해서이다. Producer Thread는 단순히 스트림에 결과를 보내고 이를 잊어버린다. Consumer Thread는 어디서 오는지 상관할 필요없이 스트림으로부터 데이터를 읽는다. 즉 파이프를 사용하면 Thread 동기화에 대한 걱정없이 다중 Thread들을 서로 연결할 수 있다. 

 

아래의 예제 PipeTest.java는 파이프 스트림들을 보여주는 프로그램이다. Random한 시점에 Random한 숫자를 만들어내는 Producer Thread가 있다. Filter Thread는 입력 숫자를 읽고 연속해서 데이터의 평균을 계산할 것이다. 그리고 Consumer Thread는 결과를 출력할 것이다. 그림으로 표현하자면 아래와 같이 될 것이다.

 

 

Core Java의 PipeTest.java 예제를 보자.

/**
 * @version 1.20 1999-04-23
 * @author Cay Horstmann
 */

import java.util.*;
import java.io.*;

public class PipeTest
{  public static void main(String args[])
   {  try
      {  /* set up pipes */
         PipedOutputStream pout1 = new PipedOutputStream();
         PipedInputStream pin1 = new PipedInputStream(pout1);

         PipedOutputStream pout2 = new PipedOutputStream();
         PipedInputStream pin2 = new PipedInputStream(pout2);

         /* construct threads */

         Producer prod = new Producer(pout1);
         Filter filt = new Filter(pin1, pout2);
         Consumer cons = new Consumer(pin2);

         /* start threads */

         prod.start();
         filt.start();
         cons.start();
      }
      catch (IOException e){}
   }
}

class Producer extends Thread
{  public Producer(OutputStream os)
   {  out = new DataOutputStream(os);
   }

   public void run()
   {  while (true)
      {  try
         {  double num = rand.nextDouble();
            out.writeDouble(num);
            out.flush();
            sleep(Math.abs(rand.nextInt() % 1000));
         }
         catch(Exception e)
         {  System.out.println("Error: " + e);
         }
      }
   }

   private DataOutputStream out;
   private Random rand = new Random();
}

class Filter extends Thread
{  public Filter(InputStream is, OutputStream os)
   {  in = new DataInputStream(is);
      out = new DataOutputStream(os);
   }

   public void run()
   {  for (;;)
      {  try
         {  double x = in.readDouble();
            total += x;
            count++;
            if (count != 0) out.writeDouble(total / count);
         }
         catch(IOException e)
         {  System.out.println("Error: " + e);
         }
      }
   }

   private DataInputStream in;
   private DataOutputStream out;
   private double total = 0;
   private int count = 0;
}

class Consumer extends Thread
{  public Consumer(InputStream is)
   {   in = new DataInputStream(is);
   }

   public void run()
   {  for(;;)
      {  try
         {  double avg = in.readDouble();
            if (Math.abs(avg - old_avg) > 0.01)
            {  System.out.println("Current average is " + avg);
               old_avg = avg;
            }
         }
         catch(IOException e)
         {  System.out.println("Error: " + e);
         }
      }
   }

   private double old_avg = 0;
   private DataInputStream in;
}

 

main 프로그램에서는 우선 Producer와 Filter 그리고 Consumer를 연결할 PipedStream들을 선언해준다. 그리고 Producer, Filter, Consumer 각각에 해당하는 Thread들을 생성해 줄 것이다. 그러고 나서 그 Thread들을 돌린다.

 

Producer 클래스를 먼저 보겠다. Constructor에는 OutputStream으로 쓸 parameter를 DataOutputStream으로써 생성해준다. run 메서드에서는 random double number를 만들어서 Stream을 통해 내보낼것이다. 그리고나서 무작위 초 동안 Thread를 sleep 시킨다. 

Filter 클래스는 Constructor에서 InputStream과 OutputStream을 하나씩 받아와서 생성해주는데 각각은 Producer로부터 온 data를 받을 InputStream과 Consumer로 data를 보낼 OutputStream이다. run 메서드는 DataInputStream으로부터 들어온것을 readDouble로 읽어주고 total에 그 값을 더해준다. count도 1씩 증가하면서 count가 0이 아닌경우 DataOutputStream에 (total / count)한 값을 출력시켜준다.

마지막으로 Consumer 클래스는 Filter로부터 값을 전달받을 InputStream을 장착시켜주고 run 메서드에서도 대충 비슷한 작업을 할 것이다. avg에 받아온 값을 담고, 이전에 들어온 avg값인 old_avg를 뺀 것의 절대값이 0.01이 넘는 경우에 현재 avg를 출력시켜준다.

 

여기까지가 Thread간 통신을 위한 Pipe 사용이었다.

 

 

이제 Socket을 사용한 프로그램들을 살펴보자.

 

import java.io.*;
import java.net.*;

public class ClientExample {
	public static void main(String[] args) {
		Socket socket = null;
				
		try {
			socket = new Socket();
			System.out.println("[Request connect]");
			socket.connect(new InetSocketAddress("localhost", 5001));
			System.out.println("[Success connect]");
			
			byte[] bytes = null;
			String message = null;
			
			OutputStream os = socket.getOutputStream();
			message = "Hello Server~";
			bytes = message.getBytes("UTF-8");
			os.write(bytes);
			os.flush();
			System.out.println("[Data sended]");
			
			InputStream is = socket.getInputStream();
			bytes = new byte[128];
			int readByteCount = is.read(bytes);
			message = new String(bytes, 0, readByteCount, "UTF-8");
			System.out.println("[Data received] " + message);
			
			os.close();
			is.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		if (!socket.isClosed()) {
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}	
	}
}

 

원격지 컴퓨터 상의 운영체제는 포트 번호 5001에 연결하기 위한 요청을 포함하는 네트워크 패키지를 얻을때 Listening 프로세스를 깨우고 연결을 수행한다. 연결은 클라이언트와 서버 중의 하나에 의해서 종료될 때까지 계속된다.

InetAddress 클래스는 Host이름과 바이트 형태의 인터넷 주소 사이에 변환이 필요할 때 사용한다.

보내고자 하는 문자열형태의 메시지를 준비한다. 그리고 그것을 UTF-8 타입(유니코드 인코딩 방식)의 바이트 형태로 뽑아낸 후 byte배열에 넣어주겠다. Socket에 있는 OutputStream에 모아둔 바이트들을 출력시켜주면 된다.

데이터를 받아오는 과정도 비슷할 것이다. Socket의 InputStream과 data를 읽어들일 byte배열을 준비한다. readByteCount에는 Stream으로 읽어온 byte들의 수를 저장한다. inputStream.read(b) → Reads some number of bytes from the input stream and stores them into the buffer array b. 메시지에는 해당 byte의 길이 0에서 readByteCount까지의 바이트를 문자열로 만들어준다.

 

import java.io.*;
import java.net.*;

public class ServerExample {
	public static void main(String[] args) {
		ServerSocket serverSocket = null;
		
		try {
			serverSocket = new ServerSocket();
			serverSocket.bind(new InetSocketAddress("localhost",5001));
			while (true) {
				System.out.println("[Waiting connect]");
				Socket socket = serverSocket.accept();
				InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
				System.out.println("[Accept connect] " + isa.getHostName());
				
				byte[] bytes = null;
				String message = null;
				
				InputStream is = socket.getInputStream();
				bytes = new byte[128];
				int readByteCount = is.read(bytes);
				message = new String(bytes, 0, readByteCount, "UTF-8");
				System.out.println("[Data received] " + message);
				
				OutputStream os = socket.getOutputStream();
				message = "Hello Client~";
				bytes = message.getBytes("UTF-8");
				os.write(bytes);
				os.flush();
				System.out.println("[Data sended]");
				
				is.close();
				os.close();
				socket.close();
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		if (!serverSocket.isClosed()) {
			try {
				serverSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}	
	}
}

 

ServerSocket의 bind는 network 간의 통신을 시작하기전에 세션을 설정하는 것을 의미한다고 한다. Binds the ServerSocket to a specific address (IP address and port number). 

Socket 객체를 만들어주고 serverSocket.accept() 를 호출하여 연결시켜준다. accept 메서드는 Listens for a connection to be made to this socket and accepts it. 연결을 받아들이는 역할을 한다. 

InetSocketAddress 객체를 socket의 getRemoteSocketAddress()하여 만들어낸다. Returns the address of the endpoint this socket is connected to, or null if it is unconnected. 그리고 이 객체의 hostname을 출력한다.

Stream을 통한 데이터 전달 방식은 앞서 본 Client와 같다.

 

 

 

이제 본격적으로 채팅프로그램을 소개하겠다.

 

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.ArrayList;

class ButtonPanel extends JPanel
{
	MainPanel clientWnd;
	JTextField ip;
	JButton connectButton;
	ButtonPanel(MainPanel clientWnd) {
		this.clientWnd = clientWnd;

		ip = new JTextField(15);
		connectButton = new JButton("Connect");

		ip.setText("localhost");

		add(connectButton);
		add(ip);

		connectButton.addActionListener(clientWnd);
	}
	String getIpAddress() {
		return ip.getText();
	}
	void changeButton() {
		if (connectButton.getText().equals("Connect")) {
			connectButton.setText("Disconnect");
		} else if (connectButton.getText().equals("Disconnect")) {
			connectButton.setText("Connect");
		}
	}
}
class InputListener extends KeyAdapter
{
	MainPanel clientWnd;
	JTextField textInput;
	InputListener(JTextField textInput,MainPanel clientWnd) {
		this.clientWnd = clientWnd;
		this.textInput = textInput;
	}
	public void keyPressed(KeyEvent ev) {
		int keyCode = ev.getKeyCode();
		if (keyCode == KeyEvent.VK_ENTER)
		{
			String msg = textInput.getText().trim();
			clientWnd.sendText("[I say] " + msg);
			clientWnd.sendLine(msg);
			textInput.setText("");
		}
	}
}
/*class ReportPanel extends JPanel
{
	MainPanel clientWnd;
	JTextField reportText;
	JButton reportButton;
	JButton queryButton;
	ReportPanel(MainPanel clientWnd) {
		this.clientWnd = clientWnd;

		reportText = new JTextField(15);
		reportButton = new JButton("Report");
		queryButton = new JButton("Query");

		add(reportText);
		add(reportButton);
		add(queryButton);

		reportButton.addActionListener(clientWnd);
		queryButton.addActionListener(clientWnd);
	}
	String getReportText() {
		return reportText.getText();
	}
}*/
class InputPanel extends JPanel
{
	MainPanel clientWnd;
	JTextField textInput;
	//ReportPanel reportPanel;
	InputPanel(MainPanel clientWnd) {
		this.clientWnd = clientWnd;
		setLayout(new GridLayout(2,1));

		textInput = new JTextField();
		textInput.addKeyListener(new InputListener(textInput,clientWnd));
		//reportPanel = new ReportPanel(clientWnd);

		add(textInput);
		//add(reportPanel);
	}
	/*String getReportText() {
		return reportPanel.getReportText();
	}*/
}
//-------------------------------------------

class MsgPanel extends JPanel
{
	MainPanel clientWnd;
	JPanel panel;
	int sendX = 250;
	int receiveX = 30;
	int yPos;
	String currentMsg = "";
	String prevMsg = "";
	boolean isSending;
	boolean isReceiving;
	int cnt = 0;

	MsgPanel(MainPanel clientWnd) {
		this.clientWnd = clientWnd;
		panel = new JPanel();
		panel.setForeground(Color.red);
		yPos = 20;
		add(panel);
	}

	public void paintComponent(Graphics g) {
		//super.paintComponent(g);

/*		for (int i = 0; i < cnt; i++) {
			if (isSending) {
				g.drawString(currentMsg, sendX, yPos);

			}
			if (isReceiving) {
				g.drawString(currentMsg, receiveX, yPos);
			}

		} */

		yPos += 20;

		if (isSending) {
			//g.drawString(currentMsg, sendX, yPos);
			if (currentMsg.length() > 20) {
				g.drawString(currentMsg.substring(0,20), sendX, yPos);
				currentMsg = currentMsg.substring(20);
				g.drawString(currentMsg, sendX, yPos+12);
			}

		}
		if (isReceiving) {
			g.drawString(currentMsg, receiveX, yPos);
		}
	}

/*	public void draw(Graphics g) {
		if (isSending) {
			g.drawString(currentMsg, sendX, yPos);
			g.drawString(prevMsg, sendX, yPos-20);
		}
		if (isReceiving) {
			g.drawString(currentMsg, receiveX, yPos);
			g.drawString(prevMsg, receiveX, yPos-20);
		}

	}*/

	void sending(String str) {
		isSending = true;
		isReceiving = false;
		currentMsg = str;
		cnt++;
		repaint();
	}

	void receiving(String str) {
		isSending = false;
		isReceiving = true;
		currentMsg = str;
		cnt++;
		repaint();
	}

}
//------------------------------------------
class MainPanel extends JPanel implements ActionListener
{
	String ipAddress = "localhost";
	Socket chatClient;
	BufferedReader fromChatServer;
	PrintWriter toChatServer;
	Thread chatThread;

	ButtonPanel buttonPanel;
	InputPanel inputPanel;
//	JTextArea textBox;
	JScrollPane textPane;
	MsgPanel msgPanel;	

	MainPanel() {
		buttonPanel = new ButtonPanel(this);
		setLayout(new BorderLayout());

		//textBox = new JTextArea();
		msgPanel = new MsgPanel(this);
		//System.out.println(msgPanel.getBounds());
		textPane = new JScrollPane(msgPanel);
		//textPane.setBounds(50,50,200,200);
		//textPane.getVerticalScrollBar();
		//textPane.setVerticalScrollBar(new JScrollBar());
		//textBox.setBorder(BorderFactory.createLoweredBevelBorder());
		inputPanel = new InputPanel(this);

		//textPane.add(new JButton("버튼~~"));

		//add(msgPanel);
		add(buttonPanel,BorderLayout.NORTH);
		add(textPane,BorderLayout.CENTER);
		add(inputPanel,BorderLayout.SOUTH);

		textPane.registerKeyboardAction(
			new ActionListener() {
				public  void actionPerformed(ActionEvent evt) {
					JScrollBar scrollBar = textPane.getVerticalScrollBar();
					scrollBar.setValue(scrollBar.getValue() + scrollBar.getBlockIncrement());
				}
			}
			, KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)
			, JComponent.WHEN_IN_FOCUSED_WINDOW
		);

	}
	public void sendText(String msg) {
		msgPanel.sending(msg);

	}

	public void receiveText(String msg) {
		msgPanel.receiving(msg);
	}

	//=====
/*	public void sendMsg(String msg) {
		JLabel label = new JLabel();
		label.setBounds(300, 300, 50, 50);

	}*/

	public void sendLine(String msg) {
		toChatServer.println(msg);
		toChatServer.flush();
	}
	public void actionPerformed(ActionEvent ev) {
		String cmd = ev.getActionCommand();
		String ipAddress = buttonPanel.getIpAddress();

		if (cmd.equals("Connect"))
		{
			try
			{
				buttonPanel.changeButton();
				chatClient = new Socket(ipAddress,7000);
				receiveText("Connected...");
				fromChatServer = new BufferedReader(new InputStreamReader(chatClient.getInputStream()));
				toChatServer = new PrintWriter(chatClient.getOutputStream());

				toChatServer.println("0");
				toChatServer.flush();

				chatThread = new Thread() {
					public void run() {
						try
						{
							String msg;
							while(true) {
								msg = fromChatServer.readLine();
								receiveText("[Server says] " + msg);
							}
						}
						catch (IOException ex)
						{
							System.out.println(ex);
						}
					}
				};
				chatThread.start();
			}
			catch (IOException ex)
			{
				System.out.println(ex);
				chatThread.stop();
			}
		} else if (cmd.equals("Disconnect"))
		{
			try
			{
				buttonPanel.changeButton();
				fromChatServer.close();
				toChatServer.close();
				chatClient.close();
				chatThread.stop();
			}
			catch (IOException ex)
			{
				System.out.println(ex);
			}
		}
	}
}
class ClientFrame extends JFrame
{
	ClientFrame() {
		super("Client");
		setSize(400,600);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		Container contentPane = getContentPane();
		contentPane.add(new MainPanel());
	}
}
class TestClient 
{
	public static void main(String[] args) 
	{
		ClientFrame clientFrame = new ClientFrame();
		clientFrame.setResizable(false);
		clientFrame.setVisible(true);
	}
}

 

클라이언트 프로그램이다. 기능만 돌아가는 망한 버전이다. 일단 기능적으론 문제가 없으니 살펴보겠다.

ClientFrame 객체를 resizable 형태로 생성해준다. Frame에는 화면들을 담아줄 MainPanel을 만들어준다. MainPanel은 삼등분하여 ip주소를 받아와 Connect 버튼 클릭을 해줄 buttonPanel, message 내용들을 scrollPane 형태로 가운데에 보여질 msgPanel, message를 칠 textField를 포함하는 inputPanel로 구성된다. MainPanel의 textPane.registerKeyboardAction은 대충 Focus가 textPanel에 맞춰져 있을때 PageDown 키를 누르면 scrollBar의 value값과 getBlockIncrement 만큼 더한걸 늘려주는것 같다. JScrollBar의 getBlockIncrement() 메서드는 Returns the amount to change the scrollbar's value by, given a block (usually "page") up/down request.라고 설명되어진다. 

프로그램 작동 절차는 채팅하기 원하는 Server 프로그램의 ip주소를 입력 후, Connect 버튼을 누른다. 이때, 서버 프로그램에서는 먼저 Socket을 열어놓고 연결을 위한 Thread를 가동시켜놔야 한다. 

 

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.*;

class ClientService extends Thread
{
	Socket client;
	HashMap<String,PrintWriter> writers;
	MainPanel wnd;
	ClientService(Socket client,HashMap<String,PrintWriter> writers,MainPanel wnd) {
		this.client = client;
		this.writers = writers;
		this.wnd = wnd;
	}
	public void run() 
	{
		try
		{
			BufferedReader fromClient = 
				new BufferedReader(new InputStreamReader(client.getInputStream()));
			PrintWriter toClient = 
				new PrintWriter(new OutputStreamWriter(client.getOutputStream()));
			
			int n;

			String s = fromClient.readLine();
			n = Integer.parseInt(s);
			String ip = client.getInetAddress().getHostAddress();

			if (n == 1) // report mode
			{
				wnd.writeText("[" + ip + "] " + "reported his selling today.");
				String selling = fromClient.readLine();
				wnd.writeText("[" + ip + "] " +  selling + " Won.");
				toClient.println("Thanks!!!");
				toClient.flush();

				fromClient.close();
				toClient.close();
				client.close();
				return;
			} else if (n == 2) // query mode
			{
				int x = (int)(Math.random()*10);

				toClient.println(""+x);
				toClient.flush();
				wnd.writeText("[" + ip + "] " + "asked his total selling.");

				fromClient.close();
				toClient.close();
				client.close();
				return;
			}
			// chatting mode
			writers.put(ip,toClient);
			String msg;
			while(true) {
				msg = fromClient.readLine();
				if (msg.equals("-1"))
				{
					break;
				}
				wnd.writeText("[" + ip + " says] " + msg);
			}

			writers.remove(ip);
			wnd.writeText("Connection with " + ip + " closed.");

			fromClient.close();
			toClient.close();
			client.close();
		}
		catch (IOException ex)
		{
			System.out.println(ex);
		}
	}
}
class ServerRole extends Thread
{
	ServerSocket listenSocket;
	MainPanel wnd;
	HashMap<String,PrintWriter> writers;

	public ServerRole(MainPanel serverWnd) {
		this.wnd = serverWnd;
		writers = new HashMap<String,PrintWriter>();
	}
	public void run()
	{
		try
		{
			listenSocket = new ServerSocket(7000);
			wnd.writeText("Server started...");
			wnd.writeText(wnd.myIP + " on port: 7000");
			while(true) {
				Socket client = listenSocket.accept();
				String ip = client.getInetAddress().getHostAddress();
				wnd.writeText(ip + " is connected...");
				wnd.addComboBoxItem(ip);

				ClientService connection = new ClientService(client,writers,wnd);
				connection.start();
			}
		}
		catch (IOException ex)
		{
			System.out.println(ex);
		}
	}
	public void closeSocket()
	{
		try
		{
			listenSocket.close();
		}
		catch (IOException ex)
		{
			System.out.println(ex);
		}
	}
	public void sendLine(String ip,String msg)
	{
		PrintWriter toClient = writers.get(ip);
		toClient.println(msg);
		toClient.flush();
	}
}

class ButtonPanel extends JPanel
{
	MainPanel serverWnd;
	JButton startButton;
	JButton stopButton;
	//JButton salesRecordButton;
	ButtonPanel(MainPanel serverWnd) {
		this.serverWnd = serverWnd;

		startButton = new JButton("Start");
		stopButton = new JButton("Stop");
		//salesRecordButton = new JButton("Sales Record");

		add(startButton);
		add(stopButton);
		//add(salesRecordButton);

		startButton.addActionListener(serverWnd);
		stopButton.addActionListener(serverWnd);
		//salesRecordButton.addActionListener(serverWnd);
	}
}
class InputListener extends KeyAdapter
{
	MainPanel serverWnd;
	JTextField textInput;
	InputListener(JTextField textInput,MainPanel serverWnd) {
		this.serverWnd = serverWnd;
		this.textInput = textInput;
	}
	public void keyPressed(KeyEvent ev) {
		int keyCode = ev.getKeyCode();
		if (keyCode == KeyEvent.VK_ENTER)
		{
			String msg = textInput.getText().trim();
			serverWnd.writeText("[I say ] " + msg);
			String clientIP = serverWnd.getSelectedIP();
			serverWnd.sendLine(clientIP,msg);
			textInput.setText("");
		}
	}
}
class InputPanel extends JPanel
{
	MainPanel serverWnd;
	JComboBox<String> ips;
	JTextField textInput;
	InputPanel(MainPanel serverWnd) {
		this.serverWnd = serverWnd;
		setLayout(new GridLayout(2,1));

		ips = new JComboBox<String>();
		textInput = new JTextField();
		textInput.addKeyListener(new InputListener(textInput,serverWnd));

		add(ips);
		add(textInput);
	}
	public void addComboBoxItem(String ip) {
		int n;
		
		ips.removeItem(ip);
		ips.insertItemAt(ip,0);
		ips.setSelectedIndex(0);
	}
	String getSelectedIP() {
		return ips.getItemAt(ips.getSelectedIndex());
	}
}
class MainPanel extends JPanel implements ActionListener
{
	ButtonPanel buttonPanel;
	JTextArea textBox;
	JScrollPane textPane;
	InputPanel inputPanel;
	ServerRole server;
	String myIP;

	MainPanel() {
		buttonPanel = new ButtonPanel(this);
		setLayout(new BorderLayout());

		textBox = new JTextArea();
		textPane = new JScrollPane(textBox);
		textBox.setBorder(BorderFactory.createLoweredBevelBorder());
		inputPanel = new InputPanel(this);

		add(buttonPanel,BorderLayout.NORTH);
		add(textPane,BorderLayout.CENTER);
		add(inputPanel,BorderLayout.SOUTH);
		writeText("Please press start button");

		try
		{
			InetAddress address = InetAddress.getLocalHost();
			myIP = address.getHostAddress();
		}
		catch (Exception ex)
		{
		}
	}
	String getSelectedIP() {
		return inputPanel.getSelectedIP();
	}
	void sendLine(String ip,String msg) {
		server.sendLine(ip,msg);
	}
	public void addComboBoxItem(String ip) {
		inputPanel.addComboBoxItem(ip);
	}
	public void writeText(String msg) {
		textBox.append(msg + "\r\n");
	}
	public void actionPerformed(ActionEvent ev) {
		String command = ev.getActionCommand();
		if (command.equals("Start"))
		{
			server = new ServerRole(this);
			server.start();
		} else if (command.equals("Stop"))
		{
			server.closeSocket();
			server.stop();
		}
	}
}
class ServerFrame extends JFrame
{
	ServerFrame() {
		super("Server");
		setSize(400,600);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		Container contentPane = getContentPane();
		contentPane.add(new MainPanel());
	}
}
class TestServer 
{
	public static void main(String[] args) 
	{
		ServerFrame serverFrame = new ServerFrame();
		serverFrame.setVisible(true);
	}
}

 

위의 ServerRole이라는 클래스가 Server역할을 할 Thread이다. Client와의 연결을 위한 Socket을 7000 포트로 설정해 놓고 accept되는 socket들을 client들로써 받아들인다. 해당 클라이언트들의 ip 주소는 client.getInetAddress().getHostAddress()를 하여 받아놓고 comboBox에 값을 저장해 놓도록 하겠다. 현재 Server가 다수의 Client들에게 연결이 왔을때 메시지를 보내고자 하는 특정 클라이언트만 comboBox에서 골라서 채팅을 하기 위함이다. ClientService Thread를 start해주겠다. ClientService에서는 Client에서의 데이터를 받아들이기 위한 StreamReader들을 가져오고 그걸 통해 client로 부터 온 메시지들을 읽고 출력한다. report와 query 모드는 사용하지 않으니 무시해도 된다. report 버튼과 query 버튼 그리고 채팅으로 나눠서 특정 버튼을 누르면 1, 2, 3값이 날라오게 해서 각각을 구분하여 작업했지만 생략하겠다. 메시지가 -1을 표현할때까지 채팅을 읽어 오는 것이다. 

 

 

 

Client 프로그램 기본 화면

 

 

Server 프로그램 기본 화면

 

Start 버튼을 누르고 Client 쪽에서 connect한 화면이다

 

 

localHost에 연결 Test

 

Client 쪽에 GUI가 깨져서 나오는데 scrollPanel도 안달린게 아마 가운데 판넬이 잘못 된것같다. 

이 버전은 중요한 버전이 아니기에 대충 넘어가겠다.

프로그램 종료시에는 열려있는 socket에 의한 메모리 누수를 막기위해 Stop 버튼으로 소켓을 닫아준다.

 

 

 

이것보다 제대로 만든 SuldenTalk이라는 통신프로그램을 글이 길어지므로 다음 게시글에다 올려보겠다.

반응형

댓글