티스토리 뷰

1.    TCP 네트워킹

-      ServerSocket 생성자에 바인딩할 포트를 대입하고 객체를 생성한다.

-      ServerSocket severSocket = new ServerSocket();

-      severSocket.bind(new InetSocketAddress(listenPort));

-      연결수락은 accept( ) 메소드를 통해서 이루어 지며, 이 메소드는 클라이언트가 연결 요청을 줄 때까지는 블로킹한다.

-      Socket sock = severSocket.accept();

-      ServerSocket은 이용이 끝나면, 자원을 돌려줘야 하기에 닫아줘야 한다.

-       + 요청을 받을 때까지 대기 상태( Blocking )가 되는 메소드 :

-       1 ) ServerSocketaccept( );

-       2 ) Socket 생성자 또는 connect( );

-       3 ) Socketread( );write( );

-      병렬 처리의 필요성 :

-      1 ) 스레드가 도중에 블로킹되면, 다른 작업을 수행하지 못하게 된다.

-      2 ) /출력할 동안 다른 클라이언트의 연결요청 수락이나 입/출력이 불가능하다.


더보기

01. ServerSocket - TCP 연결통로 만들기

package tcp_practice;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;

import lombok.extern.log4j.Log4j2;


@Log4j2
public class S06_ServerSocket {
	
	public static void main (String[] args) throws IOException, InterruptedException { // TCP 연결통로 만들기 (****)
		
		log.debug("main({}) invoked.", Arrays.toString(args));
		// 출력 : 15:15:23.222 DEBUG --- [main           ] t.S06_ServerSocket.main - main([]) invoked.
		
//		========================================================
		
		int listenPort = 7777;
		int backLog = 100; // 연결 요청 Q
		
//		========================================================
		
		// 1. 클라이언트의 연결요청을 특정 포트로 받아서, 연결을 수락하고
		// 그 결과로 Socket 객체까지 만들어 내는데, 핵심역할을 하는 객체 생성
		ServerSocket severSocket = new ServerSocket();
		
//		log.info("0. serverSocket : {}", severSocket);
//		출력 : 09:15:16.926  INFO --- [main           ] t.S06_ServerSocket.main - 0. serverSocket : ServerSocket[unbound]
		
		// 아직 .bind로 ServerSocket을 연결하지 않아서 unbound로 출력이 된다.
		
//		========================================================
		
		// 2. 서버가 특정포트로 listen 할 수 있도록, Port Binding을 수행
		severSocket.bind(new InetSocketAddress(listenPort));
		
		// InetAddress = Hostname + IP address
		// InetSocketAddress = InetAddress + port
		// port는 프로그램 내 공유가 불가능하다. ( 특정 프로그램이 사용하면, 타 프로그램이 사용불가 ) 
		
		log.info("1. serverSocket : {}",severSocket);
//		출력 : 15:15:23.234  INFO --- [main           ] t.S06_ServerSocket.main - 1. serverSocket : ServerSocket[addr=0.0.0.0/0.0.0.0,localport=7777]
		
		// 이를 통해서 클라이언트와 서버가 가지고 있는 IP주소와 포트번호를 알 수 있다.
		// 0.0.0.0는 IP주소로 ANY 어디에서나 접속할 수 있다는 의미이다.
		
//		========================================================
		
//		severSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(),listenPort));
		
		// 특정 IP에서 특정 포트(7777)로만 받아들이겠다고 작성한 것이다.
		
//		log.info("2. serverSocket : {}", severSocket);
//		출력 : 09:26:36.324  INFO --- [main           ] t.S06_ServerSocket.main - 2. serverSocket : ServerSocket[addr=LAPTOP-O7HNQF0G/172.30.1.13,localport=7777]
		
		// InetAddress.getLocalHost()를 통해서 내 컴퓨터의 이름과 IP를 통해서 제한하였다.
		
//		========================================================
		
//		severSocket.bind(new InetSocketAddress(InetAddress.getLocalHost(),listenPort),backLog);
		
		// backLog는 연결요청 Q로, serversocket이 허용하는 양에 비해서 많은 요청이 들어왔을때 backLog에서 임시적으로 요청을 담아둔다.
		// int backLog = 100;이라는 의미는 backLog의 사이즈가 100이라는 의미이며, 요청이 backLog가 허용하는 양보다 많이 들어오면
		// 요청은 들어오지 못하고 무한정으로 기다리게 되기에, backLog는 요청의 양에 따라서 변경하는 것이 좋다.
		
//		log.info("3. serverSocket : {}", severSocket);
//		출력 : 09:31:21.653  INFO --- [main           ] t.S06_ServerSocket.main - 3. serverSocket : ServerSocket[addr=LAPTOP-O7HNQF0G/172.30.1.13,localport=7777]
		
		// backLog는 출력되지 않는다.
		
//		========================================================
		
//		severSocket.bind(new InetSocketAddress("0.0.0.0",listenPort),backLog);
		
		// IP를 ANY로 지정하였기에 1번과 같은 의미가 된다.
		
//		log.info("4. serverSocket : {}", severSocket);
//		출력 : 09:36:31.610  INFO --- [main           ] t.S06_ServerSocket.main - 4. serverSocket : ServerSocket[addr=/0.0.0.0,localport=7777]
		
//		========================================================
		
		// serverSocket과 port번호 지정을 동시에 하는 방법
		
//		ServerSocket severSocket = new ServerSocket(listenPort);
		
//		log.info("5. serverSocket : {}",severSocket);
		
//		========================================================
		
		// serverSocket과 port번호 및 backLog 설정을 동시에 하는 방법
		
//		ServerSocket severSocket = new ServerSocket(listenPort,backLog);
		
//		log.info("6. serverSocket : {}",severSocket);
		
//		========================================================
		
		// serverSocket과 port번호, backLog, IP주소 설정을 동시에 하는 방법
		
//		ServerSocket severSocket = new ServerSocket(listenPort,backLog,InetAddress.getLocalHost());
		
//		log.info("7. serverSocket : {}",severSocket);
//		출력 : 09:42:36.920  INFO --- [main           ] t.S06_ServerSocket.main - 7. serverSocket : ServerSocket[addr=LAPTOP-O7HNQF0G/172.30.1.13,localport=7777]
		
//		========================================================
		
		// Server의 Listening (****)
		
//		========================================================
//		Thread.sleep( 1000*60 ); // Timeout을 위해 잠시 프로그램의 실행을 중단시켰다. ( blocking )
//		========================================================
		
		try( severSocket; ){
			
			log.info("8. server Listening on port : {} and addr : {} ...",listenPort,severSocket.getInetAddress());
//			출력 : 10:01:47.458  INFO --- [main           ] t.S06_ServerSocket.main - 8. server Listening on port : 7777 and addr : 0.0.0.0/0.0.0.0 ...
			
			while(true) { // 서버는 계속 받아들어야 하기에 while로 계속 받아들이게 한다. ( listening )
				
				Socket socket = severSocket.accept(); // Block I/O - 연결요청이 없으면 멈춰 버리게 한다. (*****)
				
				// 명령프롬프트에서 telnet + IP주소 + port번호로 연결해야 한다.
				
				try ( socket ){
					
					log.info("9. socket : {}",socket);
					
					log.info("\t + getInetAddress : {}, getPort : {}, getLocalPort : {}",
							socket.getInetAddress(),socket.getPort(),socket.getLocalPort());
					
					log.info("\t + getRemoteSocketAddress : {}",socket.getRemoteSocketAddress());
					
					// 출력 :
					
//					10:06:33.516  INFO --- [main           ] t.S06_ServerSocket.main - 9. socket : Socket[addr=/127.0.0.1,port=56784,localport=7777]
//					10:06:33.516  INFO --- [main           ] t.S06_ServerSocket.main - 	 + getInetAddress : /127.0.0.1, getPort : 56784, getLocalPort : 7777
//					10:06:33.517  INFO --- [main           ] t.S06_ServerSocket.main - 	 + getRemoteSocketAddress : /127.0.0.1:56784
					
					// localport번호는 지정할 수 있지만 socket의 port번호는 임의로 할당된다.
					// IP주소는 ANY이기에 아무런 IP주소로도 접속이 가능하다.
					
					// 9. 출력은 연결 요청이 와야지만 출력이 된다.
					// 지금 상태로는 클라이언트가 보낸 요청을 받을 준비가 되어 있지 않은 상태이다. (***)
					
				} // try
				
				// ServerSocket은 양쪽 터널을 뚫어주는 역할을 하는거지, 양쪽 끝을 형성하는 것은 Socket이다.(****)
				
			} // while
			
		} // try - with - resource
		
	} // main

} // end class

더보기

02. TCP - 클라이언트 생성

package tcp_practice;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Arrays;

import lombok.extern.log4j.Log4j2;


@Log4j2
public class S07_SingleThreadSocketClient { // 클라이언트 생성
	
	// 키보드의 엔터키를 눌렀을 때에 발생하는 키코드 2개를 상수로 선언
	
	private static final int CR = 13;
	private static final int LF = 10;
	
	public static void main (String[] args) throws UnknownHostException, IOException{
		
		log.debug("main({}) invoked.", Arrays.toString(args));
		
//		===========================================================================
		
		String severAddress = "localhost"; // Loopback Address ( 127.0.0.1)의 도메인 명
		int serverPort = 7777;
		int connectTimeout = 1000; // milliseconds 단위 => ( 1000 = 1초 )
		
//		===========================================================================
		// 1. Socket 객체 생성시, 바로 서버과 연결 시도
//		===========================================================================
		
//		Socket socket = new Socket( severAddress, serverPort );
		
		// 서버에 연결이 되지 않으면, 예외가 발생하게 된다. ( 서버 프로그램이 구동되고 있는 상태어야 한다. )
		// 만약, 그래도 원하는 로그가 보이지 않는다면 콘솔 창을 늘려서 확인하는 방법도 있다.
		
//		log.info("1. socket : {}", socket);
//		출력 : 10:32:33.631  INFO --- [main           ] t.S07_SingleThreadSocketClient.main - 1. socket : Socket[addr=localhost/127.0.0.1,port=7777,localport=56855]
		
		// S06클래스의 port번호가 localport번호로 출력되고 있음을 볼 수 있다.
		// 실행할 때마다 서버와 연결을 시도하기 때문에, 계속 다른 localport 번호가 생성된다.
		
//		===========================================================================
		// 2. Socket 객체 생성시, 바로 서버과 연결 시도 2 - InetAddress 활용
//		===========================================================================
		
//		Socket socket = new Socket(InetAddress.getByName(severAddress), serverPort);
		
		// 서버의 IP주소를 InetAddress로 사용하였다.
		
//		log.info("2. socket : {}", socket);
		
//		===========================================================================
		// 3. Socket 객체 생성과 서버로의 연결시도를 분리시행한다. (****)
//		===========================================================================
		
		Socket sock = new Socket(); // Socket 객체 생성을 따로 분리한다.
		
//		sock.connect(new InetSocketAddress(severAddress,serverPort));
		
		// InetSocketAddress는 InetAddress의 정보 + Port의 정보를 가지고 있기에, Socket으로 서로 연동할 때 사용한다.
		
//		log.info("3. socket : {}", sock);
		
		//===========================================================================
		
		// 서버 연결 timeout을 추가로 지정하였다. ( timeout을 지정하는 편이 좋다. )
		
		sock.connect(new InetSocketAddress(severAddress,serverPort),connectTimeout);
		
		log.info("3-2. socket : {}", sock);
//		출력 : 11:27:03.191  INFO --- [main           ] t.S07_SingleThreadSocketClient.main - 3-2. socket : Socket[addr=localhost/127.0.0.1,port=7777,localport=54082]
		
		// timeout은 출력되지 않는다.
		// 만약 연결이 되어 있지 않거나, 서버 시간이 timeout을 넘어가면 connect 예외가 발생한다.
		
//		===========================================================================
		
		// 리모트 포트 번호와 로컬 포트 번호가 서버와 서로 반대로 출력되고 있다.
		
		log.info("\t4-1. getLocalSocketAddress : {}", sock.getLocalSocketAddress());
		log.info("\t4-2. getRemoteSocketAddress : {}", sock.getRemoteSocketAddress());
		
//		===========================================================================
//		SENT
//		===========================================================================
		
		try(sock){
			
			try {
				
				String sendLine = "Hello, World!";
				
				// Socket 객체에서 데이터를 보내고 / 받을 수 있는 InputStream / OutputStream 객체를 얻어낼 수 있다.
//				InputStream is = sock.getInputStream();
				OutputStream os = sock.getOutputStream();
				
				BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
				// 문자 기반의 출력 스트림이기에 먼저 OutputStreamWriter 보조스트림을 통해서 바이트 기반을 문자 기반으로 변환
				
				bw.write(sendLine); // Block IO - 쓸 준비가 될 때까지 blocking 한다. (***)
				
				// Blocking IO의 종류 :
				// (1) ServerSocket.accept();
				// (2) Socket.connect();
				// (3) InputStream.read();
				// (4) OutputStream.Write();
				
				// Enter 키 코드 값 전송
				bw.write(CR);
				bw.write(LF);
				// 콘솔에서 Enter키를 누르지 않으면 Read할 수 없기에 키 코드 값으로 Enter를 하도록 하였다.
				
				bw.flush();
				// 출력 스트림이기에 flush를 해야 하며, try - with - resource이기에 close할 필요는 없다.
				
				log.info("5. sendLine : {}", sendLine);
				// 출력 : 12:23:30.814  INFO --- [main           ] t.S07_SingleThreadSocketClient.main - 5. sendLine : Hello, World!
				
			} catch ( Exception e ) {
				
				log.error("* Exception : {} - {}", e.getClass().getName(), e.getMessage());
				//예외의 이름과 메세지를 출력해하는 코드이다.
				
			} finally {
				
//				sock.shutdownOutput();
				// Socket의 출력 기능만 close하겠다는 의미이다.
				
//				sock.shutdownInput();
				// Socket의 입력 기능만 close하겠다는 의미이다.
				
			} // try - catch - finally
			
//			=======================================================================
//			RECV ( 받을 준비를 해줘야 한다. )
//			=======================================================================
			
			try {
				
				InputStream is = sock.getInputStream();
				BufferedReader br = new BufferedReader(new InputStreamReader(is));
				
				String recvLine = br.readLine(); // Block IO
				
				log.info("6. recvLine : {}", recvLine);
				
			} catch (Exception e){
				log.error("* Exception : {} - {}", e.getClass().getName(), e.getMessage());
			} finally {
//				sock.shutdownInput();
			} // try - catch - finally
			
		} // try - with - resource
		
		log.info("7. Disconnected.");
		
	} // main

} // end class

더보기

03. TCP - 서버 생성

package tcp_practice;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;

import lombok.extern.log4j.Log4j2;

@Log4j2
public class S07_SingleThreadSocketServer {
	
	// 키보드의 엔터키를 눌렀을 때에 발생하는 키코드 2개를 상수로 선언
	private static final int CR = 13;
	private static final int LF = 10;
	
	public static void main (String[] args) throws IOException {
		
		log.debug("main({}) invoked.", Arrays.toString(args));
		
		int listenPort = 7777; // 서버의 listenPort 번호
		int backLog = 100; // 연결 요청 Q - serverSocket의 backLog Q size
		
		// 지정된 바인딩 포트 번호로, 포트 바인딩 하는 severSocket생성
		ServerSocket severSocket = new ServerSocket();
		log.info("1. serverSocket : {}",severSocket);
		
		severSocket.bind(new InetSocketAddress(listenPort));
		log.info("1-1.serverSocket {} ", severSocket);
		
//		=================================================================
		
		try(severSocket;){ // severSocket을 자동으로 닫게 해주고
			
			while(true) { // 무한루프 : 서버의 역할은 지속적으로 새로운 연결을 받고, socket 생성해야 할 역할을 수행하기 때문
				
				log.info("------------------------------------------------------------------------------");
				log.info("2. server Listening on port : {} and addr : {} ...",listenPort,severSocket.getInetAddress());
				log.info("------------------------------------------------------------------------------");
				
				// setSoTimeout 메소드 수행은 accept 메소드 수행 전에 먼저 적용해야 한다.
				// 즉, severSocket에 설정을 추가하는 것 : 연결요청을 얼마나 기다릴 것인가에 대한 설정
				
				severSocket.setSoTimeout(0); // 0은 무한정으로 기다리겠다는 의미이다.
				
				// 만일 새로운 연결요청이 들어오면, 수락하고, 새로운 socket 객체 생성
				// 즉 클라이언트와 서버 간의 새로운 연결(터널) 생성
				
				Socket sock = severSocket.accept(); // Blocking IO
				
				// 연결된 새로운 클라이언트에 대한 정보를 멸가지 출력해보자
				
				log.info("3. Client connected.");
				log.info("\t3-1. socket : {}",sock);
				
				log.info("\t3-2 + getLocalSocketAddress : {}", sock.getLocalAddress());
				
				log.info("\t3-3 + getRemoteSocketAddress : {}",sock.getRemoteSocketAddress());
				
//				=======================================
				// RECV ( 받을 준비를 해줘야 한다. )
//				=======================================
				
				String recvLine = null;
				String sendLine = null;
				
				try ( sock; ){
					
					try {
						
						InputStream is = sock.getInputStream();
						BufferedReader br = new BufferedReader(new InputStreamReader(is));
						
						recvLine = br.readLine(); // Block IO
						
						log.info("\t3-4 + recvLine : {}",recvLine);
						
					} catch ( Exception e ) { 
						log.error("* Exception : {} - {}", e.getClass().getName(), e.getMessage());
					} finally {
//						socket.shutdownInput();
					} // inner try - catch
					
				} // inner try - with - resource
				
//				=======================================
				// SENT
//				=======================================
				
				try {
					
					sendLine = recvLine;
					
					OutputStream os = sock.getOutputStream();
					BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
					
					bw.write(sendLine); // block IO
					
					bw.write(CR);
					bw.write(LF);
					
					bw.flush();
					
					log.info("\t4 + sendLine : {}",sendLine);
					
				} catch(Exception e) {
					log.error("* Exception : {} - {}", e.getClass().getName(), e.getMessage());
				} // try - catch
				
				log.info("7. Disconnected.");
				
			} // while
			
		} // try - with - resource : sock
		
	} // main

} // end class

더보기

04. 자바 빈즈 클래스

package tcp_practice;

import java.io.Serializable;

import lombok.Data;

@Data // getter / setter / toString / NoArgsConstructor / EqualsAndHashCode를 1번에 만들어 준다.(***)

// Mission Control 비지니스 데이터 저장용도의 자바빈즈 클래스 생성
// 아래의 규칙을 지키는 클래스를 특별히 "자바빈즈" 클래스라고 부르고
// 이 자바 빈즈 클래스는 아주 중요한 비지니스 데이터를 저장하는 용도로 사용된다.

// 규칙 : 
// 1) private 접근제한자로 필드를 선언한다. 		(필수)
// 2) 모든 필드의 getter 메소드와 setter 메소드를 갖는다. 	(필수)
// 3) 기본 생성자가 반드시 존재해야 된다. 		(필수)
// 4) implements Serializable					(선택)

// 만일 클래스가 자바빈즈 클래스라면, 필드(객체의 속성)와는 다른
// "프로퍼티"라는 가상의 개념이 생기게 된다. ( 필드와 비슷 )

// 프로퍼티 = get을 빼거나, set을 빼고, Camel 기법울 회수한 이름이 탄생 -> 이 이름이 프로퍼티
// 필드 : String name -> setMyName -> set제거 -> MyName -> myName
// 이렇게 생겨난 프로퍼티로 필드를 조작하게 된다.
public class Member implements Serializable { // 자바빈즈 클래스

	private static final long serialVersionUID = 1L;
	
	// 모든 필드는 private로 은닉화
	private Integer id;
	private String name;
	private Integer age;
	
	
} // end class
728x90
댓글
«   2024/09   »
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
최근에 올라온 글
Total
Today
Yesterday
공지사항