공적's life

4-2 NetWork 본문

Programing/Java programing

4-2 NetWork

melpis 2010. 8. 3. 20:45

이제 우리는 통신을 배워 보겠습니다. 자세하게 하는 것이 아니라

 

단순하게 배울 것입니다. 우리에 목표는 network에 대해서 전반적으로

 

공부하는 것이 아니라 우리가 사용하는 것만 배워볼 것입니다.

 

일단 기본적으로 통신을 하기 위해서는 Socket클래스를 사용합니다 .

 

간단한 예제를 볼까요?

 

public class SocketNetwork {

 

    public static void main(String[] args) throws UnknownHostException, IOException {

        //소켓 열기

        Socket socket = new Socket("www.naver.com",80);

        //쓰기 준비

        OutputStream os=socket.getOutputStream();

        //읽기 준비

        InputStream is=socket.getInputStream();

        //쓰기

        os.write("GET \n".getBytes());

        //버퍼 준비

        byte [] buffer = new byte[4096];

        //읽기

        while((is.read(buffer))>0){

            System.out.println(new String(buffer,"UTF-8"));

        }

        //닫기

        is.close();

        os.close();

        socket.close();

    }

    

}

 

I.O를 왜 배우는 지 알겠지요? 여기서 활용하기 위해서 입니다. 전체적인 절차는 I.O와 비슷합니다.

 

이 클래스 역할은 서버에 원하는 것을 받거나 혹은 쓰거나 입니다.

 

그렇다면 서버가 필요하겠죠? 예제를 살짝 볼까요?

 

public class ServerSocketNetwork {

 

    public static void main(String[] args) throws IOException {

        ServerSocket server = new ServerSocket(9999);

 

        Socket client=server.accept();

      

        OutputStream os=client.getOutputStream();

        InputStream is = client.getInputStream();

        byte [] buffer = new byte[4096];

        String http=null;

        

        is.read(buffer);

        http= new String(buffer);

        

        System.out.println(http);

        os.close();

        is.close();

        client.close();

          

        

        

    }

    

}

 

위에 클래스와 그다지 차이점은 보이지가 않네요. 실제로 브라우저에 http://localhost:9999/를 쳐보세요.

 

그럼 무언가 출력이 될 것입니다. 그 출력된 내용이 바로 http입니다.

 

일단 ServerSocket부터 설명하고 나서 그 다음에 보도록하죠.

 

ServerSocket에서 생성자는 port를 설정 할 수 있습니다. Port는 무엇이냐?

 

바로 통신을 할 때 나는 어떤 프로토콜을 사용하겠다 라는 기준입니다.

 

0~1024까지는 이미 알려진 port입니다. 이것은 인터넷 검색해보시면 많이 나오니까 패스하도록 하죠.

 

ServerSocket을 생성하고 나서 accept()란 메소드를 호출하면, 클라이언트 즉 접속하지 않을 때까지

 

accept()에서 대기하고 있습니다. 접속하면 Socket을 리턴합니다.

 

그럼 Socket클래스에서 사용한 것과 똑같이 사용하시면 됩니다. 원하는 내용을 쓰거나

 

읽거나 할 수 있습니다. 그럼 우리가 제일 자주 사용하게 될 http가 무엇인지 알아 볼까요?

 

일단 기본적으로 port번호는 80번입니다.

 

그리고 나서 몇 가지 규칙이 있습니다. 빈칸과 ":"과 라인으로 구분이 됩니다. 그럼 실제

 

Http를 한번 볼까요? 위에 예제에 ServerSocket에 생성자에 80으로 하고 나서

 

http://localhost/를 입력해줍니다.

 

GET / HTTP/1.1

Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*

Accept-Language: ko-KR

User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; Trident/4.0; GTB6.5; Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1) ; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; Tablet PC 2.0; .NET4.0C; .NET4.0E; InfoPath.3)

Accept-Encoding: gzip, deflate

Host: localhost

Connection: Keep-Alive

그럼 이런 메시지가 나옵니다.

 

http://www.w3.org/Protocols/rfc2616/rfc2616.html 규칙을 보고 싶으시다면 여기로 가셔서 확인하시면 됩니다.

 

브라우저에서는 페이지를 표시 할 수 없습니다. 란 메시지가 뜹니다.

 

그럼 이제 무언가 표지를 해볼까요?

 

String message="안녕하세요";

        

    os.write(message.getBytes());

 

이제 메시지가 출력되지요?

 

이런 것을 응용해서 WebServer를 만들 수 있습니다. 말이 나온 김에 해보도록 하죠.

 

그럼 WebServer는 무엇을 하느냐 서버에 저장된 이미지나 html을 브라우저에 전송해주는 역할을 합니다.

 

http에 특징은 서버가 메시지를 받을 때는 위에 이상한(?)문자로 받고, 줄 때는 html으로 줍니다.

 

그걸 브라우저가 받아서 파싱을 한 후에 사용자에게 보여줍니다. 시간이 남으면 한번 초 간단 브라우저도 만들어 보도록하죠.

 

자아 그럼 일단 해볼까요?

 

일단은 우리는 메시지 중에 첫 줄에만 집중 해보도록 하죠.

 

첫 번째 줄은 사용자가 요청하는 내용을 서버로 보내주기 때문입니다.

 

GET / HTTP/1.1

 

여기서 첫 번째 GET는 어떠한 방식으로 보냈느냐?

 

두 번째는 / 경로입니다.

 

세 번째는 프로토콜 이름입니다.

 

그러니 우리는 두 번째 경로만 뽑아보도록 하죠.

//접속을 기다림

    Socket client=server.accept();

    //쓰기 준비

    OutputStream os=client.getOutputStream();

    //읽기 준비

    InputStream is = client.getInputStream();

    //버퍼

    byte [] buffer = new byte[4096];

    //읽기

    is.read(buffer);

    //문자로 변환

    String http= new String(buffer);

    

    System.out.println(http);

    //라인 별로 분리

    String[] headerLineSeparation=http.split("\r\n");

    //공백 별로으로 분리

    String[] headerSpaceSeparation=headerLineSeparation[0].split(" ");

    System.out.println(headerSpaceSeparation[1]);

 

    String browserMessage =null;

    // /EXIT면 서버 종료

    if(headerSpaceSeparation[1].equals("/EXIT")){

        browserMessage ="서버종료됨";

        os.write(browserMessage.getBytes());

        os.flush();

        break;            

    }

    // 일반

    browserMessage="안녕하세요";

    

    os.write(browserMessage.getBytes());

    os.flush();

그다지 어렵지는 않지요? Html파일 읽어서 브라우저에게 보내보죠 ^^;

    File htmlFile = new File("c:/www/",userPath);

    

    if(!htmlFile.exists()){

        browserMessage="HTTP/1.1 404 Not Found\r\n";

        os.write(browserMessage.getBytes());

        os.flush();

    }else{

        FileInputStream fis = new FileInputStream(htmlFile);

        byte[] fileBuff = new byte[4096];

        fis.read(fileBuff);

        os.write(fileBuff);

        os.flush();

    }

경로를 받아서 읽는 겁니다. 만약에 파일이 없다면 404 Not Found를 일으키는 겁니다. 많이들 보셨죠?

 

브라우저에서 보낼 때만 위에 이상한(?)문자열로 보내는 것이 아니라 브라우저가 받을 때도 저렇게 받습니다.

 

이제 c:/www/ 폴더에 간단한 html파일을 작성 하신 후에 실행 시켜보세요 예를 들어서 www폴더 밑에

 

a.html이면 http://localhost/a.html 입니다.

 

지금은 파일들이 너무도 작기 때문에 실행이 잘됩니다. 그럼 이제 이미지 파일을 불러 올까요?

 

<html>

<head></head>

<body>

안녕하세용!!!!

<img src="a.jpg">

</body>

</html>

 

제가 작성한 html입니다. 절대 경로와 상대경로가 있는데 이건 나중에 설명하도록 하고 같은 폴더에

 

표시해 주시면 됩니다. 그래서 실행을 시켰더니 그림이 나오질 않네요.

 

왜 그럴까요? 정답은 한번만 읽었기 때문입니다. 말로 하니까 어려워 보이는데 이제 코드로 한번 다시 보도록 하죠. 그전에 브라우저에서

 

요청 사항을 볼까요?

GET /a.html HTTP/1.1

Accept: */*

Accept-Language: ko-KR

User-Agent: Mozilla/4.0

Accept-Encoding: gzip, deflate

Host: localhost

Connection: Keep-Alive

 

/a.html

GET /a.jpg HTTP/1.1

Accept: */*

Referer: http://localhost/a.html

Accept-Language: ko-KR

User-Agent: Mozilla/4.0

Accept-Encoding: gzip, deflate

Host: localhost

Connection: Keep-Alive

 

/a.jpg

아 저는 그냥 html만 요청했는데 내부적으로 img태그를 만나니까 다시 한번 요청하네요?

 

하지만 우리는 한번만 데이터를 썼습니다. 여러 번 써볼까요?

 

FileInputStream fis = new FileInputStream(htmlFile);

    byte[] fileBuff = new byte[4096];

    while((fis.read(fileBuff))>0){

        os.write(fileBuff);

    }

    os.flush();

간단하게 파일에서 읽을 것이 있으면 계속 읽는 겁니다. 그리고 쓰고 밀어 넣는 거죠.

 

전에 설명했지만 read에 리턴값은 읽은 바이트 수입니다. 그래서 하나도 읽지 않았다면 0이 되고 while문을

 

빠져 나오게 되는 것이지요.

 

이제 브라우저에서 실행 시켜보세요 그림이 나올 겁니다.

 

여기까지 너무 좋은 거 같은데 보기는 싫네요. 다시 한번 메소드와 클래스로 나누어 볼까요?

 

String userPath = request(http);

            

    // /EXIT면 서버 종료

    if(userPath.equals("/EXIT")){

        System.out.println("서버종료됨");

        break;            

    }else{

        // response로 넘김

        response(userPath,os);

    }

private static void response(String userPath, OutputStream os) throws IOException {

        File htmlFile = new File("c:/www/",userPath);

        

        if(!htmlFile.exists()){

            String browserMessage="HTTP/1.1 404 Not Found\r\n";

            os.write(browserMessage.getBytes());

            os.flush();

        }else{

            FileInputStream fis = new FileInputStream(htmlFile);

            byte[] fileBuff = new byte[4096];

            while((fis.read(fileBuff))>0){

                os.write(fileBuff);

            }

            os.flush();

        }

        

    }

 

    private static String request(String http) {

        String returnURL=null;

        //라인 별로 분리

        String[] headerLineSeparation=http.split("\r\n");

        //공백 별로으로 분리

        String[] headerSpaceSeparation=headerLineSeparation[0].split(" ");

        System.out.println(headerSpaceSeparation[1]);

        returnURL=headerSpaceSeparation[1];

        return returnURL;

        

    }

    

여기까지 분리 했습니다. 하지만 아직 미약한 거 같군요. 저는 response에 그냥 출력만 하고 싶어요 파일에 관한 것은 다른 사람이 하면

 

안될까요? 그럼 해보도록 하죠.

private static String request(String http) {

        String returnURL=null;

        //라인 별로 분리

        String[] headerLineSeparation=http.split("\r\n");

        //공백 별로으로 분리

        String[] headerSpaceSeparation=headerLineSeparation[0].split(" ");

        System.out.println(headerSpaceSeparation[1]);

        returnURL=headerSpaceSeparation[1];

        return returnURL;

        

    }

    

    private static void response(OutputStream os, String browserMessage) throws IOException {

        response(os, browserMessage.getBytes());

    }

    

    private static void response(OutputStream os, byte[] buffer) throws IOException {

        os.write(buffer);

        os.flush();

        os.close();

    }

 

    private static void resource(String userPath, OutputStream os) throws IOException {

        File htmlFile = new File("c:/www/",userPath);

        if(!htmlFile.exists()){

            String browserMessage="HTTP/1.1 404 Not Found\r\n";

            response(os,browserMessage);

        }else{

            FileInputStream fis = new FileInputStream(htmlFile);

            byte[] fileBuff = new byte[4096];

            while((fis.read(fileBuff))>0){

                response(os,fileBuff);

            }

        }

        

    }

 

오버로딩이라는 것을 사용해 보았습니다. 실상 response가 하는 역할은 똑같은데 받는 데이터가 다르기 때문에 새로운 이름을 짓지

 

않고, 파라미터만 바꾸어서 사용하였습니다. 간단히 오버로딩을 정리하자면 내가 하고자 하는 기능은 똑같은데 파라미터가

 

다를 때 사용해주시면 됩니다. 왜 이렇게 사용하는가? 예를 들어서 키보드가 있는데 103키 키보드와 108키 키보드가 있습니다.

 

키보드가 하는 역할은 문자를 입력 받는 것입니다. 이 두 가지 키보드는 같은 역할은 하지만 키에 개수가 다릅니다. 하지만

 

우리는 그냥 둘 다 키보드라고 부르지 않습니까? 이거와 유사한 원리 가 되겠습니다. 헷갈리라고 만든 것이 아니라

 

같은 기능을 하니까 상황에 맞게 써라 입니다.

 

자아 이제 클래스로 분리 해볼까요?

Request request = new Request(is);

            

    // /EXIT면 서버 종료

    if(request.getRequestURL().equals("/EXIT")){

        System.out.println("서버종료됨");

        break;            

    }

            

    Response response = new Response(os);

    Resource resource = new Resource();

    resource.service(request, response);

      

    //자원해제

    resource = null;

    response = null;

    request = null;

            

    //닫기

    os.close();

    is.close();

    client.close();

 

 

WebServer클래스가 상당히 단순해 졌죠? 나머지들은 메소드와 별반 차이가 없기 때문에 설명 드리지 않겠습니다.

 

여기서 주목해야 할 것은 Stream들을 넘겨서 스스로 처리 하게 했습니다. 여기에서 가공하는 것보다

 

더 전문적으로 가공 할 수 있다고 판단하였기에 그랬습니다.

 

그리고 자바는 가비지 컬렉터가 돌기 때문에 스스로 자원을 해제 해야 하지 않지만 저렇게 해주시는 것이 좋습니다.

 

물론 저렇게 한다고 가비지 콜렉터가 돈다는 보장은 못하지만 적어도 다 사용했다 라는 힌트 정도는 줄 수 있으니까요.

 

소스코드를 보시고 그 다음은 전에 만들어 두었던 문서관리에 DataBase부분을 고쳐 보도록 하죠. 이건 다음 글 쓸 때 해보도록 하죠.

 

 

 

 

 

 

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;


public class WebServer {

	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(80);
			
		while(true){
			//접속을 기다림 
			Socket client=server.accept();
			//쓰기 준비
			OutputStream  os=client.getOutputStream();
			//읽기 준비
			InputStream is = client.getInputStream();
			
			Request request = new Request(is);
			
			// /EXIT면 서버 종료
			if(request.getRequestURL().equals("/EXIT")){
				System.out.println("서버종료됨");
				break;			
			}
			
			Response response = new Response(os);
		
			Resource resource = new Resource();
			resource.service(request, response);
			
			//자원해제
			resource = null;
			response = null;
			request = null;
			
			//닫기
			os.close();
			is.close();
			client.close();
		}
	}

}

import java.io.IOException;
import java.io.InputStream;


public class Request {
	private InputStream is = null;
	private String requestURL= null;
	
	public Request(InputStream is) throws IOException{
		this.is = is;
		readAndParse();
	}

	private void readAndParse() throws IOException {
		byte [] buffer = new byte[4096];
		this.is.read(buffer);
		
		String httpProtocol= new String(buffer);
		//라인 별로 분리
		String[] headerLineSeparation=httpProtocol.split("\r\n");
		//공백 별로 분리
		String[] headerSpaceSeparation=headerLineSeparation[0].split(" ");
	
		this.requestURL=headerSpaceSeparation[1];
	}
	
	public String getRequestURL(){
		return this.requestURL;
	}
}
import java.io.IOException;
import java.io.OutputStream;


public class Response {
	OutputStream os = null;
	
	public Response(OutputStream os){
		this.os = os;
	}
	public void write(String data) throws IOException{
		write(data.getBytes());
	}
	
	public void write(byte[] data) throws IOException{
		this.os.write(data);
		this.os.flush();
	}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class Resource {
	
	public Resource(){
		
	}
	public void service(Request request, Response response) throws IOException{
		
		File htmlFile = new File("c:/www/",request.getRequestURL());
		
		if(!htmlFile.exists()){
			String browserMessage="HTTP/1.1 404 Not Found\r\n";
			response.write(browserMessage);
		}else{
			FileInputStream fis = new FileInputStream(htmlFile);
			byte[] fileBuff = new byte[4096];
			while((fis.read(fileBuff))>0){
				response.write(fileBuff);
			}
		}
	}
	
}

'Programing > Java programing' 카테고리의 다른 글

4-4 JDBC -1  (0) 2010.08.10
4-3 DataBase network  (0) 2010.08.07
4-1 I.O and File  (0) 2010.07.31
3-3 메소드, 클래스 분리 회고  (0) 2010.07.26
3-2 클래스로 분리  (0) 2010.07.25