19.04.22 IO기반 입출력 및 네트워킹-2

Back-End/Java 2019. 4. 22. 18:00
728x90
반응형



-보조 스트림-


다른 스트림과 연결되어 여러 가지 편리한 기능을 제공해주는 스트림을 말한다.
보조 스트림을 필터(filter) 스트림이라고도 하는데, 이는 보조 스트림의 일부가 FilterInputStream,
FilterOutputStream의 하위 클래스이기 때문이다.

보조 스트림은 문자 변환, 입출력 성능 향상, 기본 데이터 타입 입출력,
객체 입출력 등의 기능을 제공한다.

보조 스트림을 생성할 때에는 자신이 연결될 스트림을 다음과 같이 생성자의 매개값으로 받는다.

보조스트림 변수 = new 보조스트림(연결스트림)




-문자 변환 보조 스트림-


소스 스트림이 바이트 기반 스트림(InputStream, OutputStream  등) 이면서 입출력 데이터가 문자라면 

Reader과 Writer로 변환해서 사용하는 것을 고려해야한다.




-InputStreamReader-


바이트 입력 스트림에 연결되어 문자 스트림인 Reader로 변환시키는 보조 스트림이다.


Reader reader = new InputStreamReader(바이트입력스트림);




-예제 및 출력결과-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hs.chap18;
 
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
 
public class InputStreamReadeExample {
    public static void main(String[] args) throws Exception {
        InputStream is = System.in// 시스템으로 부터 입력을 받음
        Reader reader = new InputStreamReader(is); // 입력받은 값을 InputStreamReader메소드를 사용해 문자입력 스트림인 Reader로 변환
 
        int readCharNo;
        char[] cbuf = new char[100]; // cbuf에 100개의 공간을 만듦
        while ((readCharNo = reader.read(cbuf)) != -1// 아까 선언한 readCharNo의 값이 -1과 같지 않으면 (출력할 값이 남아있으면) 문자를 계속 출력
        {
            String data = new String(cbuf, 0, readCharNo);
            System.out.println(data);
        }
 
        reader.close();
    }
 
}
 
cs



-OutputStreamWriter-


바이트 출력 스트림에 연결되어 문자 출력 스트림인 Writer로 변환시키는 보조 스트림이다.

Writer writer = new OutStreamWriter(바이트출력스트림);




-성능 향상 보조 스트림-


프로그램의 실행 입출력이 가장 늦은 장치를 따라간다.
CPU와 메모리가 아무리 뛰어나도 하드디스크의 입출력이 늦어지면 프로그램의 실행 성능은 하드디스크의
처리 속도에 맞춰진다.
프로그램이 입출력 소스와 직접 작업하지 않고 중간에 메모리 버퍼(buffer)와 작업함으로써 실행 성능을
향상시킬 수 있다.




-BufferdInputStream과 BufferedReader-


BufferedInputStream는 바이트 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이고,

BufferedReader는 문자 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다.


BufferedInputStream bis = new BufferedInputStrem(바이트입력스트림);


BufferedReader br = new BufferedReader(문자입력스트림);



-예제 및 출력결과-


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.hs.chap18;
 
import java.io.BufferedInputStream;
import java.io.FileInputStream;
 
public class BufferdInputStreamExample {
 
    public static void main(String[] args) throws Exception {
        long start = 0;
        long end = 0;
 
        FileInputStream fis1 = new FileInputStream("C:/forest.jpg");
        start = System.currentTimeMillis(); // fis1을 실행하는데 걸리는 시간 측정 (시간참조 메소드를 사용)
        while (fis1.read() != -1) {
        } // 더이상 읽을값이 없을때까지 출력 (-1이 될때까지)
        end = System.currentTimeMillis(); // 끝나는 시간 측정
        System.out.println("사용하지 않았을 때: " + (end - start) + "ms"); // 끝난시간에서 시작시간을 빼서 총 걸린 시간 측정.
        fis1.close(); // 파일출력이 끝났으므로 종료한다.
 
        FileInputStream fis2 = new FileInputStream("C:/forest.jpg");
        BufferedInputStream bis = new BufferedInputStream(fis2);
        start = System.currentTimeMillis();
        while (bis.read() != -1) {
        }
        end = System.currentTimeMillis();
        System.out.println("사용했을 때: " + (end - start) + "ms");
        bis.close();
        fis2.close();
    }
}
 
cs




-BufferedOutputStream과 BufferedWriter-


BufferedOutputStream은 바이트 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이고,

BufferedWriter은 문자 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림이다.


BufferedOutputStream bos = new BufferedOutputStream(바이트출력스트림);


BufferedWriter bw = new Buffered(문자출력스트림);




-기본 타입 입출력 보조 스트림-


바이트 스트림은 바이트 단위로 입출력하기 때문에 자바의 기본 데이터 타입인 boolean, char,
short, int, long, float, double 단위로 입출력할 수 없다.
이 메소스들로 입출력할 때는 데이터 타입의 크기가 모두 다르므로 DataOutputStream으로 출력한 데이터를
다시 DataInputStream으로 읽어올 때는 출력한 순서와 동일한 순서로 읽어야 한다.
예를 들어 출력할 때의 순서와 int -> boolean -> double 이라면 읽을 때의 순서도
int -> boolean -> double 이어야 한다.



-예제 및 출력결과-


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.hs.chap18;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
 
public class DataInputOutputStreamExample {
 
    public static void main(String[] args) throws Exception {
        FileOutputStream fos = new FileOutputStream("C:/forest.txt"); // fos에 C:/forest.txt을 출력하는 객체를 만든다.
        DataOutputStream dos = new DataOutputStream(fos); // dos에 fos의 데이터를 출력하는 객체를 만든다.
 
        dos.writeUTF("홍길동"); // write메소드를 사용해 txt에 값을 입력
        dos.writeDouble(5); // write메소드를 사용해 txt에 값을 입력
        dos.writeInt(1); // write메소드를 사용해 txt에 값을 입력
 
        dos.writeUTF("감자바"); // write메소드를 사용해 txt에 값을 입력
        dos.writeDouble(90.3); // write메소드를 사용해 txt에 값을 입력
        dos.writeInt(2); // write메소드를 사용해 txt에 값을 입력
 
        dos.flush();
        dos.close(); // flush()메소드를 사용해 문자가 다 출력되면 잔여값이 남기지 않도록 한다.
                        // close()모든 출력이 종료되면 종료시킨다.
 
        FileInputStream fis = new FileInputStream("C://forest.txt");
        DataInputStream dis = new DataInputStream(fis);
 
        for (int i = 0; i < 2; i++) {
            String name = dis.readUTF();
            double score = dis.readDouble();
            int order = dis.readInt();
            System.out.println(name + " : " + score + " : " + order);
        }
 
        dis.close();
        fis.close();
    }
}
 
cs



-프린트 보조 스트림-


PrintStrem과 PrintWriter은 프린터와 유사하게 출력하는 print(), println()메소드를 가지고 있는
보조 스트림이다.
콘솔 출력 스트림인 System.out이 바로 PrintStream 타입이기 때문에 print(), println() 메소드를
사용할 수 있었다.
다른 보조 스트림과 마찬가지로 연결할 출력 스트림을 생성자의 매개값으로 받는다.


PrintStream ps = new PrintStream(바이트출력스트림);


PrintWriter pw = new PrintWriter(문자출력스트림);



-예제 및 출력결과-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.hs.chap18;
 
import java.io.FileOutputStream;
import java.io.PrintStream;
 
public class PrintStreamExample {
    public static void main(String[] args) throws Exception {
        FileOutputStream fos = new FileOutputStream("C:/forest.txt");
        PrintStream ps = new PrintStream(fos); // 출력 스트림의 변수를 매개값으로 받아옴
 
        ps.println("[프린터 보조 스트림]");
        ps.print("마치");
        ps.println("프린트가 출력하는 것처럼");
        ps.println("데이터를 출력합니다.");
 
        System.out.println();
 
        ps.flush();
        ps.close();
        fos.close();
 
    }
}
 
cs





-printf 메소드-


첫 번째 매개값으로 형식화된 문자열을 지정하고, 두 번째 매개값부터 형식화된 문자열에 들어갈 값을 나열해주면 된다.





-예제 및 출력결과-


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.hs.chap18;
 
import java.util.Date;
 
public class PrintfExample {
 
    public static void main(String[] args) {
        System.out.printf("상품의 가격 %d원/n"123);
        System.out.printf("상품의 가격 %6d원/n"123);
        System.out.printf("상품의 가격 %-6d원/n"123);
        System.out.printf("상품의 가격 %06d원/n"123);
 
        System.out.printf("반지름이 %d인 원의 넓이:%10.2f/n"10, Math.PI * 10 * 10);
 
        System.out.printf("%6d | %-10s | %10s/n"1"홍길동""도적");
 
        Date now = new Date();
        System.out.printf("오늘은 %tY년 %tm월 %td일 입니다/n", now, now, now);
    }
}
 






-객체 입출력 보조 스트림-


객체는 문자가 아니기 때문에 바이트 기반 스트림으로 출력해야 한다.
객체를 출력하기 위해서는 객체의 데이터(필드값)를 일렬로 늘어선 연속적인 바이트로 변경해야 하는데,
이것을 객체 직렬화(serialization)라고 한다.
입력 스트림으로부터 읽어들인 연속적인 바이트를 객체로 복원하는 것을 역직렬화라고 한다.




-ObjectInputStream, ObjectOutputStream-


ObjectInputStream는 바이트 출력 스트림과 연결되어 객체를 직렬화 하는 역할을 하고,
ObjectOutputStream는 바이트 입력 스트림과 연결되어 객체로 역직렬화 하는 역할을 한다.

두가지 모두다 다른 보조 스트림과 마찬가지로
연결할 바이트 입출력 스트림을 생성자의 매개값으로 받는다.


ObjectInputStream ois = new ObjectInputStream(바이트입력스트림);


ObjectInputStream oos = new ObjectOutputStream(바이트출력스트림);


ObjectOutputStream으로 객체를 직렬화하기 위해서는 writeObject()메소드를 사용한다.


oos.writeObject(객체);


반대로 ObjectInputStream의 readObject() 메소드는 입력 스트림에서 읽은 바이트를 역직렬화
해서 객체로 생성한다. readObject() 메소드의 리턴 타입은 Object 타입이기 때문에 객체 원래의
타입으로 변환해야 한다.


객체타입 변수 = (객체타입) ois.readObject();



-예제 및 출력결과-


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.hs.chap18;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
 
public class ObjectInputOutputStreamExample {
 
    public static void main(String[] args) throws Exception {
        FileOutputStream fos = new FileOutputStream("C:/forest.txt"); // 파일출력스트림 txt를 fos에 저장
        ObjectOutputStream oos = new ObjectOutputStream(fos); // fos의 객체를 받아 객체출력스트림을 oos에 저장
 
        oos.writeObject(new Integer(10)); // 새로운 객체를 만들어서 oos에 저장
        oos.writeObject(new Double(3.14));
        oos.writeObject(new int[] { 1, 2, 3 });
        oos.writeObject(new String("홍길동"));
 
        oos.flush(); // 남은(필요없는부분)이 없도록 출력
        oos.close();
        fos.close();
 
        FileInputStream fis = new FileInputStream("C:/forest.txt"); // 파일입력스트림 txt를 fis에 저장
        ObjectInputStream ois = new ObjectInputStream(fis);
 
        Integer obj1 = (Integer) ois.readObject(); // ois의 객체를 읽어서 Integer타입으로 타입변환해 obj1에 저장
        Double obj2 = (Double) ois.readObject(); // ois의 객체를 읽어서 Double타입으로 타입변환해 obj2에 저장
        int[] obj3 = (int[]) ois.readObject(); // ois의 객체를 읽어서 Double타입으로 타입변환해 obj3에 저장
        String obj4 = (String) ois.readObject(); // ois의 객체를 읽어서 Double타입으로 타입변환해 obj4에 저장
 
        ois.close();
        fis.close();
 
        System.out.println(obj1);
        System.out.println(obj2);
        System.out.println(obj3[0] + "," + obj3[1] + "," + obj3[2]);
        System.out.println(obj4);
 
    }
}
 
cs



s






-직렬화가 가능한 클래스(Serializable)-


자바가 Seralizable 인터페이스를 구현한 클래스만 직렬화할 수 있도록 제한하고 있다.
Serializable 인터페이스는 필드나 메소드가 없는 빈 인터페이스이지만, 객체를 직렬화할 때
private 필드를 포함한 모든 필드를 바이트로 변환해도 좋다는 표시 역할을 한다.


public class XXX implements Serializable { }


또한 직렬화는 필드만 해당하고, 메소드나 생성자는 해당되지 않는다.
하지만 필드선언에 static 또는 transient가 붙어 있을 경우에는 직렬화가 되지 않는다.



-예제 및 출력결과-


1
2
3
4
5
6
7
8
9
10
11
12
package com.hs.chap18;
 
import java.io.Serializable;
 
public class ClassA implements Serializable { // 필드를 직렬화 하기 위해서 Serializable 인터페이스 구현
    int field1;
    ClassB field2 = new ClassB();
    int field3; // static와 transient는 직렬화에서 제외됨
    int field4;
 
}
 
cs

1
2
3
4
5
6
7
8
package com.hs.chap18;
 
import java.io.Serializable;
 
public class ClassB implements Serializable { // 필드를 직렬화 하기 위해서 Serializable 인터페이스 구현
    int field1;
}
 
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.hs.chap18;
 
import java.io.FileInputStream;
import java.io.ObjectInputStream;
 
public class SerializableReader {
 
    public static void main(String[] args) throws Exception {
        FileInputStream fis = new FileInputStream("C:/forest.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        ClassA v = (ClassA) ois.readObject(); // ois로 읽은 객체를 classA타입으로 타입변환해 v에 저장
        System.out.println("field1: " + v.field1);
        System.out.println("field2.field1: " + v.field2.field1);
        System.out.println("field3: " + v.field3);
        System.out.println("field4: " + v.field4);
    }
}
 
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.hs.chap18;
 
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
 
public class SerializableWriter {
    public static void main(String[] args) throws Exception {
        FileOutputStream fos = new FileOutputStream("C:/forest.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
 
        ClassA classA = new ClassA(); // classA의 객체 생성
        classA.field1 = 1;
        classA.field2.field1 = 2;
        classA.field3 = 3;
        classA.field4 = 4;
        oos.writeObject(classA); // 직렬화해서 파일에 저장
        oos.flush();
        oos.close();
        fos.close();
    }
}
 
cs




-serialVersion UID 필드-

직렬화된 객체를 역직렬화할 때는 직렬화했을 때와 같은 클래스를 사용해야한다.
예외의 내용은 직렬화할 때와 역직렬화할 때 사용된 클래스의 serialVersionUID가 다르다는 것이다.
클래스의 serialVersionUID가 명시적으로 선언되어 있으면 컴파일 시에 serialVersionUID 필드가
추가되지 않기 때문에 동일한 serialVersionUID 값을 유지할 수 있다.



-writeObject()와 readObject() 메소드-

두 클래스가 상속 관계에 있을때 부모 클래스가 Serializable 인터페이스를 구현하고 있으면
자식 클래스는 Serializabled 인터페이스를 구현하지 않아도 자식 객체를 직렬화 하면
부모 필드 및 자식 필드가 모두 직렬화 된다.
하지만 부모 클래스는 그렇지 않다.
부모 클래스의 필드를 직렬화 하고 싶다면 2가지 방법이 있다.
주의할 점은 접근 제한자가 private가 아니면 자동 호출되지 않기 때문에 반드시 private를 붙여주어야 한다.

1. 부모 클래스가 Serializable 인터페이스를 구현하도록 한다.
2. 자식 클래스에서 writeObject()와 readObject() 메소드를 선언해서 부모 객체의 필드를 직접 출력시킨다.

private void writeObject(ObjectOutputStream out) throws IOException {
out.writeXXX(부모필드);   -> 부모 객체의 필드값을 출력함
 .
 .
 .
 .
out.defaultWriteObject();  ->자식 객체의 필드값을 직렬화
}

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
부모필드 = in.readXXX();   -> 부모 객체의 필드값을 읽어옴
 .
 .
 .
 .
in.defaultReadObject();  ->자식 객체의 필드값을 역직렬화
}



-예제 및 출력 결과-

1
2
3
4
5
6
package com.hs.chap18;
 
public class Parent {
    public String field1;
}
 
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.hs.chap18;
 
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class Child extends Parent implements Serializable { // 부모 클래스를 상속받고 Serializable 인터페이스를 구현한다.
    public String field2;
 
    private void writeObject(ObjectOutputStream outthrows IOException {
        out.writeUTF(field1); // 부모 객체의 필드 값을 출력한다.
        out.defaultWriteObject();
    }
 
    private void readObject(ObjectInputStream inthrows IOException, ClassNotFoundException {
        field1 = in.readUTF(); // 부모 객체의 필드값을 읽어옴
        in.defaultReadObject();
    }
 
}
 
cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.hs.chap18;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
 
public class NonSerializableParentExample {
    public static void main(String[] args) throws Exception{
        FileOutputStream fos = new FileOutputStream("C:/Temp/Object.dat");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Child child = new Child();
        child.field1 = "홍길동";
        child.field2 = "홍삼원";
        oos.writeObject(child);
        oos.flush();
        oos.close();
        fos.close();
        
        FileInputStream fis = new FileInputStream("C:/Temp/Object.dat");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Child v = (Child) ois.readObject();
        System.out.println("field1: "+v.field1);
        System.out.println("field2: "+v.field2);
        ois.close();
        fis.close();
 
    }
 
}
 
cs




-네트워크 기초-


네트워크는 여러 대의 컴퓨터를 통신 회선으로 연결한 것을 말한다.
인터넷은 지역 네트워크를 통신 회선으로 연결한 것을 말한다.




-서버와 클라이언트-


서비스를 제공하는 프로그램을 일반적으로 서버(server)이라고 부르고,
서비스를 받는 프로그램을 클라이언트 (client)라고 부른다.
클라이언트는 서비스를 받기 위해 연결을 요청하고, 서버는 연결을 수락하여 서비스를 제공한다.




-InetAddress로 IP주소 얻기-


InetAddress.getLocalHost() 메소드 사용.


InetAddress ia = InetAddress.getLocalHost();


외부 컴퓨터의 도메인 이름을 알고 있다면 다음 두 개의 메소드를 사용하여 InetAddress 객체를
얻으면 된다.


InetAddress ia = InetAddress.getByName(String host);
InetAddress[ ] iaArr = InetAddress.getAllByName(String host);




-TCP 네트워킹-

TCP는 연결 지향적 프로토콜이다.
연결 지향 프로토콜이란, 클라이언트와 서버가 연결된 상태에서 데이터를 주고받는 프로토콜을 말한다.
TCP의 단점은 데이터를 보내기 전에 반드시 연결이 형성되어야 하고, 고정된 통신 선로가 최단선이
아닐 경우 상대적으로 UDP보다 데이터 전송 속도가 느릴 수 있다.



-TCP 서버의 역할-

1. 클라이언트가 연결 요청을 해오면 연결을 수락하는 것. (java.net.ServerSocket 클래스)
2. 연결된 클라이언트와 통신하는 것. (java.net.Socket 클래스)



-ServerSocket와 Socket의 용도-


서버를 개발하려면 우선ServerSocket 객체를 얻어야 한다.
가장 간단한 방법은 생성자에 바인딩 포트를 대입하고 객체를 생성하는 것이다.
다음은 5001번 포트에 바인딩하는 ServerSocket를 생성한다.


ServerSocket serverSocket = new ServerSocket(5001);


다른 방법은 디폴트 생성자로 객체를 생성하고 포트 바인딩을 위해 bind() 메소드를 호출하는것이다.
bind() 메소드의 매개값은 포트 정보를 가진 InetSocketAddress 이다.


ServerSocket serverSocket = new ServerSocket();

serverSocket.bind(new InetSocketAddress(5001));


포트 바인딩이 끝났다면 ServerSocket는 클라이언트 연결 수락을 위해 accept() 메소드를 실행해야 한다.
accept() 메소드는 클라이언트가 연결 요청하기 전까지 블로킹 되는데, 블로킹이란 스레드가 대기 상태가
된다는 뜻이다.


연결된 클라이언트의 IP와 포트 정보를 알고 싶다면 Socket의 getRemoteSocketAddress() 메소드를
호출해서 SocketAddress를 얻으면 된다.


실제 리턴되는 것은 InetSockAddress 객체 이므로 타입 변환할 수 있다.


InetSocketAddress socketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();



-예제 및 출력 결과-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.hs.chap18;
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
 
public class ServerExample {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(); // 서버소켓 객체를 생성
            serverSocket.bind(new InetSocketAddress("localhost"5001)); // bind 메소드 호출해서 로컬호스트 5001번포트에 바인딩하는 서버소켓을
                                                                            // 생성한다.
            while (true) {
                System.out.println("[연결 기다림]");
                Socket socket = serverSocket.accept(); // 서버소켓의 연결 수락을 위해 accept()메소드 실행
                InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();// 소켓의
                                                                                            // getRemoteSocketAddress()메소드를
                                                                                            // 호출해서 소켓주소를 얻은후
                System.out.println("[연결 수락함]" + isa.getHostName()); // InetSocketAddress 타입으로 타입변환을 한후 isa에 저장한다.
 
            }
 
        } catch (Exception e) {
        }
 
        if (!serverSocket.isClosed()) {
            try {
                serverSocket.close();
            } catch (IOException e1) {
            }
 
        }
 
    }
}
 
cs

 

 
 

 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.hs.chap18;
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
 
public class ClientExample {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            socket = new Socket(); // 소켓을 생성
            System.out.println("[연결 요청]");
            socket.connect(new InetSocketAddress("localhost"5001)); //커넥트 메소드로 5001포트로 연결
            System.out.println("[연결 성공]");
        } catch (Exception e) {
        }
 
        if (!socket.isClosed()) { //소켓이 닫히지 않으면 예외처리 (연결이 되어있을 경우)
            try {
                socket.close();
            } catch (IOException e1) {
            }
 
        }
 
    }
 
}
 
cs

 

 

 

 

-Socket 데이터 통신-

 

클라이언트가 연결 요청(connect()) 하고 서버가 연결 수락(accept()) 했다면, 양쪽의 Socket
객체로부터 각각 입력 스트림(InputStream)과 출력 스트림(OutputStream)을 얻을 수 있다.

다음은 Socket으로 부터 InputStream과 OutputStream을 얻는 코드이다.

 

 

//입력 스트림 얻기


InputStream is = socket.getInputStream();

 


//출력 스트림 얻기


OutputStream os = socket.getOutputStream();

 

 

 

-상대방에게 데이터 보내기-

 

String data = "보낼 데이터";
byte[ ] byteArr = data.getBytes("UTF-8");
OutputStream outputStream = socket.getOutputStream();
outputStream.write(byteArr);
outputStream.flush();

 

 

 

-상대방에게 데이터 받기-

 

byte[ ] byteArr = new byte[100];
InputStream inputStream = socket.getInputStream();
int readByteCount = inputStream.read(byteArr);
String data = new String(byteArr,0,readByteCount,"UTF-8");

 

 

 

-스레드 병렬 처리-

 

연결 수락을 위해 ServerSocket의 accept()를 실행하거나, 서버 연결 요청을 위해 Socket 생성자 또는
connect()를 실행할 경우에는 해당 작업이 완료되기 전까지 블로킹 된다.
결론적으로 ServerSocket과 Socket는 동기방식으로 구동된다.
만약 서버를 실행시키는 main 스레드가 직접 입출력 작업을 담당하게 되면 입출력이 완료될 때까지
다른 작업을 할 수 없는 상태가 된다.
그렇기 때문에 accept(), connect(), read(), write()는 별도의 작업 스레드를 생성해서 병렬적으로
처리하는 것이 좋다.
그렇기 때문에 과도한 스레드 생성을 방지하려면 스레드풀을 사용하는 것이 바람직하다.

 

 

 

-서버 클래스 구조-


1.startServer() 메소드

 

start 버튼을 클릭하면 실행하는 메소드.
ExecutorService 생성, ServerSocket 생성 및 포트 바인딩, 연결 수락 코드가 있다.


2.stopServer() 메소드

 

stop 버튼을 클릭하면 stopServer() 메소드가 실행되는데, stopServer() 메소드에는 연결된
모든 Socket 닫기, ServerSocket 닫기, ExecutorService 종료 코드가 있다.


 

 


-클라이언트 클래스 구조-


1.startCilent() 메소드

 

start 버튼을 클릭하면 실행되는 메소드.
startClient() 메소드에는 Socket 생성 및 연결 요청 코드가 있다.

 


2.stopClient() 메소드

 

stop 버튼을 클릭하거나 서버 통신이 안 될 경우 실행되는 메소드.
stopClient() 메소드에는 Socket를 닫는 close() 메소드 호출 코드가 있다.

 


3.receive() 메소드

 

receive() 메소드는 서버에서 보낸 데이터를 받는 역할을 한다.
이 메소드는 startClient()에서 생성한 작업 스레드상에서 호출된다.

 


4.send(String data) 메소드

 

send(String data) 메소드는 사용자가 메시지를 입력하고 send 버튼을 클릭하면 메시지를 매개값으로
호출한다. send() 메소드는 서버로 메시지를 보내는 역할을 한다.

 

 

 

-UDP 네트워킹-

 

UDP는 비연결 지향적 프로토콜이다. 비연결 지향적이란?
데이터를 주고받을 때 연결 절차를 거치지 않고, 발신자가 일방적으로 데이터를 발신하는 방식이다.
연결 과정이 없기 때문에 TCP 보다는 빠른 전송을 할 수 있지만 데이터 전달의 신뢰성은 떨어진다.

 

 

 

-예제 및 출력결과-

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.hs.chap18;
 
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
 
public class UdpSendExample {
    public static void main(String[] args) throws Exception {
        DatagramSocket datagramSocket = new DatagramSocket(); // DatagramSocket객체를 생성
 
        System.out.println("[발신 시작]");
 
        for (int i = 1; i < 3; i++) {
            String data = "메시지" + i; // 2번 보냄
            byte[] byteArr = data.getBytes("UTF-8"); // 문자열을 UTF-8로 인코딩하고, 배열을 얻는다.
            DatagramPacket packet = new DatagramPacket(byteArr, byteArr.lengthnew InetSocketAddress("localhost"5001)
            // Datagram Packet 생성
            );
            datagramSocket.send(packet); // 패킷을 전송한다.
            System.out.println("[보낸 바이트 수]: " + byteArr.length + "bytes");
 
        }
        System.out.println("[발신 종료]");
 
        datagramSocket.close();
 
    }
 
}
 
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.hs.chap18;
 
import java.net.DatagramPacket;
import java.net.DatagramSocket;
 
public class UdpReceiveExample extends Thread {
 
    public static void main(String[] args) throws Exception {
        DatagramSocket datagramSocket = new DatagramSocket(5001); //5001번 포트에서 수신하는 DatagramSocket 객체 생성
 
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("[수신 시작]");
                try {
                    while (true) {
                        DatagramPacket packet = new DatagramPacket(new byte[100], 100); //Datagram Packet를 담을 배열을 만든다.
                        datagramSocket.receive(packet);
 
                        String data = new String(packet.getData(), 0, packet.getLength(), "UTF-8");
                        System.out.println("[받은 내용: " + packet.getSocketAddress() + "]" + data);
                    }
                } catch (Exception e) {
                    System.out.println("[수신 종료]");
                }
            }
        };
        thread.start();
 
        Thread.sleep(10000);
        datagramSocket.close();
 
    }
}
 
cs

 

 

728x90
반응형
: