[책펌] Protocol 설계를 이용한 로그인 인증예제 (소켓, 프로토콜, 바이트스트림)

By | 10월 22, 2008

1. 개요

- 서버, 클라이언트가 공유하는 '요청의 종류와 내용에 관한 프로토콜'을 만들어 로그인예제에 적용한다.

2. 로그인 예제 프로그램의 흐름

(1) 서버가 클라이언트에 로그인 요청을 한다.
(2) 클라이언트는 아이디와 패스워드를 서버에게 전송한다.
(3) 아이디와 패스워드가 정확히 맞았다는 메시지를 전송한다.
(3) 패스워드가 틀린 경우의 메시지를 전송한다.
(3) 아이디가 틀린 경우의 메시지를 전송한다.
(4) 클라이언트는 서버에게 종료 메시지를 전송한다. 이 때 서버도 프로그램을 종료한다.

3. 클래스 설명

- Protocol.java : 이 클래스는 클라이언트와 서버에서 사용하는 클래스다. 주요 목적은 packet 바이트 배열을 생성하여 프로토콜 타입과 실제 데이터(ID와 PWD)를 저장하여 packet 바이트 배열을 클라이언트와 서버가 전송하게 된다.

LoginServer.java : 서버를 의미하며, 일반적인 서버는 클라이언트의 요청이 있는 경우 통신을 시작하는데, 이 클래스는 클라이언트가 프로그램을 시작하면 서버에서 로그인 요청을 하게 된다. 클라이언트에서 로그인 요청이 왔을 때 ID와 PWD를 체크한 후에 결과를 전송하고 클라이언트가 종료되면 프로그램도 같이 종료된다.

- LoginClient.java : 클라이언트를 의미하며, 서버에서 로그인 요청이 있는 경우 ID와 PWD를 입력하여 로그인 인증을 받게 된다. 로그인 인증의 결과와 상관없이 프로그램은 종료된다.

4. 실행
- java LoginServer
- java LoginClient 호스트주소 포트번호

Protocol.java

package socket.protocol;

import java.io.Serializable;

public class Protocol implements Serializable{

 //프로토콜 타입에 관한 변수
 public static final int PT_UNDEFINED = -1;   //프로토콜이 지정되어 있지 않을 경우에
 public static final int PT_EXIT = 0;
 public static final int PT_REQ_LOGIN = 1;   //로그인요청
 public static final int PT_RES_LOGIN = 2;   //인증요청
 public static final int PT_LOGIN_RESULT = 3;  //인증결과
 public static final int LEN_LOGIN_ID = 20;   //ID길이
 public static final int LEN_LOGIN_PASSWORD = 20; //PW길이
 public static final int LEN_LOGIN_RESULT = 2;  //로그인인증값 길이
 public static final int LEN_PROTOCOL_TYPE = 1;  //프로토콜타입 길이
 public static final int LEN_MAX = 1000;    //최대 데이타 길이
 
 protected int protocolType;
 
 private byte[] packet;   //프로토콜과 데이터의 저장공간이 되는 바이트배열

//생성자
 public Protocol(){
  this(PT_UNDEFINED);
 }
 
//생성자
 public Protocol(int protocolType){

  this.protocolType = protocolType;
  
//어떤 상수를 생성자에 넣어 Protocol 클래스를 생성하느냐에 따라서 바이트배열 packet 의 length 가 결정된다.
  getPacket(protocolType);
 }
 
 
 public byte[] getPacket(int protocolType){
  
  if(packet == null){
   
   switch(protocolType){
   
    case PT_REQ_LOGIN : packet = new byte[LEN_PROTOCOL_TYPE]; break;
    case PT_RES_LOGIN : packet = new byte[LEN_PROTOCOL_TYPE + LEN_LOGIN_ID + LEN_LOGIN_PASSWORD]; break;
    case PT_UNDEFINED : packet = new byte[LEN_MAX]; break;
    case PT_LOGIN_RESULT : packet = new byte[LEN_PROTOCOL_TYPE + LEN_LOGIN_RESULT]; break;
    case PT_EXIT : packet = new byte[LEN_PROTOCOL_TYPE]; break;
   }
  }

  packet[0] = (byte)protocolType;   //packet 바이트배열의 첫번째 방에 프로토콜타입 상수를 셋팅해 놓는다.
  return packet;
 }

 //로그인후 성공/실패의 결과값을 프로토콜로 부터 추출하여 문자열로 리턴
 public String getLoginResult(){
  //String의 다음 생성자를 사용 : String(byte[] bytes, int offset, int length)
  return new String(packet, LEN_PROTOCOL_TYPE, LEN_LOGIN_RESULT).trim();
 }
 
 
 //String ok를 byte[] 로 만들어서 packet의 프로토콜 타입 바로 뒤에 추가한다.
 public void setLoginResult(String ok){
  //arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
  System.arraycopy(ok.trim().getBytes(), 0, packet, LEN_PROTOCOL_TYPE, ok.trim().getBytes().length);
 }
 
 
 public void setProtocolType(int protocolType){
  this.protocolType = protocolType;
 }

 
 public int getProtocolType(){
  return protocolType;
 }
 
 
 public byte[] getPacket(){
  return packet;
 }
 
 
 //Default 생성자로 생성한 후 Protocol 클래스의 packet 데이타를 바꾸기 위한 메서드
 public void setPacket(int pt, byte[] buf){
  packet = null;
  packet = getPacket(pt);
  protocolType = pt;
  System.arraycopy(buf, 0, packet, 0, packet.length);
 }
 
 
 public String getId(){
  //String(byte[] bytes, int offset, int length)
  return new String(packet, LEN_PROTOCOL_TYPE, LEN_LOGIN_ID).trim();
 }
 
 
 //byte[] packet 에 String ID를 byte[]로 만들어 프로토콜 타입 바로 뒷부분에 추가한다.
 public void setId(String id){
  System.arraycopy(id.trim().getBytes(), 0, packet, LEN_PROTOCOL_TYPE, id.trim().getBytes().length);
 }
 
 
 public String getPassword(){
  //구성으로 보아 패스워드는 byte[] 에서 로그인 아이디 바로 뒷부분에 들어가는 듯 하다.
  return new String(packet, LEN_PROTOCOL_TYPE + LEN_LOGIN_ID, LEN_LOGIN_PASSWORD).trim();
 }
 
 
 public void setPassword(String password){
  System.arraycopy(password.trim().getBytes(), 0, packet, LEN_PROTOCOL_TYPE+LEN_LOGIN_ID, password.trim().getBytes().length);
  packet[LEN_PROTOCOL_TYPE + LEN_LOGIN_ID + password.trim().getBytes().length] = '\0';
 }
  
}

LoginServer.java

package socket.protocol;

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

public class LoginServer {

 public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException{
  
  ServerSocket sSocket = new ServerSocket(3000);
  System.out.println("클라이언트 접속 대기중 ...");
  Socket socket = sSocket.accept();
  System.out.println("클라이언트 접속");
  
//어차피 바이트배열로 전송할 것이기 때문에 필터스트림 없이 Input/OutputStream만 사용해도 된다.
  OutputStream os = socket.getOutputStream();
  InputStream is = socket.getInputStream();

  //로그인정보 요청용 프로토콜 객체 생성
  Protocol protocol = new Protocol(Protocol.PT_REQ_LOGIN);
  
  //로그인정보 요청 패킷을 전송 
  os.write(protocol.getPacket());
  

  while(true){
  
   //새 Protocol 객체 생성 (기본 생성자)
   protocol = new Protocol();
   
   //기본 생성자로 생성할 때에는 바이트배열의 길이가 1000으로 지정됨
   byte[] buf = protocol.getPacket();
   
   //socket으로부터 읽어서(클라이언트의 입력) buf 에 저장한다. (블로킹메서드)
   is.read(buf);
   
   //패킷 타입을 얻고 Protocol 객체의 packet 멤버변수에 buf를 복사한다.
   int packetType = buf[0];
   protocol.setPacket(packetType, buf);
   
   if(packetType == Protocol.PT_EXIT){
    protocol = new Protocol(Protocol.PT_EXIT);
    os.write(protocol.getPacket());
    System.out.println("서버종료");
    break;
   }
  
   
   switch(packetType){
   
   //클라이언트가 로그인 정보 응답 패킷인 경우 (클라이언트의 로그인 정보 전송일 경우)
   case Protocol.PT_RES_LOGIN :
    
    System.out.println("클라이언트가 로그인 정보를 보냈습니다.");
    String id = protocol.getId();
    String password = protocol.getPassword();
    System.out.println(id+"@@"+password+"@@");
    
    if(id.equals("bruce")){
     
     if(password.equals("1111")){
      
      //로그인 성공
      protocol = new Protocol(Protocol.PT_LOGIN_RESULT);
      protocol.setLoginResult("1");
      System.out.println("로그인 성공");
     
     }else{
      
      //암호 틀림
      protocol = new Protocol(Protocol.PT_LOGIN_RESULT);
      protocol.setLoginResult("2");
      System.out.println("암호 틀림");
     }
     
    }else{
     
     //아이디 존재 안함
     protocol = new Protocol(Protocol.PT_LOGIN_RESULT);
     protocol.setLoginResult("3");
     System.out.println("아이디 존재 안함");
    }
   
    System.out.println("로그인 처리 결과 전송");
    os.write(protocol.getPacket()); //socket의 OutputStream 에 기록한다.
    break;
   
   }//end switch
  
  }//end while
  
  is.close();
  os.close();
  socket.close();
 
 }
 
}

LoginClient.java

package socket.protocol;

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

public class LoginClient {

 public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException{
  
  if(args.length <2)System.out.println("사용법 : java LoginClient 호스트주소 포트번호");

  Socket socket = new Socket(args[0], Integer.parseInt(args[1]));
  
  OutputStream os = socket.getOutputStream();
  InputStream is = socket.getInputStream();
  Protocol protocol = new Protocol();
  byte[] buf = protocol.getPacket();
  
  //while문을 사용하는 이유 : InputStream 으로부터 계속 읽어들이기 위해서...?
  while(true){
   
//소켓의 InputStream 으로부터 읽어들여서 바이트배열 buf에 저장한다. (서버로부터 온 값)
   is.read(buf);

   int packetType = buf[0];
   protocol.setPacket(packetType, buf);
   
   if(packetType == Protocol.PT_EXIT){
    System.out.println("클라이언트 종료");
    break;
   }
   
   switch(packetType){
   
   case Protocol.PT_REQ_LOGIN :
    System.out.println("서버가 로그인정보 요청");
    BufferedReader userIn = new BufferedReader(new InputStreamReader(System.in));
    System.out.print("아이디 : ");
    String id = userIn.readLine();
    System.out.print("암호 : ");
    String pwd = userIn.readLine();

    //서버로 패킷 전송 (로그인 정보 전송)
    protocol = new Protocol(Protocol.PT_RES_LOGIN);
    protocol.setId(id);
    protocol.setPassword(pwd);
    System.out.println("로그인 정보 전송");
    os.write(protocol.getPacket());
    break;
    
   case Protocol.PT_LOGIN_RESULT :
    System.out.println("서버가 로그인 결과 전송");
    String result = protocol.getLoginResult();
    if(result.equals("1")){
     System.out.println("로그인 성공");
    }else if(result.equals("2")){
     System.out.println("암호 틀림");
    }else if(result.equals("3")){
     System.out.println("존재하지 않는 아이디");
    }
    protocol = new Protocol(Protocol.PT_EXIT);
    System.out.println("종료패킷 전송");
    break;
   }
   
  }//end while

  os.close();
  is.close();
  socket.close();
 }
}

- 출처 : [한빛미디어] 자바 5.0 프로그래밍 -

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments