Presentation is loading. Please wait.

Presentation is loading. Please wait.

3장 소켓 프로그래밍 3.1 IP 주소와 Port DNS

Similar presentations


Presentation on theme: "3장 소켓 프로그래밍 3.1 IP 주소와 Port DNS"— Presentation transcript:

1 3장 소켓 프로그래밍 3.1 IP 주소와 Port DNS
자바 언어는 네트워크 프로그래밍에 필요한 다양한 클래스를 지원. 소켓은 분산 프로그래밍을 위한 중요한 요소이며, 자바 시스템은 소켓 클래스를 제공. 자바 프로그램에서 소켓을 사용하기 위해서는 먼저 import java.net.*; 문을 사용하여야 한다. 3.1 IP 주소와 Port 네트워크 프로그래밍을 하기위해서는 상대방 컴퓨터와 통신 포트에 대한 정보를 알수 있어야 한다. 인터넷에 연결되어 데이터 교환이 가능한 컴퓨터 시스템을 호스트라고 부르며, 모든 호스트는 IP 주소를 가진다. IP 주소는 4 바이트로 구성되며, 모든 호스트는 유일한 IP를 가진다. IP는 0부터 255사이의 정수 4개로 구성되기 때문에 사람이 기억하기는 쉽지 않다. IP는 사용이 불편하므로, 사람이 기억하기 쉬운 이름을 등록하여 사용하는데 이를 도메인 이름이라고 한다. 예를 들어, 한성대학교의 도메인 이름이다. 도메인 이름은 사람에게는 편리하지만 컴퓨터가 이를 처리하기 위해서는 먼저 IP 주소로 변경해야 한다. 도메인 이름을 IP로 변경해주는 시스템을 DNS(Domain Name Server)라고 한다. 인터넷에 연결된 모든 호스트들은 DNS에 접속할 수 있어야한다. 도메인이름 IP주소 DNS

2 IP주소는 인터넷에 연결된 호스트 시스템을 결정한다
IP주소는 인터넷에 연결된 호스트 시스템을 결정한다. 한 호스트에는 여러 개의 포트가 있어, 여러 개의 프로세스가 동시에 데이터를 주고받을 수 있다. 만일 IP주소만을 가지고 통신을 한다면, 하나의 프로세스만이 통신 가능하므로 불편할 것이다. 즉 포트는 컴퓨터내의 여러 통신 프로세스 중에서 원하는 프로세스에게만 전달하기 위한 번호이다. 포트 번호는 16비트로 표현되기 때문에 0부터 65525까지의 포트 번호가 가능하다. 0부터 1023까지의 포트 번호는 시스템만이 사용하도록 예약되어 있으며, 사용자는 1024부터 65535까지의 포트를 자유롭게 사용할 수 있다. 인터넷 통신을 위해서 포트번호도 IP 주소와 함께 반드시 명시하여야 하지만, 통신 프로토콜에 따라 기본 포트 번호를 가지고 있으므로 생략이 가능하다. 다음은 주요 인터넷 프로토콜이 사용하는 기본 포트 번호를 정리한 것이다. 그러므로 우리가 홈페이지를 액세스할 때, 이라고 명시하는 것은 묵시적으로 80 포트를 사용하므로 이라고 표시하는 것과 동일하다. 자바에서는 인터넷 주소 클래스, TCP 통신을 위한 소켓 클래스, UDP 통신을 위한 소켓 클래스 등 풍부한 네트워킹 기능을 지원한다. 다음 절부터 간단히 살펴보기로 한다.

3 3.2 InetAddress 클래스 InetAddress 클래스는 자바 프로그래머에게 IP 주소에 관련된 메소드를 지원하여 준다. 그러므로 InetAddress 객체를 사용하면 IP 주소와 관련된 다양한 연산을 손쉽게 할 수 있다. InetAddress 클래스의 생성자와 메소드를 정리하면 다음과 같다. o 생성자 InetAddress 클래스의 생성자는 없다. 객체를 만들기 위해서는 클래스 정적 메소드인 getByName(), getLocalHost(), getAllByName()을 사용. InetAddress 클래스에서 지원되는 메소드를 정리하면 다음과 같다.

4 다음은 InetAddress 클래스를 설명하기 위한 간단한 예제 프로그램이다
다음은 InetAddress 클래스를 설명하기 위한 간단한 예제 프로그램이다. 실행 결과는 로컬 호스트의 IP와 검색엔진 naver 사이트에 대한 정보를 출력한다. [예제 프로그램] import java.net.*; public class InetDemo{ public static void main(String a[]) throws Exception{ InetAddress ia; ia= InetAddress.getLocalHost(); System.out.println("LocalHost의 IP 주소: " + ia.getHostAddress()); ia = InetAddress.getByName(" System.out.println("naver의 IP 주소: " + ia.getHostAddress()); System.out.println("naver의 호스트 이름: " + ia.getHostName()); } }

5 [실행결과] C:\JAVA\newED\ch16>java InetDemo LocalHost의 IP 주소: naver의 IP 주소: naver의 호스트 이름: [코딩 연습] 한성대학교 홈페이지의 도메인 이름 “ IP 주소를 알아보시오. 그와 반대로 한성대학교 홈페이지의 IP 주소를 입력하여, 도메인 이름을 알아낼 수 있는지 알아 보시오.

6 o InetAddress.getAllByName() 메소드 예제
일부 인터넷 도메인 이름은 두개 이상의 IP로 번역된다. 이러한 경우에 getAllByName() 메소드를 사용하면 대응되는 모든 IP를 포함하는 배열을 반환한다. 이 메소드의 사용 예는 다음과 같다. InetAddress[] addr = InetAddress.getAllByName(" [예제 프로그램] import java.net.*; class AllAddress { public static void main (String[] args) { try { String domain = // " " // " InetAddress[] addresses = InetAddress.getAllByName(domain); for (int i = 0; i < addresses.length; i++) { System.out.println(addresses[i]); } System.out.println("----- one name ----"); System.out.println(InetAddress.getByName(domain)); catch (UnknownHostException e) { System.out.println("No DNS found"); } } }

7 [실행 결과] www.naver.com/61.247.208.7 www.naver.com/222.122.84.200
----- one name ---- 계속하려면 아무 키나 누르십시오 . . .

8 3.3 URL 클래스 URL(Uniform Resource Locator)는 원래 WWW 상에 존재하는 자원의 위치를 표시하기 위한 것이다. URL은 프로토콜, 호스트 ID, 포트, 디렉토리, 파일로 구성된다. 자바의 URL 클래스는 인터넷 자원 주소를 나타내는 URL을 편리하게 사용할 수 있게 해준다. URL 클래스는 다음과 같은 생성자를 가진다. o 생성자 ① URL(String protocol, String host, int port, String file) throws MalformedURLException ② URL(String protocol, String host, String file) ③ URL(String urlString) throws MalformedURLException URL 클래스에서 지원되는 메소드를 정리하면 다음과 같다.

9 public static void main(String args[]) throws MalformedURLException {
다음은 URL 클래스를 설명하기 위한 예제 프로그램이다. 예제 프로그램을 실행시킨 후, 하단의 텍스트 필드에 URL을 입력하면 텍스트 영역에 URL 정보를 출력한다. [예제 프로그램] package p1; import java.net.*; class URLDemo { public static void main(String args[]) throws MalformedURLException { URL hp = new URL(" System.out.println("Protocol: " + hp.getProtocol()); System.out.println("Port: " + hp.getPort()); System.out.println("Host: " + hp.getHost()); System.out.println("File: " + hp.getFile()); System.out.println("Ext:" + hp.toExternalForm()); } [실행결과] Protocol: http Port: -1 Host: File: /index.jsp Ext:

10 o URL에서 데이터 읽기 URL 객체는 일반적으로 원격 컴퓨터의 자원 객체를 가리킨다. 자바의 URL 클래스에서 URL 객체가 가리키는 자원 객체에서 스트림을 읽어 오기 위해서는 openStream() 메소드를 사용한다. try { int ch; URL url = new URL(" InputStream in = url.openStream(); while((ch = in.read()) != -1) System.out.print(ch); } catch(IOException e) { // IOException 처리 }

11 3.4 URLConnection URLConnection 클래스는 URL이 명시하는 자원에 접속한 후, 원격 자원에 대한 정보를 손쉽게 알아볼 수 있는 여러 가지 메소드를 지원하는 클래스이다. 그러므로 URLConnection 클래스는 URL 클래스보다 더 많은 서버 관련 메소드를 지원한다. URLConnection을 사용하여 서버와 연결을 하기 위해서는 다음과 같은 절차를 필요로 한다. ① URL 클래스를 사용하여 연결 하고자 하는 원격자원의 URL객체를 생성 ② 연결에 필요한 여러 파라메터를 설정 ③ URL객체의 openConnection() 메소드를 이용해서 원격 자원과 연결 ④ URLConnection 클래스의 다양한 메소드 사용 URLConnection 객체를 생성하기 위해서는 생성자를 사용하거나 URL객체의 openConnection() 메소드를 사용하여야 한다. URLConnection 클래스 생성자는 다음 하나만을 지원한다.

12 o 생성자 ① protected URLConnection(URL url)
다음은 URLConnectin 클래스에서 지원되는 메소드를 정리한 것이다. ① URL getURL() - URLConnection 객체의 URL을 반환한다. ② int getContentLength() - 연결된 문서의 길이를 반환한다. ③ String getContentType() - 연결된 문서의 타입을 반환한다. ④ long getDate() - header date 항목의 값을 반환한다. ⑤ long getExpiration() - 헤더의 expires 항목 값을 반환한다. ⑥ long getLastModified() - 해당 문서의 마지막 수정 날짜를 반환한다. ⑦ InputStream getInputStream() throws IOException - 연결된 문서를 읽기 위한 InputStream객체를 반환 ⑧ OutputStream getOutputStream() thorws IOException - 연결된 문서에 출력하기 위한 OutputStream객체를 반환 ⑨ String getHeaderField(int n) - n 번째 헤더 항목을 얻는다. (시작은 0부터) ⑩ String getHeaderField(String name) - 주어진 헤더에 해당되는 필드값을 얻는다. ⑪ String getHeaderFieldKey(int n) - n 번째 헤더 항목의 키를 반환한다.

13 다음은 간단한 URLConnection 클래스 예제 프로그램이다
다음은 간단한 URLConnection 클래스 예제 프로그램이다. 하단의 텍스트 필드에 URL을 입력하면 입력된 URL에 대한 정보와 URL 파일 내용을 텍스트 영역에 출력한다. [예제 프로그램] package p1; import java.net.*; import java.io.*; import java.util.Date; public class UCDemo { public static void main(String args[]) throws Exception { int c; URL hp = new URL(" URLConnection hpCon = hp.openConnection(); // get date long d = hpCon.getDate(); System.out.println("Date: " + new Date(d)); // get content type System.out.println("Content-Type: " + hpCon.getContentType()); // get expiration date d = hpCon.getExpiration(); System.out.println("Expires: " + new Date(d)); // get last-modified date d = hpCon.getLastModified(); System.out.println("Last-Modified: " + new Date(d)); // get content length

14 int len = hpCon.getContentLength();
if(len == -1) System.out.println("Content length unavailable."); else System.out.println("Content-Length: " + len); if(len != 0) { System.out.println("=== Content ==="); InputStream input = hpCon.getInputStream(); int i = len; while (((c = input.read()) != -1)) { System.out.print((char) c); } input.close();

15 [실행결과] packageDate: Wed Jun 30 23:30:04 KST 2010 Content-Type: text/html Expires: Thu Jan 01 09:00:00 KST 1970 Last-Modified: Tue Jun 10 08:03:02 KST 2003 Content-Length: 327 === Content === <HTML><HEAD> <META HTTP-EQUIV="Refresh" CONTENT="0; URL= </HEAD><BODY></BODY></HTML> <! > <!-- Caught you peeking! --> ☞ 요즘 웹사이트는 HTML문서를 읽는 것으로부터 보호되는 경우가 있다. Prenhall.com 사이트에서도 HTML문서를 읽어 오는 것이 허용되지 않았음

16 3.5 TCP Socket Socket이란 컴퓨터 네트워크 통신 프로그램을 위한 소프트웨어 메커니즘을 나타낸다. 소켓은 네트워크 통신에 대한 세부사항을 감추어 주고, 메시지 교환을 위한 고급 메소드를 지원한다. 그러므로 프로그래머가 소켓을 사용하면 통신 프로그램을 간단하게 작성할 수 있다. 다음 그림은 소켓을 통해서 두 원격 프로세스가 데이터를 주고받는 것을 나타낸 것이다. 네트워크 통신에 관한 세부 사항은 소켓을 구현한 시스템 프로그래머가 담당하고, 응용 프로그래머에게는 감추어진다. 프로세스 A와 B가 데이터 x를 송수신하기 위해서는 먼저 소켓을 설정한 후에, 프로세스 A는 소켓에 데이터 x를 쓰고, 프로세스 B는 소켓에서 데이터 읽기를 실행하면 된다. 자바의 소켓은 객체화되어 있어서 기존 시스템의 소켓과 비교할 때 생성과 설정이 간편하다. 특히 자바 소켓은 플랫폼에 무관하게 생성하여 사용할 수 있으며, 다양한 고급 메소드를 지원한다는 특징이 있다. 또한 자바의 소켓은 기존의 시스템과는 달리 TCP 프로토콜만을 지원하며, UDP 프로토콜을 사용하기 위해서는 별도의 데이터그램 소켓(DatagramSocket)을 사용해야 한다.

17 3.5.1 TCP와 UDP TCP와 UDP는 TCP/IP 프로토콜의 전송방식을 나타낸다. 여기서 TCP와 UDP를 간단히 비교 설명하면 다음과 같다. ① TCP TCP는 연결성 통신 방식을 의미한다. 데이터를 송수신하고자 하는 호스트간에 먼저 가상 연결선을 구성한 다음 데이터를 전송하는 것이다. TCP는 일상생활의 전화 통신과 비슷하다. 전화를 거는 사람은 먼저 다이얼을 돌려 수신자를 결정한다. 수신자가 전화를 받아 서로의 신원을 확인하면 데이터를 주고 받게된다. 더 이상 송수신할 데이터가 없으면 전화를 끊어 연결을 끊게 된다. TCP 전송방식의 특징은 다음과 같다. - 신뢰성이 높은 프로토콜이다. - 전송 채널을 설정해야 하므로 짧은 메시지 전송시 오버헤드가 크다. - 동일한 경로를 통해 전송되므로 전송된 메시지 순서가 송신측과 항상 일치한다. - 데이터를 전송하지 않아도 채널이 유지되므로 전송라인이 낭비될 수 있다. ② UDP UDP는 비연결성 통신 방식이다. 전송하고자하는 데이터를 일정 크기의 패킷으로 나누어 전송한다. 전송채널을 확립하지 않기 때문에 모든 패킷은 목적지 정보를 포함하여야 한다. - 신뢰성이 낮은 프로토콜이다. - 전송되는 메시지의 순서와 수신되는 메시지의 순서가 일치하지 않을 가능성이 있다. 메시지 순서를 다시 맞추어야 하는 불편함이 있다. - 모든 메시지에 목적지를 명시하여야 한다. - 각 메시지는 통신라인의 busy여부에 따라 다른 경로를 통해 전송될 수 있다. TCP와 비교해서 전송라인의 효율이 좋다. - 통신 채널을 설정하는 오버헤드가 없다.

18 소켓을 사용한 클라이언트-서버간의 접속 순서
소켓은 일반 소켓과 서버소켓으로 구분된다. 서버 소켓은 네트워크 상에서 자신에게 오는 요청을 기다린다. 만일 클라이언트 측에서 접속 요구가 있으면, 새로운 소켓을 생성해서 클라이언트와 통신을 하게된다. 이러한 절차를 간단히 기술하면 다음과 같다. 3.5.2 ServerSocket 클래스 ServerSocket 클래스는 통신 서버에서 생성하는 소켓이다. ServerSocket은 클라이언트로부터 접속 요구를 기다리고 있다가, 접속 요구가 있을 경우 새로운 통신 소켓을 생성하는 일을 한다.

19 ☞ 서버소켓에서는 클라이언트의 접속에 대기하므로, 클라이언트의 정보를 설정할 수 없다
☞ 서버소켓에서는 클라이언트의 접속에 대기하므로, 클라이언트의 정보를 설정할 수 없다. 다만 클라이언트가 접속할 포트번호는 명시하여야 한다. ServerSocket 클래스의 생성자는 다음과 같다. o 생성자 ① ServerSocket(int port) - 주어진 포트를 사용하는 서버소켓을 생성한다. ② ServerSocket(int port, int backlog) - 주어진 포트와 입력 버퍼의 크기를 사용하는 서버소켓을 생성한다. ③ ServerSocket(int port, int backlog, InetAddress bindAddr) - bindAddr은 서버의 주소가 두 개 이상인 경우에 서버소켓이 사용할 인터넷 주소를 명시한 것이다. 다음은 ServerSocket 클래스에서 지원되는 메소드를 정리한 것이다.

20 서버 소켓은 accept() 메소드를 수행함으로써 클라이언트의 요청에 따라 실제 통신을 수행할 소켓을 생성하여 반환한다
서버 소켓은 accept() 메소드를 수행함으로써 클라이언트의 요청에 따라 실제 통신을 수행할 소켓을 생성하여 반환한다. ServerSocket 객체는 클라이언트와 통신할 소켓을 생성하며, 실제 통신은 서버 소켓이 아니라 생성된 소켓을 통해 이루어진다. 3.5.3 Socket 클래스 TCP 방식을 사용해서 통신을 하기 위해서는 Socket 클래스 객체를 생성해야 한다. Socket 클래스는 클라이언트와 서버간에 실질적인 통신 메소드를 지원하고 있다. Socket 클래스의 생성자는 다음과 같다. o 생성자 ① Socket(String host, int port) throws IOException, UnknownHostException - 소켓을 생성하고 주어진 host와 port에 접속한다. 접속이 안 이루어지는 경우 IOException이 발생한다. ② Socket(InetAddress address, int port) throws IOException - 소켓을 생성하고 주어진 address와 port에 접속한다. 접속이 안 이루어지는 경우 IOException이 발생한다. ③ Socket(SocketImpl impl) throws SocketException - 사용자 명시 SocketImpl 클래스의 Socket 객체 생성 ④ Socket() - 연결 안된 소켓을 생성

21 다음은 Socket 클래스에서 지원되는 메소드를 정리한 것이다.

22 [예제 프로그램] import java.net.*; import java.io.*;
public class SocketInfo { public static void main(String[] args) { try { Socket sock = new Socket(args[0], 80); System.out.println("Remote Addr: " + sock.getInetAddress()); System.out.println("Remote port: " + sock.getPort()); System.out.println("Local Addr : " + sock.getLocalAddress()); System.out.println("Local port : " + sock.getLocalPort()); } catch (UnknownHostException e) { System.err.println("UnKnown Host : " + args[0]); catch (SocketException e) { System.err.println("Socket Exception " + e); catch (IOException e) {

23 [실행결과] Remote Addr: www.hansung.ac.kr/128.134.165.1 Remote port: 80
Local Addr : / Local port : 2447 [Question] 위의 실형결과에서는 Local port가 2247로 출력되었다. 원격 포트와 로컬포트가 다른 이유는 무엇인가? ☞ 시스템이 1024번 이후의 사용 가능한 로컬 시스템의 포트를 임의로 선택한 것임. 하나의 소켓를 사용하여 동시에 입력 스트림과 출력 스트 림 객체를 생성하는 것이 가능하다. 입출력 객체를 생성한 후에는 스트림 입출력 객체에 적용할 수 있는 모든 메소드를 사용할 수 있다. 그러므로 자바의 네트워크 프로그래밍을 위해서는 먼저 입출력 스트림을 이해하는 것이 필요하다.

24 o Telnet과 통신하는 소켓 프로그램 윈도우를 포함하는 대부분의 운영체제에서 지원하는 유틸리티 telnet은 원격 컴퓨터에 텍스트 명령어를 전송하고 그 결과값을 반환하는 기능을 수행한다. telnet은 소켓 통신을 하므로, 자바 소켓 프로그램과 연동할 수 있다. 윈도우7: 제어판 – 프로그램 – 프로그램 및 기능 – 윈도우 기능 사용 텔넷 클라이언트를 체크

25 [예제 프로그램] package p2; import java.io.*; import java.net.*;
public class TelnetDemo { public static void main(String[] a) throws Exception { ServerSocket ss = new ServerSocket(4400); Socket s=ss.accept(); BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); PrintWriter pw = new PrintWriter(s.getOutputStream()); System.out.println("Connected:"+s); String msg=in.readLine(); pw.println(msg); pw.flush(); while(!msg.equals("end")){ System.out.println(msg); msg = in.readLine(); } }}

26 [실행 결과] ① 도스창 C:\>telnet 211.47.90.2 4400 (서버의 IP주소와 4400을 명시) kim
lee park End ② 자바 프로그램 출력 Connected:Socket[addr=/ ,port=64755,localport=4400]

27 3.5.4 단순 메시지 전송 예제 다음은 TCP 소켓을 설명하기 위한 예제 프로그램이다. 여기서는 클라이언트와 서버가 모두 자바로 작성되어 문자열을 전송한다. 서버 프로그램을 먼저 실행한 후에 클라이언트 프로그램을 실행하여야 한다. 클라이언트 프로그램을 실행할 때 명령행 인자로 서버의 IP 주소나 도메인이름을 명시해야 한다. [예제 프로그램] 1) 서버 예제 코드 앞에서 사용한 TelnetDemo.java를 실행한다.

28 2) 클라이언트 예제 코드 package p2; import java.io.*; import java.net.*;
public class SimpleMsg { public static void main(String[] arg) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Key in IP = "); String msg, ip = in.readLine(); Socket s = new Socket(ip,4400); System.out.println("Server connected:"+s); PrintWriter pw = new PrintWriter(s.getOutputStream()); do{ System.out.print("Message: "); msg=in.readLine(); pw.write(msg+"\n"); pw.flush(); }while(!msg.equals("end")); }

29 [실행결과] 1) 서버 측 실행 결과 Connected:Socket[addr=/ ,port=64755,localport=4400] kim lee Park 2) 클라이언트 측 실행 결과 Key in IP = Server connected:Socket[addr=/ ,port=4400,localport=64775] Message: kim Message: lee Message: park Message: end

30 public class AcceptMulti extends Thread { Socket sk;
[프로그래밍 연습] 여러 사람이 한사람에게 메시지를 전송할 수 있도록 TelnetDemo.java를 수정하시오. (SimpleMsg.java는 그대로 사용함) 여기서는 멀티 스레딩을 사용하여야 한다. [예제 프로그램] package p2; import java.io.*; import java.net.*; public class AcceptMulti extends Thread { Socket sk; public AcceptMulti(Socket so){ sk=so; } public static void main(String[] a) throws Exception { ServerSocket ss = new ServerSocket(4400); while(true) { Socket so=ss.accept(); System.out.println("Connected:"+so); new AcceptMulti(so).start();

31 public void run(){ try { BufferedReader in = new BufferedReader(new InputStreamReader(sk.getInputStream())); PrintWriter pw = new PrintWriter(sk.getOutputStream()); String msg=in.readLine(); pw.println(msg); pw.flush(); while(!msg.equals("end")){ System.out.println(msg); msg = in.readLine(); }}catch(IOException ex){ System.out.println(ex); System.exit(-1); }}} o 실행방법 ☞ 수신측에서 AcceptMulti.java를 실행하고, 다수의 컴퓨터에서 Telnet이나 SimpleMsg.java를 실행한다. (실행시에는 수신컴퓨터의 IP와 포트번호 4400을 명시해야 한다.) ☞ 수신측에서 멀티 스레딩을 해야 하는 이유는 TCP 소켓은 1:1 통신이 가능하므로, 수신측에서는 메시지를 송신하는 프로세스의 수와 동일한 수의 스레드를 생성하 여 1:1로 대응시킨다.

32 3.5.5 파일 송수신 예제 package p2; import java.io.*; import java.net.*;
자바에서 파일 입출력 프로그래밍과 소켓 프로그래밍은 스트리밍 기술을 사용하므로, 실제 데이터를 송수신하는 코딩은 매우 비슷하다. 여기서 소개하는 예제는 파일 스트림과 소켓을 이용하여 파일을 송수신하는 프로그램이다. 파일이름을 먼저 송신한 다음, 파일 내용을 전송한다. 매우 단순한 프로그램이지만, 모든 종류의 파일을 송수신할 수 있다. ※ ObejctInputStream, ObjectOutputStream 객체의 readUTF(), writeUTF()메소드는 유니코드 문자열을 송수신할 때 사용하는 메소드이다. [파일 수신 코드: FileReceiver.java] package p2; import java.io.*; import java.net.*; public class FileReceiver implements Runnable { Socket s; public FileReceiver(Socket s){ this.s = s; }

33 public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(8888); while(true){ Socket s = ss.accept(); System.out.println("Client Connected"+":"); new Thread(new FileReceiver(s)).start(); }} public void run() { DataInputStream r; DataOutputStream w; try { r = new DataInputStream(s.getInputStream()); String fn = r.readUTF(); System.out.println("File: "+fn+" receiving"); w=new DataOutputStream(new FileOutputStream(fn)); while(true) w.writeChar(r.readChar()); }catch(EOFException e){ System.out.println("EOF"); } System.out.println("File Transfer End"); r.close(); w.close(); } catch(IOException exp) {}

34 [파일 송신 코드: FileSender.java]
import java.io.*; import java.net.*; class FileSender { public static void main(String[] args) throws Exception { if(args.length < 2) { System.out.println("Specify Server IP & File Name"); System.exit(1); } Socket s=new Socket(args[0], 8888); DataOutputStream w = new DataOutputStream(s.getOutputStream()); DataInputStream r = new DataInputStream(new FileInputStream(args[1])); w.writeUTF(args[1]); try { while( true) w.writeChar(r.readChar()); } catch(EOFException exp) { } System.out.println("File Transfer End"); r.close(); w.close(); }}

35 [실행 결과] ① 파일 수신측 C:\JAVA\test\server>java FileReceiver
Client Connected File: 08.jpg receiving EOF File Transfer End C:\JAVA\test\server> ② 파일 송신측 C:\JAVA\test>java FileSender localhost 08.jpg C:\JAVA\test>

36 3.6 시스템 클래스 객체의 송수신 소켓을 통해서 데이터를 전송하는 것은 파일에 데이터를 저장하는 것과 매우 유사하다. 클래스가 정의될 때, java.io.Serializable 인터페이스를 구현(implements)을 명시하였다면, 그 객체는 파일에 저장하는 것이 가능하며, TCP 소켓을 통해 원격 프로세스에 전송할 수 있다. 다음은 java.util.Date 객체를 TCP 소켓을 통해서 송수신하는 간단한 예제이다. Date 클래스도 Serializable 인터페이스를 구현하고 있으므로 TCP 소켓을 통해 송수신이 가능하다. 다음 예제는 ObjectOutputStream, ObjectInputStream 객체의 writeObject(), readObject() 메소드를 사용하여 객체를 송수신하는 예제 프로그램이다.

37 [예제 코드: ReceiveDate.java]
import java.io.*; import java.net.*; import java.util.Date; public class ReceiveDate { public static void main(String args[]) throws Exception { ServerSocket ss =new ServerSocket(7788); Socket sock = ss.accept(); ObjectInputStream is = new ObjectInputStream(sock.getInputStream()); Date date = (Date) is.readObject(); System.out.println("Today is "+date); is.close(); sock.close(); }}

38 [예제 코드: SendDate.java] import java.io.*; import java.net.*;
import java.util.Date; public class SendDate { public static void main(String args[]) throws Exception { String ip=null; if (args.length==0) ip="localhost"; else ip=args[0]; Socket sock = new Socket(ip, 7788); ObjectOutputStream os = new ObjectOutputStream(sock.getOutputStream()); Date date = new Date(); os.writeObject(date); os.flush(); os.close(); sock.close(); System.out.println("We have sent a Date"); }}

39 [실행결과] ReceiveDate가 서버 소켓을 포함하고 있으므로, 먼저 실행되어야 한다. SendDate 실행 컴퓨터와 ReceiveDate 실행 컴퓨터가 서로 다르다면, 실행시 ReceiveDate 실행 컴퓨터의 IP 또는 도메인 주소를 명시해야 한다. 1) 서버 소켓 측 E:\test>java ReceiveDate Today is Wed Jan 23 14:15:47 KST 2002 2) 클라이언트 측 E:\test>java SendDate We have sent a Date

40 3.6.2 자바 제공 유틸리티: Serialver 앞의 예제에서 사용한 Date는 Serializable 인터페이스를 구현한 클래스이지만, 자바가 제공하는 모든 클래스들이 Serializable 인터페이스를 구현하고 있는 것은 아니다. 그러므로 자바의 객체를 파일에 저장하거나 소켓으로 전송하는 프로그램을 작성할 때에는 그 클래스가 Serializable 인터페이스를 구현하고 있는지 확인할 필요가 있다. 이를 위해서 JDK에서 제공되는 유틸리티 “serialver"를 사용할 수 있다. C:\java>serialver -show 위의 명령어를 입력하면, 다음과 같은 프레임이 표시되고, 검사를 원하는 클래스의 이름(경로 포함)을 입력하고 [show] 버튼을 클릭한다. 다음은 Date 클래스를 입력한 결과 Date 클래스의 serialVersionUID값이 출력되었음을 보여준다. 다음은 java.awt.Graphics 클래스를 입력한 결과이다. Graphics 클래스는 Serializable인터페이스를 구현하고 있지 않음을 알 수 있다.

41 3.6.3 프로그래머 정의 객체의 송수신 프로그래머가 정의한 객체를 소켓을 통해 전송하려면, 클래스를 정의할 때 Serializable 인터페이스를 구현하여야 한다. 예를 들어 프로그래머가 Person이라는 클래스를 다음과 같이 정의하였다고 가정한다. class Person { // 정의 내용은 생략 } 위의 Person 클래스의 객체를 소켓으로 전송하기 위해서는 다음과 같이 변경되어야 한다. 새롭게 구현되어야 하는 메소드는 없다. class Person implements Serializable { // 정의 내용은 동일 다음은 TCP 소켓을 통하여 Person 객체를 송수신하는 간단한 예제이다. SendPerson 클래스가 서버 소켓을 포함하고 있으므로 먼저 실행하여야 한다. GetPerson 클래스를 실행할 때 명령행 인자로서 SendPerson이 실행되고 있는 컴퓨터의 IP나 도메인 이름을 명시해야 한다. 이를 생략하면, 두 프로그램이 동일한 컴퓨터에서 실행되는 것으로 간주한다.

42 [SendPerson.java] package p3; import java.io.*; import java.net.*;
class Person implements Serializable { static final long serialVersionUID= L; String name; int id; Person(String name, int id) { this.name = name; this.id = id; } public String toString() { String s = name + " " + id; return s; }}

43 public class SendPerson {
public static void main(String args[]) { Person p[] = new Person[3]; p[0] = new Person("Kim", 7); p[1] = new Person("Lee", 8); p[2] = new Person("Park", 12); try { Socket s=null; if(args.length>0)s=new Socket(args[0],7788); else s=new Socket("localhost",7788); ObjectOutputStream os = new ObjectOutputStream(s.getOutputStream()); for(int i=0; i<3; i++) os.writeObject(p[i]); os.close(); System.out.println("3 Person Sent"); } catch (Exception e) { e.printStackTrace(); } }}

44 [ReceviePerson.java] package p3; import java.io.*; import java.net.*;
public class ReceivePerson { public static void main(String args[]) { Person p[] = new Person[3]; try { ServerSocket ss=new ServerSocket(7788); Socket sock = ss.accept(); ObjectInputStream is = new ObjectInputStream(sock.getInputStream()); for(int i=0; i<3; i++) p[i] = (Person) is.readObject(); is.close(); System.out.println("Three Person received"); for(int i=0; i<3; i++) System.out.println(p[i]); } catch (Exception e) { e.printStackTrace(); } }

45 [실행과정 및 결과] 1) 서버 소켓 측 E:\test>java SendPerson 3 Person Sent 2) 클라이언트 측 E:\test>java ReceivePerson Three Person received Kim 7 Lee 8 Park 12

46 [네트워크 주소록 프로그램 연습] 다음은 네트워크를 통해 Student 클래스의 객체를 송수신하며, 전화번호를 저장/검색하는 예제 프로그램이다. 다음 프로그램을 실행하고, 전화번호를 서버측 파일에 저장하도록 수정하시오. (서버측에서 주기적으로 전화번호부의 변경사항을 체크하고, 변경이 발생하면 파일에 저장하도록 한다. 서버가 실행을 재개할 경우 파일에 저장되었던 기존 전화번호의 내용을 읽어오도록 한다.) [Student 클래스와 Constants 인터페이스의 정의] package p3; import java.io.Serializable; interface Constants { final int SAVE=1, FIND=2, END=3; final int FOUND=4, NOTFOUND=5; }

47 class Student implements Constants, Serializable {
String name, telephone; int enterYr, control=0; public Student(String name, int year, String telephone) { this.name = name; this.enterYr = year; this.telephone = telephone; } public Student(String name, int year, String telephone, int control) { this(name,year,telephone); this.control=control; public String toString(){ return name+":"+enterYr+":"+telephone; }}

48 [실행 결과] 1. 클라이언트 실행 2. 서버 실행 Student Server Ready
Accepted: Socket[addr=/ ,port=50661,localport=7890] 김정수:2004: Accepted: Socket[addr=/ ,port=50663,localport=7890] 김정훈:2009: ※ 서버에서 검색한 객체를 클라이언트에게 전송하여, 클라이언트가 객체를 읽어오는 부분에 bug가 있음. 각자 원인을 분석해보기 바람.

49 3.6.4 간단한 웹 서버 프로그램 HTTP 프로토콜은 웹에서 가장 많이 사용되는 프로토콜이다. 웹 브라우저가 특정 사이트의 홈페이지를 접속하기 위해서는 URL을 명시하여야 한다. 웹 브라우저와 웹 서버 사이의 HTTP 통신은 TCP/IP 기반으로 클라이언트가 요청하고 서버가 응답함으로써 완료된다. 1) HTTP 요청 웹 클라이언트가 서버에게 전송하는 메시지의 첫째 라인은 매우 중요하며, 다음의 정보를 포함하고 있다. ① GET/POST 방식의 선택 ② 접속하고자하는 리소스의 경로명과 파일이름 ③ HTTP 프로토콜의 버전 예) 클라이언트 메시지의 첫째 라인의 예 ① GET / HTTP/1.1 [CR LF]// default 페이지 전송 요구 ② GET /index.html HTTP/1.1 [CR LF]// index.html 문서 요구 ③ GET /MyPage/index.html HTTP/1.1 [CR LF]// /MyPage/index.html 문서 요구 ④ POST /cgi-bin/score.cgi HTTP/1.1 [CR LF]// /cgi-bin/score.cgi 실행 결과 요구

50 웹 서버가 클라이언트 요구에 응답하는 메시지로서, HTTP 응답 부분의 시작 형식은 다음과 같다.
웹 클라이언트의 요청에는 웹 브라우저, 클라이언트 도메인, 사용 언어, 문자 셋, 코딩 형식, 수식 가능 파일 형식에 관한 정보를 포함한다. 다음은 웹 클라이언트의 요청 메시지의 예이다. [웹 클라이언트의 요청 메시지 예] GET / HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, */* Accept-Language: ko Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows 98; Win 9x 4.90) Host: ikim.hansung.ac.kr:8000 Connection: Keep-Alive 2) HTTP 응답 웹 서버가 클라이언트 요구에 응답하는 메시지로서, HTTP 응답 부분의 시작 형식은 다음과 같다. [응답 형식] HTTP/protocol-version response-code [CR LF] [LF] HTML_Document

51 [실제 예] HTTP/1.1 200 OK [CRLF] [LF]
<html><body><h1>Hello! HTTP</h1></body></html> 다음은 단순한 웹 서버 예제이다. 이 예제를 실행하고, 웹 브라우저로 접속하면, 서버 측에서는 HTTP 요청 내용을 출력하고, 웹 브라우저에는 Hello 메시지를 표시한다. 실행시에 명령행 인자로서 웹서비스 포트를 지정할 수 있다. 명시하지 않으면, 기본값인 8000번 포트로 접속요구를 받는다. 컴퓨터 시스템이 이미 선점하여 사용하고 있는 포트를 지정하는 경우에는 실행 오류가 발생한다. 이 경우 사용하지 않는 다른 포트 번호를 명시하여야 한다.

52 [실행결과] ① 서버측 실행 (포트를 8000으로 지정) ② 클라이언트측 실행결과
E:\newBook\add\ch02>java MyWebServer My Web Server is Ready HTTP Req.: Socket[addr=/ ,port=1661,localport=8000] 1:GET / HTTP/1.1 2:Accept: */* 3:Accept-Language: ko 4:Accept-Encoding: gzip, deflate 5:User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; YIE6) 6:Host: ikim.hansung.ac.kr:8000 7:Connection: Keep-Alive ② 클라이언트측 실행결과

53 [프로그래밍 연습] 앞의 예제를 변경하여, 아침에 웹 서버를 접속하면 “Hello! Good Morning!"을 출력하고 오후에 웹 서버를 접속하면 “Hello! Good Afternoon!"을 출력하며, 저녁 6시 이후에는 “Hello! Good evening!"을 출력하도록 한다. 프로그래밍 힌트 - Calendar.getInstance()를 사용하면 Calendar 객체 생성/반환한다. - java.util.Calendar 클래스의 메소드 get() 메소드를 사용하면 24시간 단위로 표시된 시각을 반환한다. int calendarObj.get(Calendar.HOUR_OF_DAY) - 사이트를 접속한 다음, Calendar를 검색 창에 입력하여 검색한다. 어떠한 생성자와 메소드가 지원되는 지 반드시 살펴보도록 한다. (자바 프로그래머가 되기 위해서는 자주 Sun사의 자바 사이트에 접속하여 원하는 클래스와 메소드의 형식을 검색하여 볼 필요가 있다.

54 3.6.5 컴퓨터시스템의 사용 중인 포트 체크 다음은 ServerSocket의 생성자를 사용하여 컴퓨터 시스템에서 사용 중인 포트 번호를 출력한다. 생성자 ServerSocket(port_number) 실행할 때, 이미 port_number가 사용 중이라면, IOException이 발생한다. 많은 포트가 사용 중이라는 것은 컴퓨터 시스템의 많은 응용 프로그램이 외부와 통신을 하고 있다는 증거이다. [프로그램 소스] import java.io.*; import java.net.*; public class PortCheck { public static void main(String[] args){ ServerSocket ss=null; for(int n=1; n<=65535; n++) try { if(n%5000==0) System.out.println("n="+n); ss=new ServerSocket(n); ss.close(); } catch(IOException e) { System.out.println(n+"번 포트가 사용중임"); }}}

55 [실행 결과] 80번 포트가 사용중임 135번 포트가 사용중임 445번 포트가 사용중임 1028번 포트가 사용중임
1050번 포트가 사용중임 1057번 포트가 사용중임 1059번 포트가 사용중임 1060번 포트가 사용중임 n=5000 8000번 포트가 사용중임 n=10000 n=15000 n=20000 n=25000 n=30000 n=35000 n=40000 n=45000 n=50000 51103번 포트가 사용중임 n=55000 n=60000 n=65000

56 3.7 채팅 프로그램 소켓 프로그램의 가장 중요한 응용 분야는 채팅 프로그램과 파일을 송수신하는 프로그램이다. 이절에서는 채팅 프로그램에 구조에 대해서 알아보기로 한다. :1 채팅 프로그램 채팅 프로그램은 기본적으로 멀티스레딩 프로그램이다. 기본적으로 두개의 스레드가 필요하다. 하나의 스레드는 사용자의 키보드 입력을 받아서 상대방에게 전송하는 일을 한다. 다른 스레드는 상대방으로부터 전송받은 메시지를 화면에 출력하는 일을 한다. 1:1 채팅 프로그램은 다음과 같이 실행한다. ① 서버와 클라이언트가 접속하여 통신 소켓을 생성한다. ② 생성된 소켓을 사용하여 데이터 입출력 스트림을 만든다. ③ 스레드를 생성하고, 상대방으로부터 받은 메시지를 화면에 출력시킨다. ④ 메인스레드는 텍스트필드에서 메시지를 입력받아 이벤트 처리(상대방에 전송)한다.

57 [실행 결과] 3.7.2 Vector 사용 멀티 캐스팅 예제 일반적인 다자간 채팅 프로그램은 하나의 멀티캐스팅 서버와 다수의 채팅 클라이언트가 필요하다. 여기서 멀티캐스팅 서버는 클라이언트에게서 받은 메시지를 모든 클라이언트에게 전송하는 일을 한다. 멀티캐스팅 서버가 접속된 모든 클라이언트의 정보를 저장하는데 사용하는 것이 바로 java.util.Vector이다. 채팅 서버를 다루기 전에 먼저 간단한 Date Server 예제 프로그램을 소개한다. 다음은 Vector를 사용하여 Date객체를 접속한 모든 클라이언트에게 전송해주는 역할을 한다. 다음 Date Server가 하는 일을 정리하면 다음과 같다. ① 서버 소켓과 벡터를 생성한다. 자식 스레드를 생성한다. ② 자식 스레드는 1초마다 벡터의 모든 클라이언트에게 Date객체 전송한다. ③ 메인 스레드는 클라이언트의 접속을 기다린다. 새롭게 접속한 클라이언트 정보를 벡터에 저장하고, 새로운 클라이언트의 접속을 기다리는 무한 루프를 실행한다.

58 [DateServer 프로그램] package p4; import java.io.*; import java.net.*;
import java.util.*; public class DateServer extends Thread { Vector<ObjectOutputStream> vec = null; public DateServer(Vector<ObjectOutputStream> vec){ this.vec = vec; } public void run(){ while(true) { Date d=new Date(); Enumeration<ObjectOutputStream> en = vec.elements(); ObjectOutputStream oos=null; try { while(en.hasMoreElements()){ oos = en.nextElement(); oos.writeObject(d); oos.flush();

59 Thread.sleep(1000); }catch(Exception e){ // System.out.println(e); // (1) // vec.removeElement(oos); // (2) } public static void main(String[] args) throws Exception { int port =7788; ServerSocket ss = new ServerSocket(port); Vector<ObjectOutputStream> vec = new Vector<ObjectOutputStream>(5); new DateServer(vec).start(); System.out.println("Server Ready"); while(true){ Socket s = ss.accept(); System.out.println("Accepted from "+s); vec.addElement( new ObjectOutputStream(s.getOutputStream())); }}

60 [DateClient 프로그램] import java.io.*; import java.net.*;
import java.util.*; public class DateClient { public static void main(String[] args)throws Exception { int port = 7788; Socket s=null; if(args.length>0) s = new Socket(args[0], port); else s = new Socket(" ",port); ObjectInputStream ois = new ObjectInputStream(s.getInputStream()); while(true){ Date d=(Date) ois.readObject(); System.out.println(d.toString()); }

61 ☞ 잘 동작하지 않는다. 통신불량으로 예외가 발생하므로, 백터의 다음 소켓으로 제어가 넘어가지 않는다.
[실행결과] ① 서버 실행 결과 Server Ready Accepted from Socket[addr=/ ,port=1870,localport=7788] ② 클라이언트 실행 결과 Sat Sep 22 00:14:58 KST 2007 Sat Sep 22 00:14:59 KST 2007 Sat Sep 22 00:15:00 KST 2007 Sat Sep 22 00:15:01 KST 2007 ...... [프로그램 실습] ① 위의 실행결과가 나오는 중간에, 클라이언트 프로그램을 강제로 중단시킨다. 그리고 클라이언트를 다시 실행시키면, 실행결과가 어떻게 되겠는가? ☞ 잘 동작하지 않는다. 통신불량으로 예외가 발생하므로, 백터의 다음 소켓으로 제어가 넘어가지 않는다. ② 서버를 중단시키고 주석 처리된 (1) 라인의 주석 표시를 삭제하고, ①번을 다시 실행하여 보시오. 잘 동작하지 않는 이유를 설명할 수 있는가? ③ 서버를 중단시키고 주석 처리된 (1), (2) 라인의 주석 표시를 삭제하고, ①번을 다시 실행하여보시오. ☞ (1),(2) 라인의 기능은 일부 클라이언트가 통신 불량에 빠졌을 때에도 서버의 기능을 계속적으로 수행하기 위한 것이다. 중단된 클라이언트의 정보를 벡터에서 삭제하므로 더 이상 Exception이 발생하지 않으므로, 정상 동작한다. ☞ 채팅참여자가 여러 명인 경우에, 한 사용자가 통신 불량에 빠질 수 있으므로, 이를 적절히 처리하여야 한다.

62 3.7.3 채팅 예제 프로그램 가장 일반적인 채팅 프로그램의 구조를 살펴보기로 한다. 다음 채팅 예제는 채팅서버(ChatS2.java)와 채팅클라이언트(ChatC2.java)로 구성된다. 채팅 서버(ChatS2)를 먼저 실행시키고, 채팅 클라이언트(ChatC2)에 서버의 IP와 대화명을 입력하면 서버와 접속된다. 채팅 서버는 클라이언트가 접속할 때마다, 스레드 객체를 생성하여 실행시킨다. 접속할 때마다 생성되는 스레드 객체(Chatter 객체)는 통신 소켓에 대한 정보를 담고 있다. 스레드 객체는 모두 벡터에 저장되어, 채팅 메시지를 broadcasting하는데 사용된다.

63 클라이언트가 서버에 접속하면, 가장 먼저 채팅에 사용할 이름을 전송한다
클라이언트가 서버에 접속하면, 가장 먼저 채팅에 사용할 이름을 전송한다. 이는 채팅 ID로 사용되므로 다른 사람과는 반드시 구분되어야 한다. ① 채팅 시작 - 채팅 서버는 "/p"+이름을 모든 클라이언트에게 전송하여 새로운 참가자를 등록하도록 한다. 서버는 관련 스레드 객체를 Vector에 저장해서, 메시지를 broadcasting 하도록 한다. ② 채팅 - 채팅 서버는 클라이언트의 메시지를 [이름]+메시지로 변경하여 모든 클라이언트에게 broadcasting 한다. 클라이언트는 이 메시지를 받아서, 텍스트영역에 그대로 출력한다. ③ 채팅 종료 - 종료 버튼을 누르면, 클라이언트는 서버에게 “/q"를 전송한다. 채팅 서버는 이를 ”/q"+이름으로 변경하여 모든 클라이언트에게 전송한다. 클라이언트 채팅참가자가 떠났음을 알리는 메시지를 출력한다. 채팅 서버는 Vector(변수명: vc)에서, 관련 스레드객체를 삭제한다.

64 [채팅 서버: ChatS2.java] import java.io.*; import java.net.*;
import java.util.*; public class ChatS2 { private ServerSocket ss; private Socket soc; private Vector<Chatter> vc = new Vector<Chatter>(); public ChatS2(){ try{ ss = new ServerSocket(12345); System.out.println("Server Ready..."); }catch(IOException ee){ System.err.println("포트가 이미 사용중"); System.exit(1); }

65 public void execute(){
while(true){ try{ soc = ss.accept(); Chatter chat = new Chatter(soc); chat.start(); System.out.println("접속자:" + chat.getNickName() + " : " + soc); Chatter user=null; for(int i = 0; i < vc.size(); i++){ user = vc.elementAt(i); user.getOut().write(("/p" + chat.getNickName() + "\n")); String message = "*** " + chat.getNickName() + "님께서 입장하셨습니다. ***"; user.getOut().write((message + "\n")); user.getOut().flush(); } vc.add(chat); chat.getOut().write(("/p" + user.getNickName() + "\n")); chat.getOut().flush(); }catch(IOException ee){ System.err.println("accept에러 : " + ee);

66 class Chatter extends Thread{
private Socket socket; private OutputStreamWriter out; private BufferedReader in; private String nickname; public Chatter(Socket s){ socket = s; try{ out = new OutputStreamWriter(socket.getOutputStream()); in = new BufferedReader(new InputStreamReader( socket.getInputStream())); nickname = in.readLine(); }catch(IOException ee){} } public void run(){ String str = null; while(true){ str = in.readLine(); if(str == null){ continue; if(str.charAt(0) == '/'){ if(str.charAt(1) == 'q'){ for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementAt(i); if(user.getNickName().equals(nickname)){ vc.removeElementAt(i); break;

67 for(int i = 0; i < vc.size(); i++){
Chatter user = vc.elementAt(i); String sss = "/q" + nickname; try{ user.getOut().write((sss + "\n")); user.getOut().flush(); }catch(IOException ee){} } break; else{ String message = "["+nickname + "] " + str; //System.out.println(i+":"+vc.size()+"mesg:"+message); user.getOut().write((message + "\n")); }catch(IOException ee){ // insert some code for fault-tolerance ① // }}} public String getNickName(){ return nickname; public OutputStreamWriter getOut(){ return out; public static void main(String[] ar){ ChatS2 cs =new ChatS2(); cs.execute();

68 [채팅 클라이언트: ChatC2.java] import java.io.*; import java.net.*;
import java.awt.*; import java.awt.event.*; class ChatC2 extends Frame implements ActionListener, Runnable{ final static long serialVersionUID= ; private Label iplb = new Label("서버 IP :", Label.RIGHT); private TextField iptf = new TextField(" "); private Label namelb = new Label("대화명 : ", Label.RIGHT); private TextField nametf = new TextField(); private TextArea viewta = new TextArea(); private Label talklb = new Label("대화 : ", Label.RIGHT); private TextField talktf = new TextField(); private Button disconnbt = new Button("끝내기"); private Socket soc; private OutputStream out; private BufferedReader in; private Thread currentTh;

69 public ChatC2(String str){
super(str); this.init(); this.start(); this.pack(); this.setVisible(true); } public void init(){ this.setLayout(new BorderLayout()); Panel p = new Panel(new BorderLayout()); //중앙부 Panel p1 = new Panel(new BorderLayout());//우측부 this.add("Center", p); this.add("East", p1); Panel p_1 = new Panel(new GridLayout(1,4)); p_1.add(iplb); p_1.add(iptf); p_1.add(namelb); p_1.add( nametf); p.add("North", p_1); viewta.setFont(new Font("Serif",Font.BOLD,14)); p.add("Center", viewta);

70 viewta.setEditable(false);
Panel p_2 = new Panel(new BorderLayout()); p_2.add("West", talklb); p_2.add("Center", talktf); p_2.add("East", disconnbt); p.add("South", p_2); } public void start(){ nametf.addActionListener(this); talktf.addActionListener(this); disconnbt.addActionListener(this); public void actionPerformed(ActionEvent e){ if(e.getSource() == nametf ) { String str = nametf.getText().trim(); if(str == null || str.length() == 0){ nametf.setText(""); nametf.requestFocus(); viewta.setText("이름 입력하세요."); return;

71 try{ InetAddress ia = InetAddress.getByName(iptf.getText()); soc = new Socket(ia, 12345); out = soc.getOutputStream(); in = new BufferedReader(new InputStreamReader(soc.getInputStream())); out.write((str + "\n").getBytes()); currentTh = new Thread(this); currentTh.start(); }catch(IOException ee){ viewta.setText("서버와 통신 불가능"); return; } if(e.getSource() == talktf ) { String message = talktf.getText().trim(); if(message == null || message.length() == 0){ talktf.setText(""); talktf.requestFocus();

72 try{ out.write((message + "\n").getBytes());
}catch(IOException ee){} talktf.setText(""); talktf.requestFocus(); } if(e.getSource() == disconnbt){ currentTh.interrupt(); try{ out.write(("/q" + "\n").getBytes()); System.exit(0); public void run(){ nametf.setEnabled(false); viewta.setText("*** 반갑습니다. ***\n"); String str = null; while(true){ str = in.readLine(); if(str == null) continue; if(str.charAt(0) == '/'){ if(str.charAt(1) == 'q'){ String name = str.substring(2); viewta.append("# " + name + "님 퇴장하셨습니다. \n"); else viewta.append(str + "\n"); public static void main(String[] ar){ new ChatC2("채팅 클라이언트");

73 [실행결과] ① 서버의 도스창 ② 채팅 클라이언트

74 3.7.4 네트워크 장애에 견디는 코드 추가 물리적으로 떨어진 사람들 간에 채팅이 이루어지면, 네트워크 연결이 불안해지는 수가 있다. 특히 채팅 참가자가 늘어나면 소켓 연결이 불안해지고 채팅 서버가 제대로 동작하지 못할 가능성이 높아진다. 이러한 네트워크 장애에 대비하기 위해서 다음 기능을 채팅 서버에 추가한다. 소켓 연결이 나빠진 채팅 참가자를 Vector에서 삭제하고, “/q+이름”을 broadcasting 한다. 그리고 연결이 나빠진 채팅 참가자를 담당했던 스레드를 중단시키는 코드를 추가한다. [프로그래밍 실습] ① 앞의 채팅 예제에서, 채팅 클라이언트를 강제로 종료시킨다. ② 그 이후 채팅이 정상적으로 이루어지는지 살펴본다. ③ 프로그램을 중단시키고, 다음 코드를 서버 코드에 추가시킨다.(① 부분) ④ 채팅 예제를 다시 실행시키고, 채팅 클라이언트를 강제로 종료시키고 장애가 발생하는지 알아본다. ⑤ 추가된 소스 코드를 분석해 본다.

75 [ChatS2.java의 ① 부분에 추가할 코드]
// added for fault tolerance System.out.println("Error in "+user.getNickName()); vc.remove(user); System.out.println(user.getNickName()+" removed" +"vc.size() ="+vc.size()); String del = "/q"+user.getNickName()+"\n"; System.out.print(del); try { for(int n=0; n<vc.size(); n++) { Chatter tmp = vc.elementAt(n); System.out.println("Nick="+tmp.getNickName()); tmp.getOut().write(del); tmp.getOut().flush(); }}catch(IOException exp){ System.out.println("another exp "+exp); } System.out.println(nickname+" : "+user.getNickName()); if(nickname.equals(user.getNickName())) return; // end code for fault tolerance

76 3.7.5 채팅자 명단 나열, 밀담 전송 기능 추가 채팅 클라이언트에 채팅자 명단과 채팅 인원을 표시하고, 원하는 사람에게만 메시지를 전송하는 밀담기능을 기능을 추가하였다. ① 채팅자 명단과 채팅인원 표시기능 추가 - 채팅 서버에 추가된 기능은 없다. 기존과 같이 채팅자가 새로 들어오면 “/p+이름”을 채팅자가 나가면 “q+이름”을 모든 클라이언트에게 전송한다. - 채팅 클라이언트에서 새로 들어온 사람의 이름을 List에 추가하고, 인원수를 표시한다. 대화를 떠난 사람을 List에서 삭제하고, 표시 인원수를 감소시킨다. ② 밀담 전송 기능 - 밀담 전송을 나타내는 Radio 버튼이 표시되었다면, "/w" + 이름 + message를 채팅 서버에 전송한다. - 채팅 서버는 “[이름(밀담)] ” + message를 1명에게만 전송한다. ③ 통신 불량 - 통신 불량 상태인 사용자가 있다면, 서버는 “/x”+ 이름을 모든 클라이언트에게 전송한다. 통신 불량상태인 사용자의 정보를 Vector에서 삭제하고, 이 사용자(통신 불량상태)의 메시지를 broadcasting 하는 스레드의 실행을 중단시킨다.

77 [채팅 서버: ChatS3.java] package p4; import java.io.*; import java.net.*;
import java.util.*; public class ChatS3 { private ServerSocket ss; private Socket soc; private Vector<Chatter> vc = new Vector<Chatter>(); public ChatS3(){ try{ ss = new ServerSocket(15001); System.out.println("Server Ready..."); }catch(IOException ee){ System.err.println("포트가 이미 사용중"); System.exit(1); }

78 public void execute(){
while(true){ try{ soc = ss.accept(); Chatter chat = new Chatter(soc); chat.start(); System.out.println("접속자:" + chat.getNickName() + " : " + soc); Chatter user=null; for(int i = 0; i < vc.size(); i++){ user = vc.elementAt(i); user.getOut().write(("/p" + chat.getNickName() + "\n")); String message = "*** " + chat.getNickName() + "님께서 입장하셨습니다. ***"; user.getOut().write((message + "\n")); user.getOut().flush(); }

79 vc.add(chat); for(int i = 0; i < vc.size(); i++){ user = vc.elementAt(i); chat.getOut().write(("/p" + user.getNickName() + "\n")); chat.getOut().flush(); } }catch(IOException ee){ System.err.println("accept에러 : " + ee); class Chatter extends Thread{ private Socket socket; private OutputStreamWriter out; private BufferedReader in; private String nickname; public Chatter(Socket s){ socket = s; try{ out = new OutputStreamWriter(socket.getOutputStream()); in = new BufferedReader(new InputStreamReader( socket.getInputStream())); nickname = in.readLine(); }catch(IOException ee){}

80 public void run(){ String str = null; while(true){ try{ str = in.readLine(); }catch(IOException ee){} if(str == null){ continue; } if(str.charAt(0) == '/'){ if(str.charAt(1) == 'w'){ String name = str.substring(2, str.indexOf(":")); String msg = str.substring(str.indexOf(":") + 1); for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementAt(i); if(user.getNickName().equals(name)){ String secret = "["+nickname+ "(밀담)] " + msg; user.getOut().write((secret +"\n")); user.getOut().flush(); out.write((secret +"\n")); out.flush();

81 else if(str.charAt(1) == 'q'){ for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementAt(i); if(user.getNickName().equals(nickname)){ vc.removeElementAt(i); break; } String sss = "/q" + nickname; try{ user.getOut().write((sss + "\n")); user.getOut().flush(); }catch(IOException ee){}

82 else{ for(int i = 0; i < vc.size(); i++){ Chatter user = vc.elementAt(i); String message = "["+nickname + "] " + str; try{ System.out.println(i+":"+vc.size()+" mesg:"+message); user.getOut().write((message + "\n")); user.getOut().flush(); }catch(IOException ee){ // added for fault tolerance System.out.println("Error in "+user.getNickName()); vc.remove(user); System.out.println(user.getNickName()+" removed" +"vc.size() ="+vc.size()); String del = "/x"+user.getNickName()+"\n"; System.out.print(del); try { for(int n=0; n<vc.size(); n++) { Chatter tmp = vc.elementAt(n); System.out.println("Nick="+tmp.getNickName()); tmp.getOut().write(del); tmp.getOut().flush(); }}catch(IOException exp){ System.out.println("another exp "+exp); }

83 System.out.println(nickname+" : "+user.getNickName());
if(nickname.equals(user.getNickName())) return; // end code for fault tolerance } public String getNickName(){ return nickname; public OutputStreamWriter getOut(){ return out; public static void main(String[] ar){ ChatS3 cs =new ChatS3(); cs.execute();

84 [채팅 클라이언트: ChatC3.java] package p4; import java.io.*;
import java.net.*; import java.awt.*; import java.awt.event.*; class ChatC3 extends Frame implements ActionListener, Runnable{ final static long serialVersionUID= ; private Label iplb = new Label("서버 IP :", Label.RIGHT); private TextField iptf = new TextField(" "); private Label namelb = new Label("대화명 : ", Label.RIGHT); private TextField nametf = new TextField(); private TextArea viewta = new TextArea(); private Label talklb = new Label("대화 : ", Label.RIGHT); private TextField talktf = new TextField(); private Label inwonlb = new Label("인원 : ", Label.RIGHT); private Label inwonlb1 = new Label("0", Label.CENTER); private Label inwonlb2 = new Label("명", Label.LEFT); private List inwonli = new List(5, false); private CheckboxGroup cg = new CheckboxGroup(); private Checkbox hidecb = new Checkbox("밀담 전송", cg, false); private Checkbox voidcb = new Checkbox("밀담 해제", cg, true); private Button disconnbt = new Button("끝내기");

85 private Socket soc=null;
private OutputStream out; private BufferedReader in; private Thread currentTh; public ChatC3(String str){ super(str); set_Screen(); nametf.addActionListener(this); talktf.addActionListener(this); disconnbt.addActionListener(this); setSize(520,300); setVisible(true); } public void set_Screen(){ this.setLayout(new BorderLayout()); Panel p = new Panel(new BorderLayout()); Panel p1 = new Panel(new BorderLayout()); this.add("Center", p); this.add("East", p1);

86 Panel p_1 = new Panel(new GridLayout(1,4));
p_1.add(iplb); p_1.add(iptf); p_1.add(namelb); p_1.add( nametf); p.add("North", p_1); viewta.setFont(new Font("Serif",Font.BOLD,14)); p.add("Center", viewta); viewta.setEditable(false); Panel p_2 = new Panel(new BorderLayout()); p_2.add("West", talklb); p_2.add("Center", talktf); p_2.add("East", disconnbt); p.add("South", p_2); Panel p1_1 = new Panel(new FlowLayout()); p1_1.add(inwonlb); p1_1.add(inwonlb1); p1_1.add(inwonlb2); p1.add("North", p1_1); p1.add("Center", inwonli); Panel p1_2 = new Panel(new BorderLayout()); Panel p1_2_1 = new Panel(new GridLayout(2, 1));

87 p1_2_1.add(hidecb); p1_2_1.add(voidcb); p1_2.add("Center", p1_2_1); Panel p1_2_2 = new Panel(new FlowLayout(FlowLayout.RIGHT)); p1_2_2.add(disconnbt); p1_2.add("South", p1_2_2); p1.add("South", p1_2); } public void actionPerformed(ActionEvent e){ if(e.getSource() == nametf){ String str = nametf.getText().trim(); if(str == null || str.length() == 0){ nametf.setText(""); nametf.requestFocus(); viewta.setText("이름을 입력하세요."); return;

88 try{ InetAddress ia = InetAddress.getByName(iptf.getText()); soc = new Socket(ia, 15001); out = soc.getOutputStream(); in = new BufferedReader(new InputStreamReader(soc.getInputStream())); out.write((str + "\n").getBytes()); currentTh = new Thread(this); currentTh.start(); }catch(IOException ee){ viewta.setText("서버와 통신 불가능"); return; } if(e.getSource() == talktf ){ String message = talktf.getText().trim(); if(message == null || message.length() == 0){ talktf.setText(""); talktf.requestFocus();

89 if(hidecb.getState() == true){
String user = (inwonli.getSelectedItem()).trim(); String mymessage = "/w" + user + ":" + message; try{ out.write((mymessage + "\n").getBytes()); }catch(IOException ee){} } else{ out.write((message + "\n").getBytes()); talktf.setText(""); talktf.requestFocus();

90 else if(e. getSource() == disconnbt){ if(soc==null) System
else if(e.getSource() == disconnbt){ if(soc==null) System.exit(0); try{ out.write(("/q" + "\n").getBytes()); }catch(IOException ee){} System.exit(0); } public void run(){ nametf.setEnabled(false); talktf.requestFocus(); viewta.setText("*** 반갑습니다. ***\n"); String str = null; while(true){ str = in.readLine(); if(str == null){ continue;

91 if(str.charAt(0) == '/'){ if(str.charAt(1) == 'p'){ String name = str.substring(2); name = name.trim(); inwonli.add(name); int xx = Integer.parseInt(inwonlb1.getText().trim()); xx++; inwonlb1.setText(String.valueOf(xx)); } else if(str.charAt(1) == 'q' || str.charAt(1)=='x'){ for(int i = 0; i < inwonli.getItemCount(); i++){ String tmp1 = inwonli.getItem(i); tmp1 = tmp1.trim(); if(tmp1.equals(name)){ inwonli.remove(i); break;

92 int xx = Integer.parseInt(inwonlb1.getText().trim());
inwonlb1.setText(String.valueOf(xx)); if(str.charAt(1)=='q') viewta.append("# " + name + "님 퇴장하셨습니다. \n"); else viewta.append("# " + name + "님 통신 불통 입니다. \n"); } else{ viewta.append(str + "\n"); public static void main(String[] ar){ new ChatC3("채팅 클라이언트");

93 [실행 결과] [프로그래밍 연습: 중복 채팅명 제거] - 다음 코드는 대화명 입력시에 기존의 채팅 참가자와 동일한 이름이 있을 경우, 적절한 조치를 취한다. 다음 코드를 적절히 입력한 다음 실행하시오.

94 1) 서버측의 Chatter 클래스 생성자 부분
public Chatter(Socket s){ socket = s; Chatter user; try{ out = new OutputStreamWriter(socket.getOutputStream()); in = new BufferedReader(new InputStreamReader( socket.getInputStream())); //nickname = in.readLine(); // Duplicated nickname check start here boolean unique = true; while(true) { System.out.println("이름 중복 체크루틴");// 4 debug nickname = in.readLine(); for(int i=0; i<vc.size(); i++) { user=vc.elementAt(i); if(nickname.equals(user.getNickName())){ System.out.println("dupldated nickname"); // 4 debug out.write(":reject\n"); out.flush(); unique=false; break; } if(unique==true) break; else unique=true; out.write(":accept\n"); // Duplicated nickname check end here }catch(IOException ee){}

95 2) 클라이언트: ActionPerformed 메소드 부분
try{ InetAddress ia = InetAddress.getByName(iptf.getText()); // duplicated nickname check start here if(soc==null) { soc = new Socket(ia, 15001); out = soc.getOutputStream(); in = new BufferedReader(new InputStreamReader (soc.getInputStream())); iptf.setEnabled(false); } out.write((name + "\n").getBytes()); String answer=in.readLine(); System.out.println("answer="+answer);// 4 debug if (answer.startsWith(":reject")) { viewta.append(name+": 이름중복: 다른이름을 사용하세요\n"); nametf.setText(""); return; // duplicated nickname check end here currentTh = new Thread(this); currentTh.start(); }catch(IOException ee){ viewta.setText("서버와 통신 불가능");

96 [실행결과] O 네트워크 틱택토(Tic-Tac-Toe) 게임 다음은 swing으로 작성한 stand-alone Tic-Tac-Toe 게임이다. Tic Tac Toe는 3 x 3 크기의 보드에 O과 X를 번갈아 가면서 두어 어느쪽이든 3개의 돌이 직선이 되면 이기는 게임이다. 우리나라의 오목과 매우 유사하나, 돌을 놓은 위치가 다르고 보드의 크기가 매우 작은 점이 특징이다.

97 [예제 프로그램] package p5.sub1; import java.awt.Color;
import java.awt.Graphics; import java.awt.GridLayout; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.*; import javax.swing.border.LineBorder; @SuppressWarnings("serial") public class Cell extends JPanel implements MouseListener { int row, column; char token = 'B'; static char myToken='O'; public Cell(int r, int c){ row = r; column = c; setBorder(new LineBorder(Color.black,1)); addMouseListener(this); }

98 protected void paintComponent(Graphics g){
super.paintComponent(g); if (token == 'X') { g.setColor(Color.red); g.fillOval(10, 10, getWidth() - 20, getHeight() - 20); g.setColor(Color.black); } else if (token == 'O'){ g.setColor(Color.green); public static void main(String args[]){ final int ROW=3, COL=3; Cell[][] cell = new Cell[ROW][COL]; JFrame fr = new JFrame("Tic Tac Toe Client"); fr.setSize(320+(ROW-3)*50,300+(COL-3)*50); JPanel p1 = new JPanel(); p1.setLayout(new GridLayout(ROW,COL)); for(int x=0; x<ROW; x++) for(int y=0; y<COL; y++){ p1.add(cell[x][y]=new Cell(x,y)); fr.add(p1,"Center"); fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); fr.setVisible(true);

99 public void mouseClicked(MouseEvent me) {
if(token=='B') { token=myToken; if(myToken=='O') myToken='X'; else myToken='O'; repaint(); } @Override public void mouseEntered(MouseEvent arg0){} public void mouseExited(MouseEvent arg0){} public void mousePressed(MouseEvent arg0){} public void mouseReleased(MouseEvent arg0){}

100 [실행결과] p5.sub2의 TestWon.java는 두 플레이어중에서 어느 선수가 이겼는지를 판별하는 프로그램이다. 특히 틱-택-토우가 3X3의 좁은 보드였는데, 이를 m * n 사이즈로 확장하였다. TestWon.java 프로그램에는 세 개의 정적 상수가 있는데 COL은 보드 컬럼의 수를, ROW는 보드 줄수를, WIN_LENGTH는 승리에 필요한 길이를 나타낸다. ROW, COL, WIN_LENGTH를 변경함으로서, 게임 속성을 변경할 수 있다.

101 p5.sub2 패키지와 p5.sub3 패키지에는 TicTacToe 클라이언트와 서버 프로그램이 작성되어 있으므로, 실행하여 보기 바란다. (서버를 먼저 실행시킨 후,클라이언트는 2개를 실행한다.) p5.sub3 패키지의 TicTacServer.java와 TicTacClient.java를 실행한 결과는 다음과 같다. 보드의 크기를 크게하여, 오목과 같이 5개의 돌이 직선을 이루면 승리하게 된다.

102 1) Tic-Tac-Toe 서버의 기능 소켓 프로그래밍을 위해서는 클라이언트와 서버간에 명확한 동작 시나리오를 정의하는 것이 필수적이다. ① 클라이언트의 접속을 기다림 첫 번째 접속한 클라이언트에게 PLAYER1(정수값)을 전송하고, 두 번째 접속한 클라이언트에게는 PLAYER2(정수값)을 전송함 - 보드 상태 저장 배영 cell[][] 초기화 ② C1에게 플레이 시작을 명함: out1.writeInt(CONTINUE) ③ C1 플레이 좌표 정보 입수 분석 - C1이 승리하였는지 체크, 승리시 메시지 전송 - 돌을 더 놓을수 없게 되었다면, 무승부 전송 - 앞의 두상태가 아니면, C2에게 플레이를 명함. ④ C2 플레이 좌표 정보 입수 분석 - C2이 승리하였는지 체크, 승리시 메시지 전송 - 앞의 두상태가 아니면, C1에게 플레이를 명함. - ③으로 되돌아감

103 2) 클라이언트의 기능 ① m*n개의 셀(각 셀은 JPanel)로 구성된 보드를 그림 ② 각 셀은 마우스 이벤트 처리를 위해, MouseListener에 등록 ③ 서버와 접속 후, 서버로부터 플레이어 번호(player변수에 저장됨)를 받음. 플레이어 번호는 자신이 첫 번째 플레이어(‘X')인지, 두 번째 플레이어('O')인지 구분함. ☞ 여기서 player 변수값에 따라 PLAYER1이면 ④를 반복 실행. 아니면 ⑤를 반복 실행한다. ④ PLAYER1은 ④를 반복 실행한다. - player 변수값이 PLAYER1이면(첫번째 플레이어 인 경우) 자신이 둘차례이다.(소스: p5.sub3.TicTacClient.java의 86번 라인) - waiting 변수 false가 될 때까지 기다렸다가, (rowSel, colSel)값을 서버에 전송한다. rowSel, colSel의 값은 사용자가 클릭한 cell에 대한 정보이다. - waiting 변수값은 보드를 클릭하여 움직임을 표시하여야 true로 변환된다. 이 변환부분은 mouseClicked(MouseEvent e) 메소드에 명시되어 있다.(동일 소스: 165번 라인) - 서버로부터 플레이 상태 정보를 얻는다. P1이 승리/ P2가 승리하였다/ 비겼다(draw)의 메시지가 수신되면 적절한 메시지를 출력하고 continuePlay를 false로 설정한다. continuePlay가 false로 설정되면, 더 이상 서버로 메시지를 전송하지 않는다. - 만일 게임이 종료되지 않았으면, 상대방 움직임 정보를 수신하여 화면에 표시한다.

104 ⑤ PLAYER2는 ⑤를 반복 실행한다. - 서버로부터 플레이 상태 정보를 얻는다. P1이 승리/ P2가 승리하였다/ 비겼다(draw)의 메시지가 수신되면 적절한 메시지를 출력하고 continuePlay를 false로 설정한다. continuePlay가 false로 설정되면, 더 이상 서버로 메시지를 전송하지 않는다. - 만일 게임이 종료되지 않았으면, 상대방 움직임 정보를 수신하여 화면에 표시한다. - waiting 변수 false가 될 때까지 기다렸다가, (rowSel, colSel)값을 서버에 전송한다. rowSel, colSel의 값은 사용자가 클릭한 cell에 대한 정보이다. - waiting 변수값은 보드를 클릭하여 움직임을 표시하여야 true로 변환된다. ※ 네트워크 프로그램은 기존의 stand-alone 프로그램에 비해 프로그래밍하기가 매우 어렵다. 특히 오류를 발견하고, 수정하는 디버깅은 더욱 어렵다. 많은 프로그래밍 경험이 필요하다.

105 [프로그래밍 연습] - Tic-Tac-Toe 프로그램에 다음 기능을 추가하여 구현해보시오. ① 시간 제한 기능 ② 복기 기능
③ 관전자 기능, 채팅 기능 ④ 무르기 기능 ⑤ 기타 부족한 기능 ※ 소스코드의 Chap3 디렉토리의 connect4.v4 패키지의 Conn4.java 프로그램은 Connect four게임을 stand-alone 형태로 작성한 것이다. 이를 네트워크 플레이가 가능하도록 변경해 보시오.


Download ppt "3장 소켓 프로그래밍 3.1 IP 주소와 Port DNS"

Similar presentations


Ads by Google