java

쉽지만 어려운 #Java #자바 #성공적 8. Socket.io 1부(소켓) 네트워크 채팅

sieunju 2016. 6. 20. 22:42
반응형

안녕하세요 J.sieun 입니다. 


이번글은 자바 중에서 좀 까다로운 Socket 이 되겠습니다.

우선 데이터를 어떻게 주고 받는지에 대해 설명해 보겠습니다. 

용어 설명:

-클라이언트 프로그램(Client Program) : 연결을 요청하는 통신 프로그램.

-서버 프로그램(Server Program) : 연결 요청을 기다리는 통신 프로그램.


클라이언트 프로그램과 서버 프로그램의 통신 과정

간단하게 저런 식으로 정보를 주고 받습니다. 


Java 에서는 소켓(Socket) 이라고 하는 것을 지원 합니다. 

-서버 소켓

서버 프로그램에서만 사용되는 소켓

연결 요청을 기다리다가, 연결 요청이 오면 연결을 맺고 또 다른 소켓을 생성

-클라이언트 소켓

클라이언트 프로그램과 서버 프로그램에서 모두 사용되는 소켓

실제 데이터 전송에서 사용되는 것은 이 소켓임

서버 프로그램에서는 서버 소켓에 의해 생성됨

클라이언트 프로그램에서는 직접 생성해야함


간단히 Swing 으로 한 채팅 프로그램 소스를 보도록 하겠습니다. 


서버 부분

import java.awt.*;

import java.awt.event.*;

import java.io.*;

import java.net.*;


import javax.swing.*;


public class ServerChat {


static JFrame frame;

static JPanel panel;


public static void main(String[] args) {


ServerSocket serverSocket = null;

Socket socket = null;

frame = new JFrame();

frame.setTitle("서버");

frame.setSize(300, 600);

frame.setLocation(500, 400);

frame.setResizable(false);

Container contentPane = frame.getContentPane();

contentPane.setLayout(null);


JPanel panel = new JPanel();


panel.setLocation(300, 10);

panel.setSize(500, 500);

contentPane.add(panel);


JTextArea ta = new JTextArea();

ta.setSize(286, 400);

ta.setLocation(0, 0);

ta.setEditable(false);

JScrollPane scroll = new JScrollPane(ta);

scroll.setSize(286, 400);

scroll.setLocation(0, 0);

contentPane.add(scroll);


JLabel ipLabel = new JLabel("IP주소");

ipLabel.setLocation(20, 415);

ipLabel.setSize(50, 15);

contentPane.add(ipLabel);


JTextField ipField = new JTextField();

ipField.setLocation(78, 410);

ipField.setSize(167, 25);

contentPane.add(ipField);


JLabel portLabel = new JLabel("포트번호");

portLabel.setLocation(20, 460);

portLabel.setSize(60, 15);

contentPane.add(portLabel);


JTextField portField = new JTextField();

portField.setLocation(78, 455);

portField.setSize(50, 25);

contentPane.add(portField);


JButton start = new JButton("서버실행");

start.setLocation(150, 455);

start.setSize(95, 25);

contentPane.add(start);


JTextField chatField = new JTextField(" ");

chatField.setLocation(20, 500);

chatField.setSize(160, 25);

contentPane.add(chatField);


final JButton send = new JButton("전송");

send.setLocation(185, 500);

send.setSize(60, 25);

contentPane.add(send);


chatField.addKeyListener(new KeyAdapter() {

public void keyPressed(KeyEvent e) {

if (e.getKeyCode() == KeyEvent.VK_ENTER) {

send.doClick();

}

}

});

start.addActionListener(new ServerStartActionListener(ta, serverSocket,

socket, portField, send, chatField, scroll, panel));


try {

InetAddress add = InetAddress.getLocalHost();

String strAddress = add.getHostAddress();

ta.append("서버의 IP주소는 : " + strAddress + "\n"

+ "채팅에 사용할 포트 번호를 입력하세요.\n");

ipField.setText(strAddress);


//테스트

//portField.setText("9999");

} catch (Exception e) {

System.out.println(e.getMessage());

}

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setVisible(true);

panel.setFocusable(true);

panel.requestFocus();

}

}


class ServerStartActionListener implements ActionListener {

JTextArea ta;

ServerSocket serverSocket;

Socket socket;

JTextField portField;

JTextField chatField;

JButton send;

JScrollPane scroll;

JPanel panel;


ServerStartActionListener(JTextArea ta, ServerSocket serverSocket,

Socket socket, JTextField portField, JButton send,

JTextField chatField, JScrollPane scroll, JPanel panel) {

this.ta = ta;

this.serverSocket = serverSocket;

this.socket = socket;

this.portField = portField;

this.send = send;

this.chatField = chatField;

this.scroll = scroll;

this.panel = panel;

}


public void actionPerformed(ActionEvent e) {

try {


int port = Integer.parseInt(portField.getText());

serverSocket = new ServerSocket(port);

socket = serverSocket.accept();


Thread thread2 = new ServerReceiverThread(socket, ta, scroll, panel);

thread2.start();


send.addActionListener(new ServerSendActionListener(ta, socket, chatField, serverSocket, scroll, panel));


} catch (Exception e1) {

ta.append("서버생성 오류");

} finally {

ta.append("채팅을 시작하세요.\n");

}

}

}


class ServerSendActionListener implements ActionListener {

JTextArea ta;

Socket socket;

JTextField chatField;

ServerSocket serverSocket;

JScrollPane scroll;

JPanel panel;

JLabel laServer;


ServerSendActionListener(final JTextArea ta, final Socket socket, JTextField chatField,

ServerSocket serverSocket, JScrollPane scroll, final JPanel panel) {

this.ta = ta;

this.chatField = chatField;

this.serverSocket = serverSocket;

this.socket = socket;

this.scroll = scroll;

this.panel = panel;


panel.addKeyListener( new KeyAdapter() {

public void keyPressed(KeyEvent e) {

int keyCode = e.getKeyCode();

System.out.println("keyCode = " + keyCode);

switch(keyCode) {

case KeyEvent.VK_UP: 

laServer.setLocation(laServer.getX(), laServer.getY()-10);

break;

case KeyEvent.VK_DOWN: 

laServer.setLocation(laServer.getX(), laServer.getY()+ 10); 

break;

case KeyEvent.VK_LEFT: 

laServer.setLocation(laServer.getX()- 10, laServer.getY()); 

break;

case KeyEvent.VK_RIGHT: 

laServer.setLocation(laServer.getX()+ 10, laServer.getY()); 

break;

}

try {

ta.append("송신>" + "#" + keyCode + "\n");

PrintWriter writer = new PrintWriter(socket.getOutputStream());

writer.println("#" + keyCode);

writer.flush();

} catch (Exception e1) {

ta.append("메세지 전송실패 : " + e1.toString());

}

}

} );

panel.setFocusable(true);

panel.requestFocus();

}


public void actionPerformed(ActionEvent e) {


try {


PrintWriter writer = new PrintWriter(socket.getOutputStream());

String str = chatField.getText();

ta.append("송신>" + str + "\n");

scroll.getVerticalScrollBar().setValue(

scroll.getVerticalScrollBar().getMaximum());

//chatField.requestFocus();

chatField.setText(" ");

writer.println(str);

writer.flush();

panel.requestFocus();


} catch (Exception e1) {

ta.append("메세지 전송실패 : " + e1.toString());

}

}

}


class ServerReceiverThread extends Thread {

Socket socket;

JTextArea ta;

JScrollPane scroll;

JPanel panel;

int keyCode;


ServerReceiverThread(Socket socket, JTextArea ta, JScrollPane scroll, JPanel panel) {

this.ta = ta;

this.socket = socket;

this.scroll = scroll;

this.panel = panel;

}


public void run() {

try {

BufferedReader reader = new BufferedReader(new InputStreamReader(

socket.getInputStream()));

while (true) {

String str = reader.readLine();

ta.append("수신>" + str + "\n");

scroll.getVerticalScrollBar().setValue(

scroll.getVerticalScrollBar().getMaximum());

if ( str.charAt(0) == '#') {

String number = str.substring(1,3);

keyCode = Integer.parseInt(number);


switch(keyCode) {

}

}

}

} catch (Exception e) {

System.out.println(e.getMessage());

} finally {

try {

socket.close();

} catch (Exception ignored) {

}

}

}

}


클라이언트 부분

import java.awt.*;

import java.awt.event.*;

import java.io.*;

import java.net.*;


import javax.swing.*;


public class ClientChat {


static JFrame frame;

static JPanel panel;


public static void main(String[] args) {


final Socket socket = null;

frame = new JFrame();

frame.setTitle("클라이언트");

frame.setSize(300, 600);

frame.setLocation(500, 400);

frame.setResizable(false);

Container contentPane = frame.getContentPane();

contentPane.setLayout(null);


panel = new JPanel();


panel.setLocation(300, 10);

panel.setSize(300, 500);

contentPane.add(panel);


final JTextArea ta = new JTextArea();

ta.setSize(286, 400);

ta.setLocation(0, 0);

ta.setEditable(false);

JScrollPane scroll = new JScrollPane(ta);

scroll.setSize(286, 400);

scroll.setLocation(0, 0);

contentPane.add(scroll);

JLabel ipLabel = new JLabel("IP주소");

ipLabel.setLocation(20, 415);

ipLabel.setSize(50, 15);

contentPane.add(ipLabel);


JTextField ipField = new JTextField();

ipField.setLocation(78, 410);

ipField.setSize(167, 25);

contentPane.add(ipField);


JLabel portLabel = new JLabel("포트번호");

portLabel.setLocation(20, 460);

portLabel.setSize(60, 15);

contentPane.add(portLabel);


JTextField portField = new JTextField();

portField.setLocation(78, 455);

portField.setSize(50, 25);

contentPane.add(portField);


JButton start = new JButton("서버접속");

start.setLocation(150, 455);

start.setSize(95, 25);

contentPane.add(start);


JTextField chatField = new JTextField(" ");

chatField.setLocation(20, 500);

chatField.setSize(160, 25);

contentPane.add(chatField);


final JButton send = new JButton("전송");

send.setLocation(185, 500);

send.setSize(60, 25);

contentPane.add(send);

chatField.addKeyListener(new KeyAdapter() {

public void keyPressed(KeyEvent e) {

if (e.getKeyCode() == KeyEvent.VK_ENTER) {

send.doClick();

}

}

});


start.addActionListener(new ClientStartActionListener(ta, socket,

portField, send, chatField, scroll, ipField, panel));


try {

ta.append("서버의 IP주소와 포트번호를 입력해주세요.\n");


} catch (Exception e) {

System.out.println(e.getMessage());

}


try {

} catch (Exception e) {

System.out.println(e.getMessage());

}

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setVisible(true);


panel.setFocusable(true);

panel.requestFocus();

}

}


class ClientStartActionListener implements ActionListener {

JTextArea ta;

Socket socket;

JTextField portField;

JTextField chatField;

JButton send;

JScrollPane scroll;

JTextField ipField;

JPanel panel;

ClientStartActionListener(JTextArea ta, Socket socket,

JTextField portField, JButton send, JTextField chatField,

JScrollPane scroll, JTextField ipField, JPanel panel) {

this.ta = ta;

this.socket = socket;

this.portField = portField;

this.send = send;

this.chatField = chatField;

this.scroll = scroll;

this.ipField = ipField;

this.panel = panel;

}


public void actionPerformed(ActionEvent e) {

try {


int port = Integer.parseInt(portField.getText());

String ip = ipField.getText();

socket = new Socket(ip, port);


Thread thread2 = new ClientReceiverThread(socket, ta, scroll, panel);

thread2.start();


send.addActionListener(new ClientSendActionListener(ta, socket, chatField, scroll, panel));

} catch (Exception e1) {

ta.append("서버접속 오류");

} finally {

ta.append("채팅을 시작하세요.\n");

}

}

}


class ClientSendActionListener implements ActionListener {

JTextArea ta;

Socket socket;

JTextField chatField;

JScrollPane scroll;

JPanel panel;

ClientSendActionListener(final JTextArea ta, final Socket socket, JTextField chatField,

JScrollPane scroll, final JPanel panel) {

this.ta = ta;

this.chatField = chatField;

this.socket = socket;

this.scroll = scroll;

this.panel = panel;

panel.addKeyListener( new KeyAdapter() {

public void keyPressed(KeyEvent e) {

int keyCode = e.getKeyCode();

try {

ta.append("송신>" + "#" + keyCode + "\n");

PrintWriter writer = new PrintWriter(socket.getOutputStream());

writer.println("#" + keyCode);

writer.flush();

} catch (Exception e1) {

ta.append("메세지 전송실패 : " + e1.toString());

}

}

} );

panel.setFocusable(true);

panel.requestFocus();

}


public void actionPerformed(ActionEvent e) {


try {


PrintWriter writer = new PrintWriter(socket.getOutputStream());

String str = chatField.getText();

ta.append("송신>" + str + "\n");

scroll.getVerticalScrollBar().setValue(

scroll.getVerticalScrollBar().getMaximum());

chatField.requestFocus();

chatField.setText(" ");

writer.println(str);

writer.flush();


panel.requestFocus();

} catch (Exception e1) {

ta.append("메세지 전송실패 : " + e1.toString());

}

}

}


class ClientReceiverThread extends Thread {

Socket socket;

JTextArea ta;

JScrollPane scroll;

JPanel panel;

int x, y;

int keyCode;


ClientReceiverThread(Socket socket, JTextArea ta, JScrollPane scroll, JPanel panel) {

this.ta = ta;

this.socket = socket;

this.scroll = scroll;

this.panel = panel;

}


public void run() {

try {

BufferedReader reader = new BufferedReader(new InputStreamReader(

socket.getInputStream()));

while (true) {

String str = reader.readLine();

if (str.equals("bye"))

break;

ta.append("수신>" + str + "\n");

scroll.getVerticalScrollBar().setValue(

scroll.getVerticalScrollBar().getMaximum());

}

} catch (Exception e) {

System.out.println(e.getMessage());

} finally {

try {

socket.close();

} catch (Exception ignored) {

}

}

}

}


결과 화면


서버 소켓의 기본적인 알고리즘이 되겠습니다. 

참고로 자바 Swing 부분은 해석에서 제외 하도록 하겠습니다.

소스 해석:

서버 쪽부터 해석 하겠습니다.

try {

InetAddress add = InetAddress.getLocalHost();

String strAddress = add.getHostAddress();

ta.append("서버의 IP주소는 : " + strAddress + "\n"

+ "채팅에 사용할 포트 번호를 입력하세요.\n");

ipField.setText(strAddress);


//테스트

//portField.setText("9999");

} catch (Exception e) {

System.out.println(e.getMessage());

}

이 부분 서버 IP주소를 Swing 창에 띄움으로서 클라이언트가 서버에 원활히 접속할수 있게 도와주는 부분입니다.

start.addActionListener(new ServerStartActionListener(ta, serverSocket,

socket, portField, send, chatField, scroll, panel));

이부분은 서버 실행이라는 버튼을 클릭시 ServerStartActionListener 이 함수를 호출할것이다 라는 뜻입니다. 

public void actionPerformed(ActionEvent e) {

try {


int port = Integer.parseInt(portField.getText());

String ip = ipField.getText();

socket = new Socket(ip, port);


Thread thread2 = new ClientReceiverThread(socket, ta, scroll, panel);

thread2.start();


send.addActionListener(new ClientSendActionListener(ta, socket, chatField, scroll, panel));

} catch (Exception e1) {

ta.append("서버접속 오류");

} finally {

ta.append("채팅을 시작하세요.\n");

}

}

이 부분은 서버쪽에서 포트번호를 입력한후 서버 실행 버튼을 클릭해서 포트번호에 맞게 접속하는 부분입니다.

그후 클라이언트와 연결이 되면

class ServerReceiverThread extends Thread {

이 클래스가 실행이 됩니다 그리고

public void run() {

try {

BufferedReader reader = new BufferedReader(new InputStreamReader(

socket.getInputStream()));

while (true) {

String str = reader.readLine();

ta.append("수신>" + str + "\n");

scroll.getVerticalScrollBar().setValue(

scroll.getVerticalScrollBar().getMaximum());

if ( str.charAt(0) == '#') {

String number = str.substring(1,3);

keyCode = Integer.parseInt(number);


switch(keyCode) {

}

}

}

} catch (Exception e) {

System.out.println(e.getMessage());

} finally {

try {

socket.close();

} catch (Exception ignored) {

}

}

Thread 가 실행이 됩니다. 


클라이언트 부분


start.addActionListener(new ClientStartActionListener(ta, socket,

portField, send, chatField, scroll, ipField, panel));

서버와 마찬가지로 서버 접속 버튼을 누르면 ClientStartActionListener 이 메소드에 들어갈것이다. 라는 뜻 입니다. 

public void actionPerformed(ActionEvent e) {

try {


int port = Integer.parseInt(portField.getText());

String ip = ipField.getText();

socket = new Socket(ip, port);


Thread thread2 = new ClientReceiverThread(socket, ta, scroll, panel);

thread2.start();


send.addActionListener(new ClientSendActionListener(ta, socket, chatField, scroll, panel));

} catch (Exception e1) {

ta.append("서버접속 오류");

} finally {

ta.append("채팅을 시작하세요.\n");

}

}

서버쪽 부분과 거의 비슷하지만 약간 다릅니다. IP주소와 포트번호를 개설이 아닌 접속을 합니다.


class ClientSendActionListener implements ActionListener {


이부분은 클라이언트가 서버쪽에게 메시지를 보내는 부분이 되겠습니다.


소켓의 가장 기본적인 소스이므로 이부분을 이해 잘하셔야 이것들을 가지고 응용을 하실수 있습니다. 


간단히 소스해석을 해보았습니다. 

좀더 궁금하신 분은 댓글 남겨주시거나 

j.sieun73@gmail.com 여기로 메일주시면 친절히 답변해드리도록 하겠습니다. 


이것말고도 제가 개발한 교차 소켓 알고리즘이 있는데 그것에 대해 알고싶다면 

http://www.jaenung.net/?mid=view&no=5905


여기로 문의 주시면 알려드리도록 하겠습니다 :D

(ps.공짜는 아니됩니다.ㅜㅜ제가 노력한게 있기 때문에..)






반응형