간단한 채팅 프로그램 만들기


- 다대다 클라이언트 간의 채팅

- 서버는 클라이언트에게 메시지를 뿌려주는 역할




[Server]

package tcp4;


import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;

import java.io.IOException;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.ArrayList;

import java.util.List;


public class Server {


ServerSocket serSock;

List<Socket> sockList;


public Server() throws IOException {

serSock = new ServerSocket(10081);

sockList = new ArrayList<>();

}


public void all_client_write(Socket sock, String msg) {


BufferedOutputStream bos = null;

try {

String info = "[" + sock.getRemoteSocketAddress() + "] : ";

for (int i = 0; i < sockList.size(); i++) {

bos = new BufferedOutputStream(sockList.get(i).getOutputStream());

System.out.println("send to " + sockList.get(i).toString() + ":" + msg);


String inFoMsg = info + msg;

bos.write(inFoMsg.getBytes("UTF-8"));

bos.flush();


}


} catch (IOException e) {

// TODO Auto-generated catch block

try {

bos.close();

sock.close();

removeSock(sock);

} catch (IOException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}


System.out.println("server write exception!");


e.printStackTrace();

}


}


public void stop(Thread thread) {

thread.interrupt();

}

public void removeSock(Socket sock) {

System.out.println("socket is close");

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

System.out.println("현재 접속: " + sockList.size());

for (int i = 0; i < sockList.size(); i++) {

if (sockList.get(i) == sock) {

try {

sockList.get(i).close();

sockList.remove(i);

break;

} catch (IOException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}


}

}

for (int i = 0; i < sockList.size(); i++) {

System.out.println(sockList.get(i).toString());

}

System.out.println("====================================\n\n");


}


public void read(Socket sock) {

Thread thread = null;


thread = new Thread(new Runnable() {


@Override

public void run() {

// TODO Auto-generated method stub

BufferedInputStream bis = null;

boolean flag = true;

try {

while (flag) {


bis = new BufferedInputStream(sock.getInputStream());

byte b[] = new byte[256];

int data = bis.read(b);


if (data != 0) {

String msg = new String(b);

System.out.println("[" + sock.getLocalAddress() + "] : " + msg);


all_client_write(sock, msg);

}


}

} catch (IOException e) {

// TODO Auto-generated catch block

try {

sock.close();

removeSock(sock);

} catch (IOException e2) {

// TODO Auto-generated catch block

e2.printStackTrace();

}

}


}


});


thread.start();


}


public void startServer() {

System.out.println("start Server!");

Thread thread = new Thread(new Runnable() {


@Override

public void run() {

// TODO Auto-generated method stub

try {

while (true) {

Socket sock = null;

sock = serSock.accept();

sockList.add(sock);


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

System.out.println("현재 접속: " + sockList.size());

for (int i = 0; i < sockList.size(); i++) {

System.out.println(sockList.get(i).toString());

}

System.out.println("====================================\n\n");


read(sock);

}


} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

System.out.println("socket accept excpetion");

return;

}

}

});

thread.start();


}


public static void main(String[] args) {

// TODO Auto-generated method stub

try {

new Server().startServer();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

System.out.println("server start exception");

}

}


}

[Client]

package tcp4;


import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;

import java.io.IOException;

import java.net.InetSocketAddress;

import java.net.Socket;

import java.net.UnknownHostException;

import java.util.Scanner;


public class Client {


Socket sock;

String host;

int port;


public void connect() throws UnknownHostException, IOException {

host = "localhost";

port = 10081;

sock = new Socket();

sock.connect(new InetSocketAddress(host, port));

}


public void read() {

Thread thread = new Thread(new Runnable() {


@Override

public void run() {

// TODO Auto-generated method stub

BufferedInputStream bis = null;

try {

while (true) {


bis = new BufferedInputStream(sock.getInputStream());

byte[] b = new byte[256];

int data = bis.read(b);


if (data != 0) {

System.out.println("\n");

System.out.println(new String(b));

}

}

} catch (IOException e) {

// TODO Auto-generated catch block


System.out.println("client read exception!");

e.printStackTrace();


try {

bis.close();

sock.close();

} catch (IOException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}

}

}

});

thread.start();

}


public void chatStart() {

Scanner scan = new Scanner(System.in);

BufferedOutputStream bos=null;

while (true) {

System.out.print("입력>>");

String msg = scan.nextLine();


try {

bos = new BufferedOutputStream(sock.getOutputStream());

bos.write(msg.getBytes("UTF-8"));

bos.flush();

} catch (IOException e) {

// TODO Auto-generated catch block


System.out.println("client write exception!");

try {

bos.close();

sock.close();

break;

} catch (IOException e1) {

// TODO Auto-generated catch block

e1.printStackTrace();

}

e.printStackTrace();

}

}

}


public static void main(String[] args) {

// TODO Auto-generated method stub

Client client = new Client();

try {

client.connect();

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

System.out.println("connect exception");

}

client.read();

client.chatStart();

}


}










====> 결과







소켓이 close() 됐을 때의 예외처리는 무엇보다 중요하다.

반드시 예외를 잡고 처리해야한다. 

시간이 없어서 빈약하게 처리했지만, 로직을 작성하는 것보다 메모리 누수 혹은 정확한 처리가 더 중요한 것 같다.




다음은 selector 와  asyncrhronousServerSocket에 대해 마지막으로 알아보자.




https://docs.oracle.com/javase/7/docs/api/java/nio/channels/AsynchronousServerSocketChannel.html















로그란?


log. 컴퓨터 혹은 시스템 상태를 관찰할 수 있도록 에플리케이션이 제공하는 정보. 


- 로그를 통해 프로그램의 특정 상황이나 발생되는 이벤트의 정보를 얻을 수 있다.

- 로그를 통해 개발자는 시스템의 현재 상황에 대한 정보를 얻을 수 있고 향후 시스템을 개선하기 위해 노력하여야 한다.


 즉 프로그램과 개발자가 소통할 수 있는 창구라고 생각한다.







로그 레벨


 - log4j 라이브러리는 자주 활용되며 OFF, FATAL, ERROR, WARN, INFO, DEBUG, TRACE 로 나눌 수 있다.

OFF       :  로그 사용 해제
FATAL    :  시스템 혹은 애플리케이션의 종료를 유발 할 수 있는 아주 심각한 오류가 발생한 상태 
ERROR   :  특정 이벤트나 상황에서 오류가 발생한 상태
WARN    :  앞으로 오류를 발생할 만한 요소에 대한 경고를 나타냄
INFO      :  시스템 시작 또는 종료 등 상태 변경에 대한 정보를 제공하는 단계

DEBUG   :   디버그. 시스템 흐름에 대한 정보를 제공할 때 사용

TRACE    :  DEBUG 단계보다 더 자세한 정보를 제공할 때 사용

'programming > Exception' 카테고리의 다른 글

# 예외처리 (1) - 예외처리의 중요성  (0) 2018.02.24


예외처리에 대한 생각



프로그램에서 예외는 발생할 수 밖에 없고, 특히 예외로 인해 서버 프로그램이 작동을 멈춘다면 문제가 발생할 요소가 있다.  적어도 발생한 예외가 어떤 예외이며, 이것이 무시해도 될 수준인지 아니면 반드시 처리를 해야하는 것인지 그것도 아니라면 시스템 자체를 종료시켜야하는지에 대해 생각해봐야한다.

시스템에서 훌륭한 기능보다 중요한 것이 손쉬운 보완이라고 생각한다. 예외는 프로그램의 보완에 있어 첫걸음이 될 것이다.

첫 단추를 잘 꿰 메야 한다는 말이 있듯이, 프로그램을 설계하고 구축하는 첫 단계에서 수많은 예외상황에 생각하고자 스스로 노력하자.


 
1. 에러와 예외는 어떻게 다른가? 

error : 시스템 단계에서 발생. 시스템의 비정상적인 상황이므로 예외처리가 아닌 시스템 환경을 개선해야 한다.
Exception : 프로그램 로직에서 발생. 프로그래머가 작성한 로직에서 예외를 예상하여 구분하고 처리해야 한다.


2. 예외의 구분 

                                    예측가능한 예외 vs 예측 불가능한 예외

* 예측 가능한 예외 
 - 프로그램에서 당연히 발생 할 수 밖에 없는 상황.    ex) 로그인 실패, 데이터 조회 실패 .. 등

* 예측 불가능한 예외
 - 에러와 같은 수준의 레벨.     ex) 버그, 시스템의 메모리 문제 .. 등
 - 시스템 환경에서 개선해야 한다. 

 

  
                                     Runtime Exception vs 그 밖의 예외

* Runtime Exception (unchecked Exception)

 - 실행 단계에서 확인합니다.
 - 그렇기 때문에 처리를 강제하지 않습니다.
 - ex) nullpointerException , IndexOutOfBoundsException, .. 등
 - 하지만 이 또한 버그이기 때문에 반드시 인지하고 처리해야 할 의무가 있습니다.



* 그 밖의 예외 (checked Exception)
  - 컴파일 단계에서 확인합니다.
  - 예외에 대한 처리를 강제합니다.
  - ex) IOException .. 
  



3. 예외를 잡은 이 후의 행동



(1) 예외를 잡았다면 반드시 처리해야 한다.

  - 하지만 그 예외가 에러라면? 저라면 시스템 환경을 개선하고 처리하지 않겠습니다.

  - 시스템을 종료하거나 혹은 예외에 대한 상황을 처리 할 수 있다.



(2) 로그를 반드시 남겨야 한다.

 - 예외가 어떤 예외인지 구체적으로 명료하게 로그로 남겨야 한다.  

 - 예외가 발생한 원인, 시스템의 정보, 이외 반드시 판단 가능한 메세지.

 - 로그는 프로그래머가 처리 상황을 판단 할 수 있는 구체적인 증거입니다. 특히 서버 개발자에겐..

  


(3) 새로운 예외를 던질 수 있다.

 - 세세한 예외를 더 추상화하여 던진 경우이다.  

 - 예외 원인에 대한 정보가 부족할 경우 새로운 예외를 던짐으로써, 구체적인 정보를 얻을 수 있다.

 


(4) 예외 무시

 - 큰 이상이 없다고 판단되면 예외를 무시할 수 있다.

 - 좋은 방법은 아니다.





4. 예외처리 패턴



(1) 예외복구

try{ .... } catch(SQLException e) {

logger.error("Insert Query Failed . Method Name '' , User id is blar...");

e.printstackTrace();

}finnally{

..

예외에 대한 처리작업을 수행해야 할 것이다.

doAnything();

}

 => 예외복구에 대해 반드시 인지해야 되는 점은 프로그램이 정상적으로 작동하게끔 수행해야 한다는 것이다. 비록 예외가 발생되었어도 프로그램 로직에 의해 시스템이 종료 혹은 계속해서 수행할 수 있어야 할 것이다.




(2) 예외회피

public void process() throws Exception {

......

}



  => 예외를 메소드에 정의함으로써, 예외에 대한 처리를 수행하지 않고 있다. 또한 추상클래스인 Exception을 예외로 던졌기 때문에, 구체적인 예외에 대한 처리가 부족하다. 즉, 좀 더 세부적인 예외를 잡아먹고 있다. 예외회피를 불가피하게 해야할 경우를 제외한다면, 신중하게 선택해야 할 것이다.

 



(3) 예외전환

try{ .... } catch(Exception e) {

... 추상 Exception에 대한 정보 또한 로그로 남기면 좋을 것이다..

throws New SQLException("Exception is SQLException. Insert Query Failed . Method Name '' , User id is blar...");

}

 => 예외를 중첩하여 사용하여 어떤 예외에 대한 처리인지 분명하게 처리하였다. 만일 복구가 불가능 한 예외에 대한 처리라면 RuntimeException으로 전환하여 다른 예외에 대해 일일히 처리하지 않게끔 할 수 있다. 










예외에 대해 정리하면서 느낀 생각은, 개발자라면 항상 예외에 대해 염두하고 있어야 한다는 점입니다.  (특히 나같은 초급개발자라면..)


예외를 잡았다면 반드시 처리해야 할 것이며, 예외 상황에 대한 충분한 로그를 남겨야 할 것입니다. 충분히 예외상황에 대해 인지하고 처리를 했다면 유지보수 측면에서 혹은 협업단계에서 효율적으로 시스템을 개선할 수 있을 것입니다.


 

=> 예외의 목적은 시스템 개선이다!!










참고사이트 : https://www.slideshare.net/dhrim/ss-2804901

'programming > Exception' 카테고리의 다른 글

#예외처리 (2) - log level  (0) 2018.02.24

+ Recent posts