2022-01-26 IO / Network

GGAE99·2022년 1월 28일
0

진도

목록 보기
14/43

너무너무너무 오래 쉬었다. 아무도 봐줄 사람은 없겠지만 변명해보자면 놀다왔다. 변명이라고 보기는 좀 그렇다. 그냥 놀다가 쉬다가왔다. 데스크탑을 만질 기회는 있었지만, 글을 작성할 시간은 없었고, 프로그래밍 공부도 땃쥐 손톱만큼 했다. 그래서 오늘 땃쥐 손톱 + α 의 내용을 작성한다.

IO

IO는 input과 output을 말한다. 컴퓨터 내부 또는 외부 장치와 프로그램 간의 데이터를 주고 받는 것을 뜻한다. 읽어들이고, 내보내고 하는 것 이다.

이 input과 output을 하기위해서는 stream이라는 통로가 필요하다.
일단 입출력의 절차를 설명하고 스트림에 대해 자세하게 설명하겠다.

입출력 절차
1. 외부 자원과 스트림 연결
-> 외부 자원과의 데이터 이동 통로를 생성(이게 스트림)
-> 스트림 클래스에 대한 객체를 생성
2. 데이터를 읽고 쓰기
-> 레버선스.읽기 메소드(); 또는 레퍼런스.쓰기 메소드()를 호출
3. 데이터 읽고 쓰기가 끝나면 OS에 스트림을 반납

위와 같은 절차를 통해 입출력이 이루어진다.

그럼 이 스트림이라는게 무엇인지 자세하게 알아보자.

스트림

  • 데이터를 입출력 하기위한 통로
  • 외부 자언과 입출력을 하기 위해 시스템으로부터 스트림을 얻어와 사용 후 시스템에 반납
  • java.io 패키지의 클래스들로 지원함
  • 스트림은 단방향임
    ∴ 입력과 출력이 동시에 일어나는 경우에는 입출력 스트림이 각각 1개씩은 필요함

위의 설명과 같다. 스트림이란 데이터가 이동하는 통로다. 반드시 반납해주어야하고, 단방향이다. 이게 중요하다. 스트림은 단방향이란 말이다. 그래서 입출력을 하나의 스트림이 동시에 할 수 없다. 입출력을 동시에 해야하는 경우가 있다면, 무조건 스트림을 2개 만들어주어야한다.
스트림도 그냥 stream이 끝이 아니다. 스트림의 분류를 알아보자.

Stream 분류
1. 전송 방향에 따른 분류

  • 입력 스트림 : 디바이스로부터 데이터를 읽어오는 스트림
    ->InputStream,Reader
    -출력 스트림 " 디바이스로 데이터를 내보내는(출력하는) 스트림
    -OutputStream, Writer
  1. 전송 단위에 따른 분류
  • 바이트 스트림 : 1byte 단위로 입출력하는 스트림
    ->InputStream,OutputStream
  • 문자 스트림 : 한문자(2byte)단위로 입출력하는 스트림
    -> Reader, Writer
  1. 보조 스트림
  • 스트림의 기능을 향상시키거나 새로운 기능을 추가 시킴
  • 보조 스트림만 단독적으로 사용할 수 없음
  1. 훨씬 더 많은 스트림의 종류
  • 그렇지만 여기서는 살펴보지 않는다.

일단 byte스트림, 문자 스트림과 보조 스트림까지 살펴보자.

byte 스트림

  • 바이트 단위로 데이터를 전송
  • 바이트 단위로 구성된 파일을 처리하기에 적합한 스트림 ex)동영상,이미지,음악
  • 최상위 클래스는 InputStream과 OutputStream이고, 바이트 기반의 주,보조 스트림이 있다.

문자 스트림

  • 문자단위(2byte)로 데이터를 전송
  • 영어 이외의 문자에 대한 처리와 인코딩을 내부에서 처리
  • 최상위 클래스는 Reader와 Writer이고, 문자 기밪의 주,보조 스트림이 있다.

보조 스트림

  • 스트림의 기능을 향상시키거나 새로운 기능을 추가하기 위해 사용
  • 보조 스트림은 실제 데이터를 주고받는 스트림이 아니기 때문에 입출력을 직접 처리할 수 없고, 스트림을 먼저 생성하고 생성된 스트림을 이용해서 보조 스트림을 생성

이렇게 굵직한 개념들을 정리해보았다. 간단하게 말하자면 보내는 데이터의 크기에 따라 입출력 스트림을 다르게하고, 그걸 도와주고, 더 빠르게 처리할 수 있도록 만들어주는게 보조스트림이다.

그럼 이 스트림들을 이용해서 대체 어디에 입출력을 해주는가? 여러 매체에서 받아오고 출력할 수 있겠지만, 지금의 필자 굉장히 초보기때문에 file클래스 정도만 배운다.

file class

  • 파일 시스템의 파일을 표현하는 클래스로 파일크기, 파일속성, 파일이름 등의 정보를 제공하며 파일 생성 및 삭제기능도 제공한다.

파일에 대한 다양한 정보를 리턴하는 메소드가 많이 있지만, 그건 필요할때마다 찾아쓰는게 더 좋을 것 같다. 알아둬서 나쁠 것은 없지만, 너무 많아서 외우고 다니기는 힘들다.

파일을 만들기 위해 데이터를 내보내야하는데 그 데이터가 객체인 경우가 있다. 그럴경우에는 바로 내보낼 수 없다. 왜냐하면 객체는 크기가 너무 크다. 그럼 어떻게 해줘야하는가?
그 답은 직렬화에 있다.

직렬화

  • 객체를 스트림으로 전송하기 위해서 진행해야 하는 작업
  • 객체는 큰 덩어리이므로 바이트단위로 잘라주어야 함
  • java.io.Serializable을 implements하여 구현

직렬화를 해주기 위해서는 serialVersuinUID라는 객체의 고유 번호를 지정해줄 필요가 있다.

serialVersionUID

  • 직렬화시에 사용되는 객체의 고유번호
  • 명시하지 않아도 Serealizable인터페이스르 implements하면 JVM이 임의의 번호를 붙여 생성하지만 직접생성하는 것을 권장한다.
public class User implements Serializable{
	//직렬화시 사용되는 객체의 고유한 번호
	private static final long serialVersionUID = 111111111L;
	private String id;
	private transient String pw; //변수에 transient가 설정되면 직렬화시 제외
    //요런 느낌

마지막으로 위의 코드에 나온 transient는 객체를 직렬화할때 제외할 필드앞에 붙이는 코드이다. 이 친구는 직렬화에서 예외된다.

네트워크

네트워크

  • 여러 대의 컴퓨터를 통신 회선으로 연걸한 것
  • 홈 내트워크, 지역 네트워크, 인터넷 등이 해당

네트워크의 목적

  • 여러 개의 통신기기(컴퓨터, 휴대폰 등) 들을 서로 연결하여 데이터를 손쉽게 주고받거나 자원(프린터 등) 등을 공유하기 위함
  • 빠른 데이터 교환

네트워크라는 말은 살면서 굉장히 접하기 쉽다. 이 쉬운걸 굳이 정리하고 요약하자면 여러 전자매체와 데이터를 주고받기 위해 통신하는 것 이다.
네트워크가 동작할 때, 각 매체들을 서버와 클라이언트로 나눌 수 있다.

서버 - 서비스를 제공하는 컴퓨터 또는 프로그램
클라이언트 - 서비스를 요청하여 사용하는(제공받는)컴퓨터

서비스를 제공하기 위해서는 서버용 프로그램이 있어야 하며, 서비스를 이용하기 위해서는 클라이언트용 프로그램이 각각 있어야 한다.
그럼 이제 네트워크가 형성되기 위해 필요한 것들을 알아보자.

네트워크의 구성에 필요한 것
1. IP 주소

  • 네트워크 상에서 통신기기들이 서로 인식하고 통신하기 위해 사용하는 고유한 주소
  • 같은 네트워크상에서 동일한 IP주소를 여러 컴퓨터가 사용할 수 없음
  • 자바에서는 InetAddress 클래스를 통해서 관리
  1. port
  • 같은 컴퓨터 내에서 프로그램을 식별하는 번호
  • 클라이언트는 서버 연결 요청 시 IP주소와 port번호를 알아야 요청 가능
  • port번호의 설정 범위는 0~65535이고, 설정할 때 0~1000번은 많이들 사용하고계신다고하니 왠만하면 그 번호를 피해서 설정하는 것이 좋음
  1. 프로토콜
  • 컴퓨터 간의 정보를 주고 받을 때의 통신방법에 대한 규약으로 접속이나, 전달방식, 데이터 형식, 검증 방법 등을 맞추기 위한 약속
  1. TCP
  • 데이터의 전달의 신뢰성을 최대한 보장하기 위한 방식
  • 연결지향형 통신
  • 순차적으로 데이터를 전송하고 확인 및 오류 시 재전송
  • HTTP, FTP등에 사용
  1. UDP
  • 데이터의 빠른 전달을 보장하기 위해 사용
  • 비 연결 지향형 통신
  • 확인 및 재전송 작업이 없음
  • 실시간 스트리밍 서비스 등에 사용 ex)twitch? 킹아
  1. socekt
  • 서버와 클라이언트가 통신을 하기 위한 매개체
  • 자바의 java.net 패키지를 통해 소켓프로그래밍을 지원

요 마지막 소켓을 이용한 프로그래밍 예시를 알아보고 끝마치자. 바로 간단한 야구게임이다.
야구 게임은 두 사람이 숫자를 정해놓고 진행한다. (몇자리인지는 하는 사람마다 상대방과 합의하에 진행하는데, 여기서는 3자리로 진행한다.) 두 사람 모두 3자리 숫자를 정했다면, 번갈아가면서 임의의 숫자를 부르고, 그 수와 자리수까지 일치하면 스트라이크, 숫자는 있지만 자리가 다르면 볼이라고 말한다.
ex)123 - 321!! -> 1s2b // 123 - 154!! -> 1s
두 사람이 게임을 할 수 있도록 프로그램을 짜보자.
근데 사실 이 게임은 서버가 문제를 내고 그냥 한사람이 맞추는거다...
같이 할 사람이 없다..................

가장 먼저 서버에서 사용할 포트번호를 지정해준다. 그리고 다른 변수들까지 한번에 다 선언하자.

public class BaseballServer {
	Scanner sc = new Scanner(System.in);
	Random r = new Random();
	ArrayList<Integer> serverBall = new ArrayList<Integer>(); //서버가 정할 숫자
	int strike;// 스트라이크 수
	int ball;// 볼 수
	int port = 8999; //port 번호 이 번호를 입력한 클라리언트 프로그램이 들어온다.
	int inning;// 시도횟수
	ServerSocket server = null; //소켓 객체
	DataOutputStream dos = null; // 출력 스트림
	DataInputStream dis = null; // 인풋 스트림

서버용 포트 번호를 지정해줬다면 클라이언트 쪽에도 똑같은 번호를 넣어주자. 변수도 선언하구.

public class BaseballClient {
	Scanner sc = new Scanner(System.in);
	ArrayList<Integer> clientBall = new ArrayList<Integer>(); // 내가 입력하는 숫자를 받아줄 자료구조
	int inning;//시도횟수
	String serverIp = "127.0.0.1"; // 내 컴퓨터에서 내가 실행할 경우에 이 ip를 사용한다.
	int serverPort = 8999; //서버의 port번호와 같게 설정한다. 그래야 접속 가능하다.
	Socket socket = null; // 소켓 객체
	DataOutputStream dos = null; // 출력 스트림
	DataInputStream dis = null; // 입력 스트림

ip 주소는 내가 내 컴퓨터에서 서버와 클라이언트를 굴릴 경우에 "127.0.0.1"을 사용해준다.

다시 서버로 돌아가서 서버용 소켓 객체를 만들고 클라이언트가 접속할때까지 대기한다.

public void main() {
	System.out.println("< < < < < BaseBall Game > > > > >");
	try {
		server = new ServerSocket(port);// 서버 생성 밑 port값 입력 후 기다리는 중

원래는 바로 클라이언트의 접속을 대기중이라고 출력해주려고 했는데, 게임이 끝나도 바로 서버가 종료되지 않도록 만들거라서 이따가 while문에 넣을 것 이다.
서버가 클라이언트의 접속을 기다리고있으니 바로 접속한다.

public void main() {
		try {
			socket = new Socket(serverIp, serverPort);

소켓 객체를 생성해 ip와 port번호를 넣어주면 접속이된다. 이제 두 프로그램은 연결되었다!!
본격적으로 연결되면 어떻게 만들지 넣어주자! 일단 둘다 소켓 객체 생성하고, 입출력 스트림도 생성하고, 보조스트림까지 생성해 프로그램의 속도를 향상시켜주자!

while (true) {
	System.out.println("클라이언트의 접속을 기다립니다.");
	serverBall.clear(); // list값 초기화
	Socket socket = server.accept();
	System.out.println("클라이언트가 접속했습니다.");
	OutputStream out = socket.getOutputStream();// socket에서 가져온 outputStream
	InputStream in = socket.getInputStream();// socket에서 가져온 inputStream
	dos = new DataOutputStream(out); // 보조 스트림
	dis = new DataInputStream(in); // 보조 스트림

요게 서버거

try {
	socket = new Socket(serverIp, serverPort);
	OutputStream out = socket.getOutputStream();
	InputStream in = socket.getInputStream();
	dos = new DataOutputStream(out);
	dis = new DataInputStream(in);

요게 클라이언트거다. 해주는 일은 같다. 단 둘다 입출력 스트림을 가져올때는 socket객체에서 가져와야한다.
지금부터는 게임이 돌아가는 코드가 주로 올라가는데 지금 보는건 그게 아니니까 따로 설명은 안하겠다. 지금부터 하는 과정이 입출력의 과정이다.

서버 코드에 숫자를 정하고, 클라이언트가 숫자를 입력해서 서버가 그 숫자를 받아온다. 그러면 서버가 클라이언트가 보낸 숫자를 정해진 숫자와 비교해 스트라이크, 볼 값을 내고, 그 값을 다시 클라이언트에게 전해준다. 이런 프로그램이다. 입출력을 하는 부분은 강조해놓아야겠다.

for (int i = 0; i < 3; i++) {
	int num = r.nextInt(10);
	serverBall.add(num);
	for (int j = 0; j < i; j++) {// 겹치는 숫자를 지정하지 않게 하도록 for문 사용
		if (serverBall.get(i).equals(serverBall.get(j))) {
			serverBall.remove(i);
			i--;
		}
    } // for end
} // for end 
// 랜덤한 숫자 출력
System.out.println("서버 숫자 " + serverBall.get(0) + " " + serverBall.get(1) + " " + serverBall.get(2));
// 필수 요소들 선언
System.out.println("< < < < < GameStart > > > > >");
inning = 1;
while (true) {
	// 클라이언트 숫자 받아오기
	strike = 0;// strike값 초기화
	ball = 0;// ball값 초기화
	**String first = dis.readUTF(); /////////////////클라이언트 숫자 받아오는 부분
	String second = dis.readUTF();
	String third = dis.readUTF(); **
	System.out.println("클라이언트가 입력한 수 : " + first + " " + second + " " + third);

// 스트라이크 볼 판별
for(int i=0;i<3;i++) {
	switch(i) {
	case 0:
		if(serverBall.get(i)==Integer.valueOf(first)) {
			strike++;
		}else if(serverBall.get(i)==Integer.valueOf(second)) {
			ball++;
		}else if(serverBall.get(i)==Integer.valueOf(third)) {
			ball++;
		}
		break;
	case 1:
		if(serverBall.get(i)==Integer.valueOf(first)) {
			ball++;
		}else if(serverBall.get(i)==Integer.valueOf(second)) {
			strike++;
		}else if(serverBall.get(i)==Integer.valueOf(third)) {
			ball++;
		}
		break;
	case 2:
		if(serverBall.get(i)==Integer.valueOf(first)) {
			ball++;
		}else if(serverBall.get(i)==Integer.valueOf(second)) {
			ball++;
		}else if(serverBall.get(i)==Integer.valueOf(third)) {
			strike++;
		}
		break;
	}
}
					
// 스트라이크 볼 출력
dos.writeUTF(String.valueOf(strike));
dos.writeUTF(String.valueOf(ball));
if (inning == 10) {// 10회일때
	if (strike == 3) {// 10회때 정답을 맞췄을 경우
		System.out.println(strike + "스트라이크. 정답 현재 클라이언트와의 접속을 종료합니다.");
		break;
	} else {// 10회때도 정답을 맞추지 못했을 경우
		System.out.println(strike + "스트라이크" + ball + "볼");
		System.out.println("시도횟수 10회로 클라이언트 접속을 종료합니다.");
		String result = "기회를 모두 사용하셨습니다. 정답은 " + serverBall.get(0) + " " + serverBall.get(1) + " "+ serverBall.get(2) + " 입니다.";
		dos.writeUTF(result);
		break;
		} // 10회일 경우 end
	} else {// 10회가 아닐때
		if (strike == 3) {// 정답을 맞췄을 경우
			System.out.println(strike + "스트라이크. 정답 현재 클라이언트와의 접속을 종료합니다.");
			break;
		}
		System.out.println(strike + "스트라이크" + ball + "볼");
	}
inning++;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{ //다 쓴 스트림들 반납해주자.
	try {
		server.close();
		dos.close();
		dis.close();
	} catch (IOException e) {
	// TODO Auto-generated catch block
	e.printStackTrace();
	}
}

}
}

이게 서버 코드다. 어차피 나만볼거다... 다시 볼때 고생하는 건 나다ㅜㅜ

public void main() {
	try {
	socket = new Socket(serverIp, serverPort);
	OutputStream out = socket.getOutputStream();
	InputStream in = socket.getInputStream();
	dos = new DataOutputStream(out);
	dis = new DataInputStream(in);
	System.out.println("< < < < < BaseBall Game > > > > >");
	System.out.println("< < < < < GameStart > > > > >");
	System.out.println();
	inning=1;
	while(true) {
		System.out.print("1번째 숫자 입력 : ");
		String first = sc.next();
		System.out.print("2번째 숫자 입력 : ");
		String second = sc.next();
		System.out.print("3번째 숫자 입력 : ");
		String third = sc.next();
		dos.writeUTF(first);
		dos.writeUTF(second);
		dos.writeUTF(third);
				
		String strike = dis.readUTF();//스트라이크 결과값 가져오기
		String ball = dis.readUTF();//볼 결과값 가져오기
		if(inning==10) {
			if(strike.equals("3")) {//10회때 정답을 맞췄을 경우
				System.out.println(inning+"회 -----> "+strike+"스트라이크");
				System.out.println("축하합니다!");
				System.out.println("프로그램을 종료합니다.");
				break;
			}else{//10회때도 정답을 맞추지 못했을 경우
				String result = dis.readUTF();//일단 result값을 가져오기	
				System.out.println(inning+"회 -----> "+result);
				break;
			}//10회의 경우 end					
			}else{//10회가 아닐경우
				if(strike.equals("3")) {
					System.out.println(inning+"회 -----> "+strike+"스트라이크");
					System.out.println("축하합니다!!");
					System.out.println("프로그램을 종료합니다.");
					break;
				}
				System.out.println(inning+"회 -----> "+strike+"스트라이크"+ball+"볼");
			}
			inning++;
		}
	} catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
		}
	}
}

요게 클라이언트 코드다 어지럽다 2일에 걸려서 썼다...? 게을러서 그렇다. 이제 곧 설 연휴인데 그 동안에 많이 못했던 프로그래밍 공부를 할 생각이다. 오늘은 여기까지? 아니 이따가 하나 글 더 쓸거다. 이제는 sql이다. 자바는 c++랑 같이 백준에서 열심히 써보겠다.

0개의 댓글