티스토리 뷰
1. TCP 네트워킹
- ServerSocket 생성자에 바인딩할 포트를 대입하고 객체를 생성한다.
- ServerSocket severSocket = new ServerSocket();
- severSocket.bind(new InetSocketAddress(listenPort));
- 연결수락은 accept( ) 메소드를 통해서 이루어 지며, 이 메소드는 클라이언트가 연결 요청을 줄 때까지는 블로킹한다.
- Socket sock = severSocket.accept();
- ServerSocket은 이용이 끝나면, 자원을 돌려줘야 하기에 닫아줘야 한다.
- + 요청을 받을 때까지 대기 상태( Blocking )가 되는 메소드 :
- 1 ) ServerSocket의 accept( );
- 2 ) Socket 생성자 또는 connect( );
- 3 ) Socket의 read( );와 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
'KH 정보교육원 [ Java ]' 카테고리의 다른 글
KH 31일차 - 네트워킹 (0) | 2022.04.11 |
---|---|
KH 30일차 - TCP 네트워킹 및 멀티 스레드 (0) | 2022.04.08 |
KH 28일차 - 네트워킹 (0) | 2022.04.06 |
KH - 2차 시험 + 국비 1개월차 후기 (0) | 2022.04.05 |
KH 27일차 - 보조스트림 / 네트워크 (0) | 2022.04.05 |
댓글