Woopii Vyeolog

[Java] Socket 통신, TCP Client 샘플 본문

java

[Java] Socket 통신, TCP Client 샘플

WooPii 2022. 4. 2. 01:13

## Github 주소 : https://github.com/leewoopyo/tcp_sample

 

GitHub - leewoopyo/tcp_sample

Contribute to leewoopyo/tcp_sample development by creating an account on GitHub.

github.com

 

 

1. 소켓이란?

소켓은 네트워크 상에서 돌아가는 두 개의 프로그램 간 양방향 통신의 엔드 포인트.

 

2. 앤드 포인트

여기에서의 앤드 포인트는 아이피 주소와 포트 번호의 조합을 의미함. 모든 TCP 연결은 2개의 앤드 포인트로 유일하게 식별되어질 수 있습니다.

 

3. 소켓 통신

소켓 통신이란 서버와 클라이언트 양방향 연결이 이루어지는 통신.

 

 

4. TCP (스트림 소켓)

  • 연결형 (Connection Oriented) 서비스로 연결이 성공해야 통신이 가능하다.
  • 데이터의 경계를 구분하지 않는다. (바이트 스트림)
  • 데이터의 전송 순서를 보장한다. (데이터의 순서 유지를 위해 각 바이트마다 번호를 부여)
  • 신뢰성 있는 데이터를 전송한다. (Sequence Number, Ack Number를 통한 신뢰성 보장)
  • 데이터 흐름 제어 (수신자 버퍼 오버플로우 방지) 및 혼잡 제어 (패킷 수가 과도하게 증가하는 현상 방지)
  • 연결의 설정 (3-way handshaking) 와 해제(4-way handshaking)
  • 전이중 (Full-Duplex), 점대점 (point to point) 서비스
  • UDP 보다 전송속도가 느리다.

 

5. TCP Client

byte의 길이를 표현하는 방식에 따라서 2가지 lengthType을 설정

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * TcpClient
 */
public class TcpClient {

    /**
     * 특정 문자를 leftPad 하는 메소드
     * @param str       : 원문 문자열
     * @param size      : padding 이후의 최종 문자의 길이
     * @param padStr    : padding 할 문자
     * @return
     */
    public static String leftPad(String str, int size, String padStr) {
        // 원본 문자열이 null 이면 null을 반환
        if (str == null) {
            return null;
        }
        // padding 문자열이 없을 떄는 ' '으로 처리
        if ((CharSequence)padStr == null || ((CharSequence)padStr).length() == 0) {
            padStr = " ";
        }
        int padLen = padStr.length();   // padding할 문자의 길이
        int strLen = str.length();      // 원본 문자의 길이
        int pads = size - strLen;       // padding이 필요한 길이

        // padding 할 길이가 없을 떄, 원분 문자열을 반환
        if (pads <= 0) {
            return str; 
        }

        // padding 할 문자가 1글자일 경우, char[]를 만들어 빈칸을 채운 후 원본 문자 concat
        if (padLen == 1) {
            char[] buf = new char[pads];
            for (int i = pads - 1; i >= 0; i--) {
                buf[i] = padStr.charAt(0);
            }
            return new String(buf).concat(str);
        }

        // padding할 문자와 padding이 필요한 길이가 같을 떄, padding 문자와 원문 문자 concat 
        if (pads == padLen) {
            return padStr.concat(str);
        // padding할 문자의 길이가 padding이 필요한 길이보다 길 때, padding 문자를 subString으로 자른 후 원본 문자열하고 concat
        } else if (pads < padLen) {
            return padStr.substring(0, pads).concat(str);
        // 그 외의 경우, padding할 문자열을 char[]로 바꾼 후 for문으로 순차적으로 한글자씩 padding이 필요한 길이만큼 붙인 다음, 원분 문자열 concat
        } else {
            char[] padding = new char[pads];
            char[] padChars = padStr.toCharArray();
            for (int i = 0; i < pads; i++) {
                padding[i] = padChars[i % padLen];
            }
            return new String(padding).concat(str);
        }
    }

    /**
     * 1. Tcp Server에 byte[] Message 요청(write), 후 응답(read)
     * 2. lengthType 변수에 따라 4byte의 length의 형태가 달라짐(String or Binary)
     * @param address       : 서버 주소 
     * @param port          : 서버 포트
     * @param lengthType    : 4byte 길이의 형태
     * @param msg           : 길이를 제외한 전문 메시지
     */
    public static void call(String address, int port, String lengthType, byte[] msg) {
        
        // try ~ catch ~ resource
        try (Socket socket = new Socket(address, port);
            OutputStream out = socket.getOutputStream();
            DataOutputStream dos = new DataOutputStream(out);
            InputStream in = socket.getInputStream();
            DataInputStream dis = new DataInputStream(in);
            ) 
        {   
            // 0. 서버에 lengthType을 먼저 전달
            System.out.println("[Tcp_Client] 0. length_type : [" + lengthType + "]");
            dos.write(lengthType.getBytes(), 0, lengthType.getBytes().length);
            dos.flush();

            // 1. lengthType이 String이면 
            if ("String".equals(lengthType)) {

                // 1-1. write_length 추출 : msg의 길이 구한 후 leftpad를 활용하여 4byte length부 추출
                byte[] byteWriteLength = leftPad(Integer.toString(msg.length), 4, "0").getBytes();
                System.out.println("[Tcp_Client] 1. write_length : [" + new String(byteWriteLength) + "]");

                // 1-2. write_msg  : length와 msg를 결합해서 서버로 write를 하는 writeMsg 구성
                byte[] writeMsg = new byte[4 + msg.length];
                System.arraycopy(byteWriteLength, 0, writeMsg, 0, 4);
                System.arraycopy(msg, 0, writeMsg, 4, msg.length);
                System.out.println("[Tcp_Client] 2. write_msg : [" + new String(writeMsg) + "]");

                // 1-3. 구성한 writeMsg를 TcpServer에 write
                dos.write(writeMsg, 0, writeMsg.length);
                dos.flush();

                // 1-4. TcpServer에서 응답받은 4byte의 length를 추출
                byte[] byteLength = new byte[4];
                dis.readFully(byteLength, 0, 4);
                int length =  Integer.parseInt(new String(byteLength));
                System.out.println("[Tcp_Client] 2. read_length : [" + length + "]");

                // 1-5. TcpServer에서 응답받은 echoMsg를 추출
                if (length > 0) {
                    byte[] readMsg = new byte[length];
                    dis.readFully(readMsg, 0, length);

                    byte[] echoMsg = new byte[4 + length];
                    System.arraycopy(byteLength, 0, echoMsg, 0, 4);
                    System.arraycopy(readMsg, 0, echoMsg, 4, length);
                    System.out.println("[Tcp_Client] 3. read_echo_msg : [" + new String(echoMsg) + "]");
                }
            }

            // 2. lengthType이 Binary이면 
            if ("Binary".equals(lengthType)) {

                // 2-1. TcpServer에 length와 msg를 write
                dos.writeInt(msg.length);
                dos.write(msg, 0, msg.length);
                dos.flush();

                // 2-2. TcpServer로부터 length를 read함
                int length = dis.readInt();
                System.out.println("[Tcp_Client] 1. read length : [" + length + "]");

                // 2-3. TcpServer로부터 echoMsg를 받음
                if (length > 0) {
                    byte[] readMsg = new byte[length];
                    dis.readFully(readMsg, 0, length);

                    byte[] echoMsg = new byte[length];
                    System.arraycopy(readMsg, 0, echoMsg, 0, length);

                    System.out.println("[Tcp_Client] 2. read_echo_msg : [" + new String(echoMsg) + "]");
                }
            }

            System.out.println("[Tcp_Client] Tcp Client Request Message Ok........");

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Main 메소드
     * @param args
     */
    public static void main(String[] args) {

        // 소켓 정보 및 lengthType 초기화
        String TCP_SVR_ADDRESS = "127.0.0.1";
        int TCP_SVR_PORT = 11111;
        String lengthType = null;
        byte[] msg = " wpleeTest_Binary".getBytes();

        // 1. main 실행 시 lengthType에 해당하는 arguments 가 있다면 lengthType에 해당 argument를 대입.
        // 2. arguments가 없다면 lengthType에 "String" 값을 넣는다.
        // 3. Tcp Client 로직 실행.
        try {
            if (args[0] != null) {
                lengthType = args[0];
                System.out.println("main args[0] ===> [" + args[0] + "]");
            } else {
                lengthType = "String";
            }
            TcpClient.call(TCP_SVR_ADDRESS, TCP_SVR_PORT, lengthType, msg);    
        } catch (ArrayIndexOutOfBoundsException aiobe) {
            lengthType = "String";
            TcpClient.call(TCP_SVR_ADDRESS, TCP_SVR_PORT, lengthType, msg);
        }
        
    }
}

 

 

 

## 참고

https://helloworld-88.tistory.com/215#:~:text=SOCKET%20%ED%86%B5%EC%8B%A0%EC%9D%98%20%ED%8A%B9%EC%A7%95,%EA%B2%BD%EC%9A%B0%EC%97%90%20%EC%9E%90%EC%A3%BC%20%EC%82%AC%EC%9A%A9%EB%90%9C%EB%8B%A4.

 

[기본] 소켓(SOCKET)통신 이란?

소켓통신 소켓(SOCKET)이란? 소켓(Socket)은 프로세스가 드넓은 네트워크 세계로 데이터를 내보내거나 혹은 그 세계로부터 데이터를 받기 위한 실제적인 창구 역할을 한다. 그러므로 프로세스가 데

helloworld-88.tistory.com

 

https://coding-factory.tistory.com/614

 

[Network] TCP / UDP의 개념과 특징, 차이점

전송 계층에서 사용되는 프로토콜 (TCP / UDP) TCP와 UDP는 OSI 표준모델과 TCP/IP 모델의 전송계층에서 사용되는 프로토콜입니다. 전송계층은 송신자와 수신자를 연결하는 통신 서비스를 제공하고 IP

coding-factory.tistory.com

 

 

 

Comments