본문 바로가기

IT Data/#Java

[JAVA] 소켓통신 Socket in TCP/IP protocol

 SocketClient.java

 

 SocketServer.java

서버와 클라이언트

프로그램 작동및 진행순서

1. 서버가 소켓을 생성하고 연결을 기다린다.

2. 클라이언트에서서버의 소켓에 연결한다.

3. 클라이언트에서 데이터를 전송한다.

4. 서버에서 데이터를 받아서 화면에 출력한다.

5. 서버와 클라이언트의 소켓연결을 끊는다.

 

프로그램은 2개를 작성한다. 당연히 서버와 클라이언트이다. 각각 SocketServer.java , SocketClient.java로한다 파일은 우측위 누르셈

 

명령 프롬프트를 2개 띄운후에 javac SocketServer.java  javac SocketClient.java를 각각 컴파일해준다.

 

하나의 콘솔에서 서버를 먼저 실행시킵니다. 파라미터로포트값을 주는데, 주지않으면 5777번이 잡히게 된다. 포트번호는 보통

3000번 이상의 값을 주는게 좋다.

java SocketServer 6000

그러면 포트번호와 클라이언트의 연결을 기다린다는 메시지가 나오게 됩니다. 6000번은 포트 번호임. 이값은 생략가능하며 생략하면

5777번을 당연히 기본으로 사용하게 됨 그러면 클라이언트를 실행시켜보자 파라미터값은 IP와 포트번호를 준다

 

java SocketClient 6000 211.35.136.174

 

포트번호는 앞에서 설정한것을 해주고 아이피주소는 자기것을 넣어주면 된다. IP를 입력안하면 127.0.0.1을 연결함 하지만 이번호는

로컬호스트 자기자신을 의미한다 만약 이번호로 되지 않거나 다른 컴퓨터에서 SocketServer를 실행시켰다면 IP를 반드시 써주어야 한다

암튼 서버와 포트 번호가 일치하고 IP를 적당히 넣어주었다면 클라이언트는 서버에 연결하여 열글자의 데이터를 보낸후 종료하게 된다.

서버도 소켓을 끊고 종료하게 된다.

 

소켓은 TCP프로토콜을 사용하여 통신을 하는데 쓰인다고 앞에서 얘기했다 따라서 소켓의 기본은 IP와 port겠다. 이것들이 각각 무엇을 의미하는지는 알지 못해도 상관없다. 중요한건 IP는 컴퓨터마다 가진 고유한 주소값이며 port는 통신을 위한 연결 라인과 같은 것이라고만 알고 있으면 된다. 암튼 소켓을 사용하여 어떤 프로그램과 연결을 하기 위해서는 그 프로그램이 있는 컴퓨터의 IP와 소켓을 만든 port번호를 알고 있어야 한다는 것만 기억하면 된다.

 

서버 소켓 준비하기

 

통신의 첫단계는 서버에서 소켓을 준비하는 것이다. 자바에서는 SererSocket이라는 클래스가 제공된다 다음과같이 생성할수있고

이 것은 java.net패키지에 들어있다

 

ServerSocket 소켓변수이름 = new ServerSocket(포트번호);

 

포트번호는 정수이고, 소켓변수이름을 아무것이나 변수로 쓸수 있는 것을 써주면 된다. 예를 들면 ServerSocket sbSocket = new SorverSocket(7979); 라고 선언 할수도 있따

 

다음은 이 서버소켓을 사용하여 클라이언트와 통신할 다른 소켓을 만들어주는 작업이다. 서버 쪽에는 반드시 두 개의 소켓이 필요하다

하나는 서벙요 소켓인데 이것으로 통신을 할 수가 없고 단지 클라이언트의 연결을 기다린다. 따라서 클라이언트와 연결이 되면 실제로 통신을 하기 위해서는 소켓을 하나 더 만들어서연결해주어야 한다는 것을 의미한다. 이소캣은 서버소켓변수이름.sccept(); 명령으로 만들어 줄 수 있다.

 

Socket soc = srvSocket.accept();

 

여기서 srvSocket은 아까 선언해준 서버소켓이름이 된다 .클래스의 이름이 Socket 라는것을 잘 새겨둬라 클라이언트도 이 소켓을 활용하여 연결된다

 

다음은 입력을 위한 스트림을 선언하는 부분이다. 물론 만들어 놓은 소켓에서 스트림을 얻어와야 하겠다 스트림은 java.io 패키지에 포함되어 있다.

 

InputStream is = soc.getInputStream();

 

이렇게 is는 스트림변수이고, soc는 앞에서 선언한 소켓의 이름이다. 이렇게 되면 입력스트림이 얻어진것이다. 그리고 이 스트림에 입력을 박기 위해서는 이 스트림을 읽을 수 있는 Reader를 정의해야만 읽을 수가 있다.

 

InputStreamReader isr = new InputStreamReader(is);

 

여기서 is는 앞에서 선언한 inputStream이고, isr은  InputStreamReader의 이름이다 이것으로 서버쪽에서 소켓을 준비하는것은 끝났다.

데이터를 읽기 위해서는 InputStreamReader에 있는 read()메소드를 사용하면 된다. 즉 int a= isr.read();와 같이하면 a에 한글자가 입력된다. int값으로 넘어오는데 문자로 바꾸기위해서는 형변환 즉 type casting를 해주면 된다  char a=(char) isr.read();와 같이 하면 된다.

들어오는 문자들을 하나의 문자열에 저장하기 위해서는 str = str+(char)isr.read();와 같이 문자열에 더해주면된다.

 

 

클라이언트 소캣

 

클라이언트 소캣은 서버소캣보다 간소하다 서버 소켓은 사용하지 않고 InputStream을 OutputStream으로 InputStreamReader를 OutputStreamWrter로 바꾸기만 하면 된다.

 

먼저 소켓을 만들어보자 앞에서 생성한서버의 IP와 prot번호를 알어야한다.

 

Socket soc = new Socket("127.0.0.1", 5777);

 

여기서 "127.0.0.1"은 IP주소이다. 앞에서 설명한대로 이것은 자기 자신을의미한다 만약 서버가 다른 컴퓨터에 있다면 그 컴퓨터의 IP를 적으면 된다 5777은 당근 포트번호이다 soc는 물론 소켓의 이름으로 변수로 쓸 수 있는 것을 적으면 됨

여기서 아까 눈여거 봐야 한다던 것을 기억해서 Socket과 동일한 클래스를 사용하고있다는것을 깨달아라

다음은 이 소켓에서 스트림을 생성하는 것이다. 서버로 데이터를 보내는 것이니까 당연히 InputStream이 아니라 OutputStream을 사용해야 한다 소켓은 출력담당이니깐

 

OutputStream os = soc.getOutputStream();

 

이 문장이 잘 이해가 안가면 앞의 서버 쪽의 InputStream을 만드는 부분 다시봐라  이제 앞에서와 마찬가지로 OutputStreamWriter를 만들어 줘야 한다.

 

OutputStreamWriter osw = new OutputStreamWritet(os);

 

이렇게 하면 writer까지 무사히 만들어 졌다. 이제 데이터를 전송하기 위해서는 write()메소드를 사용하면 된다.

 

 

 

 

 

스트림을 통한 데이터의 입출력

 

앞에서 InputStreamReader과 OutputStreamReader를 만들었다. 이제 클래스에 구현된 메소드(read, write)를 사용해 그냥 읽고 쓰면된다

 

자바 슾펙에서 InputStreamReader과 OutputStreamReader를 찾아 보자 앞으로 소켓공부할거면 즐겨찾기하고 매일봐야한다.

 

InputStreamReader를 보면 read 메소드가 두개로 정의되어 있다.

int read();

void read(char[] chr, int offset, int length);

전자는 설명에 한글자를 얻어 온다고 적혀있다 즉 a=isr.read();라고 쓰면 a에는 한글자만 들어간다 a는 정수형(int)이어야 한다.

이것을 문자로 바꾸기 위해서는 형변환 (type casting)을 해주어야 한다. 뒤으것은 몇 번째 글자(offset)부터 몇 글자(length)를 얻어와서 char의 배열에 넣는다는 의미이다.

 

public static void main(String args[]){

InputStreamReader isr = new InputStreamReader( new Server Socket(5777).accept().getInputStream());

string str ="";

for(int i=0 ;i < 10; i++){ str ="str+(char)isr.read();"}

System.out.println(str);

isr.close();

}

 

프로그램이 하나 완성되었다.

 

InputStreamReader를 만드는 모든 과정을 한줄 코딩하였다. 괄호의 가장 안쪽부터 차근차근 읽어보면 앞에서 설명한 순서로 만들어주었다는 것을 알 수 있다. 다음에는 str을 String으로 선언해주고 for문으로 열개의 문자를 읽어와서 str에 더해주고서 다시str을 출력하는 프로그램이다.

 

public static void main(String args[]){

OutputStreamWriter osw = new OutputStreamWriter(new Socket("127.0.0.1", 5777).getOutputStream());

String str = "12345567";

osw.write(str,0,10);

osw.flush();

osw.close();

}

 

아까와 마찬가지로 sec에서 찾아보면 OutputStreamWriter클래스에서는 write()가 여러개로 정의도어 있다. 한 글자씩 전송하는 것도 있고, 지금처럼 사용할 수도 있다. 지금은 str의 0번째 글자부터10개의 글자를 전송하라는 의미이다.

 

osw.flush();는 글자를 실제로 전송하라는 의미이다. 이것을써주지 않으면 메시지를 받는 쪽에서 실제로 읽을 수가 없다.

 

exception처리 (예외처리)

 

exception은  단어뜻 그대로 예외상황을의미한다. 만약 서버가 서버소켓을 만드는데 사용하고자하는 포트가 사용중이면 소켓을만들수없다. 그러면 소켓을 만들수없가없지만 이코드는 에러를 출력하지 않는다 컴파일할때 포트를 사용하는지 검사하는건 어리석은 짓이기때문이다. 컴파일할때는 사용안하다가 컴파일이후 사용하는 경우가 있을테니 ㅎㅎ

 

암튼 이런 예기치못한상황이 예외상황이고 exception이다 예외와 오류는 틀리니 착각하면안된다. 코드 오류와 실행중 예외상황은 당연히 틀리다.

 

자바의경우 자바머신이 종료될때 자원을자동으로 반환하지만 c의경우는 그렇지 않기때문에 예외상황에서 자원반환을안해주면 시스템자원을 잡아먹는 일이 발생한다.

 

암튼 중요한건 프로그래머는 프로그램의 신뢰성을 높여야하고 그러기위해서는 예외처리는 필수이다.

여기서는 크게 두가지 경우가 있을수있따 소켓 생성때 생성을 못하는 경우 다음으로 데이터를 입력받거나 출력할때 중간에 소캣 끊겼거나 하는 상황에 마지막으로 통신을 끝낼때 발생할수있다. 뭐 ... 시작 과정 끝 과정을 체크해주면 되는것이다.

 

예외상황처리는 당연히 try 와 catch문으로 처리가능하다 finally라는 예약어도 있지만 일단제외 이건 어째든 실행하라 머그렇게 사용가능

암튼 사용법은 c에서와 동일하다. 예외상항이 발생할만 메서드를 try{}로 감싸안고 예외상황은 catch에서 잡아서 처리하는 것이다.

간단히 코딩하면 다음과 같다

public static void main(String args[]){

try{

    // 감싸안을 내용

} catch(IOException e){

        System.out.println("에러임");

}

}

이렇게 하면된다 IOException은 여러가지 예외중 Input + Output의 예외 상황을 잡아내라는 의미이다.

스트림은 당연 입출력이 있느니 IO와 관계된것이다. 이러면 실행하다 예외 상황 발생시 catch다음에 있는 문장을 실행하고 넘어간다는 의미이다. 여기서는 오류메시지를 출력한것임

 

물론 이렇게 하면 잘돌아가지만 이상적인 예외처리는 아니다 발생할부분들을 각각 try-catch로 감싸줘야한다 이렇게해야 어디서 예외상황이 발생한것인지 단박에 알수있다.

 

import java.io.*;    // Stream, Reader, Writer를 사용하기 위해
import java.net.*;   // Socket을 사용하기 위해

public class SocketClient {

public static void main(String args[]) {

    OutputStreamWriter osw=null;

    try {
      osw=new OutputStreamWriter(new Socket("127.0.0.1",5777).getOutputStream());
    } catch (IOException e) {
      System.out.println("소켓을 만드는 데에 실패했습니다.");
      System.exit(-1);
    }

    String str="0123456789";

    try {
      osw.write(str,0,10);
      osw.flush();
    } catch (IOException e) {
      System.out.println("데이터 전송에 실패했습니다.");
    }  

    try {
      osw.close();
    } catch (IOException e) {
      System.out.println("소켓을 닫는데 실패했습니다.");
    }
  }
}

위와같이하면 완전한 프로그램이 작성된 것이다.

 

System.exit(-1); 이부분은 프로그램 종료 소켓생성아 안되었으니 당연히 종료해야하는게 맞다.