제육's 휘발성 코딩
Published 2021. 12. 11. 21:41
[Network] - Java TCP, UDP 통신 CS/네트워크
반응형

Java Network

image

Echo

  • Server
    • ServerSocket (port) - waiting & accept()
    • 클라이언트의 요청이 와서 accept() 시 요청온 클라이언트용 소켓 추가 생성
    • 송수신 버퍼 존재
  • Client
    • Socket ( Socket -> ServerSocket 연결 요청) - socket(ip, port)
    • 송수신 버퍼 존재
  • 결과
    • <서버>
      Connected client : 127.0.0.1 : 49706
      Client's msg : sdas
      Client's msg : null
    • <클라이언트>
      Connection is completed
      Welcome to Echo server!!
      Message : sdas
      Echoed Message : sdas
      Message : BYE
      Disconnect
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class EchoServer {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        try {
            // 클라이언트의 접속을 처리하는 서버소켓 생성
            ServerSocket ss = new ServerSocket(8000);
            // 클라이언트 접속 대기
            Socket clients = ss.accept();
            System.out.println("Connected client : " + clients.getInetAddress()
                    .getHostAddress()+" : " + clients.getPort());

            //스트림 생성
            BufferedReader in = new BufferedReader(new InputStreamReader(clients.getInputStream()));
            PrintWriter out = new PrintWriter(new OutputStreamWriter(clients.getOutputStream()));
            // 데이터 송신
            out.println("Welcome to Echo server!!"); //송신 버퍼에 전달
            out.flush();

            while(true){
                String msg = in.readLine();
                System.out.println("Client's msg : " + msg);
                if(msg.equals("BYE") || msg == null) break;
                out.println(msg);
                out.flush();
            }
            clients.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class EchoClient {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        try {
            Socket s = new Socket("127.0.0.1", 8000);
            System.out.println("Connection is completed");

            BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
            PrintWriter out = new PrintWriter(new OutputStreamWriter(s.getOutputStream()));
            String data = in.readLine();
            System.out.println(data);
            while (true) {
                System.out.print("Message : ");
                String msg = sc.nextLine();
                if(msg.equals("BYE")) break;
                out.println(msg);
                out.flush();
                msg = in.readLine();
                System.out.println("Echoed Message : " + msg);
            }
            System.out.println("Disconnect ");
            s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Thread

  • Thread란 하나의 프로세스 내부에서 독립적으로 실행되는 하나의 작업 단위
  • Thread 기반 서버
    • 클라이언트와 서버 간 양방향 송수신을 위한 스트림 채널이 필요
    • 스트림을 만든 후 서버는 Thread를 통해 다시 wait 상태로 간다.
    • 단, 서버의 과부하가 발생하므로 퍼포먼스 면에서 적합하지 않은 방법이다.
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;

public class ChatServer {
    static Vector<ChatHandler> clients = new Vector<>();
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(3005);
        while (true) {
            // 클라이언트 접속 대기
            System.out.println("Chat server is waiting ...");
            Socket s = ss.accept(); // block mode
            System.out.println("Client is connected : " + s);
            // 입출력 스트림 처리
            DataInputStream dis = new DataInputStream(s.getInputStream()); // 수신
            DataOutputStream dos = new DataOutputStream(s.getOutputStream()); // 송신

            dos.writeUTF("Welcome to a Chatting server");
            dos.writeUTF("Send your chatting id.");
            String name = dis.readUTF(); // 클라이언트 이름 수신
            ChatHandler handler = new ChatHandler(name, s, dis, dos);
            clients.add(handler);
            Thread thread = new Thread(handler);
            thread.start(); // handler 안에 있는 run 메서드가 동작
        }
    }
}
package network.thread;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.StringTokenizer;

public class ChatHandler implements Runnable{
    private String name;
    private Socket s;
    private final DataInputStream dis;
    private final DataOutputStream dos;
    private boolean isSignin;

    public ChatHandler(String name, Socket s, DataInputStream dis, DataOutputStream dos) {
        this.name = name;
        this.s = s;
        this.dis = dis;
        this.dos = dos;
        isSignin = true;
    }

    @Override
    public void run() {
        System.out.println(name + " is chatting...");
        String received;
        while (true) {
            try {
                received = dis.readUTF();
                System.out.println("Message : "+received+" from " + name);
                if(received.equals("Bye")) break;
                if(!received.contains("#")) continue;
                StringTokenizer tokenizer = new StringTokenizer(received, "#");
                String who = tokenizer.nextToken();
                String msg = tokenizer.nextToken();
                for(ChatHandler c : ChatServer.clients) {
                    if (c.name.equals(who) && c.isSignin == true) {
                        c.dos.writeUTF(who +" >> " +msg);
                        break;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            s.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
package network.thread;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;

public class ChatClient {
    final static int ServerPort = 3005;
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        InetAddress ipAddr = InetAddress.getByName("localhost");
        Socket s = new Socket(ipAddr, ServerPort);
        System.out.println("Connection OK!!");
        // 입출력 스트림 처리
        DataInputStream dis = new DataInputStream(s.getInputStream()); // 수신
        DataOutputStream dos = new DataOutputStream(s.getOutputStream()); // 송신

        String msg = dis.readUTF();
        System.out.println(msg);
        msg = dis.readUTF();
        System.out.println(msg);
        System.out.print("Name : ");
        String name = sc.nextLine();
        dos.writeUTF(name);
        // 전송쓰레드 생성
        Thread sending = new Thread(new Runnable() { // 익명클래스 기법
            @Override
            public void run() {
                while (true) { // 데이터 송수신을 위한 내용
                    System.out.print("Msg(who#message) : ");
                    String msg = sc.nextLine();
                    try {
                        dos.writeUTF(msg);
                        if(msg.equals("Bye")) break;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        // 수신 쓰레드 생성
        Thread receiving = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        String msg = dis.readUTF();
                        if(msg == null) break; // 상대방이 접속 종료
                        System.out.println(msg);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    s.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
        // 쓰레드 동작
        sending.start();
        receiving.start();
    }
}

UDP

package network.udp;


import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPEchoServer {
    public UDPEchoServer() {
        System.out.println("UDP EchoServer is started. Portno 9001");
        try {
            DatagramSocket socket = new DatagramSocket(9001);
            while (true) {
                // 데이터그램 패킷 수신
                byte[] message = new byte[1024];
                DatagramPacket packet = new DatagramPacket(message, message.length);
                socket.receive(packet);
                String data = new String(packet.getData());
                System.out.println("Received from : [" + data.trim() + "] \n" +
                        "From : " + packet.getAddress()); //원격지 주소 출력
                // 데이터그램 패킷 송신
                InetAddress address = packet.getAddress();
                int port = packet.getPort();
                byte[] sendMessage = data.getBytes();
                DatagramPacket sendPacket = new DatagramPacket(sendMessage, sendMessage.length, address, port);
                socket.send(sendPacket);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new UDPEchoServer();
    }
}
package network.udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class UDPEchoClient {

    public UDPEchoClient() {
        System.out.println("UDP EchoClient is started.");
        Scanner sc = new Scanner(System.in);
        try (DatagramSocket socket = new DatagramSocket()) {
            InetAddress address = InetAddress.getByName("localhost");
            byte[] message;
            while (true) {
                // 데이터 입력해서 데이터그램 패킷으로 만들고 전송
                System.out.println("Enter a message : ");
                String data = sc.nextLine();
                if(data.equalsIgnoreCase("quit")) break;
                message = data.getBytes(); // String -> byte[]
                DatagramPacket packet = new DatagramPacket(message, message.length, address, 9001);
                socket.send(packet); // 데이터그램 전송

                // 서버로부터 수신
                byte[] recvMessage = new byte[1024];
                DatagramPacket recvPacket = new DatagramPacket(recvMessage, recvMessage.length);
                socket.receive(recvPacket);
                data = new String(recvPacket.getData());
                System.out.println("Received : [" +data.trim() +" ] \n" +
                        " From : " + recvPacket.getSocketAddress());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        new UDPEchoClient();
    }
}

Java NIO

image

  • 기존의 쓰레드 방식은 쓰레드가 많아질수록 비효율적이지만, 셀렉터 기반 Channel + Buffer를 통해 해결가능
  • 스트림 방식인 서버소켓이 아닌 NIO에서 지원하는 서버소켓채널을 사용

Buffer

  • I / O 를 위한 메모리 공간
  • 버퍼 생성 (다이렉트 , 넌다이렉트)
    • 다이렉트 : 운영체제가 메모리 할당
      • 공간이 큼 - 메인메모리 전체에서 할당 받기 때문
      • 주로 읽기 / 쓰기가 많은 경우엔 다이렉트 버퍼가 유리
      • ByteBuffer.allocateDirect(20);
    • 넌다이렉트 : JVM이 메모리 할당
      • 공간이 작음 - JVM에서 바로 처리하기 때문에 속도가 빠름
      • 버퍼를 생성, 삭제가 빈번하면 유리
      • ByteBuffer.allocate(20);
  • 버퍼 기록 / 읽기
    • position : pointer와 같은 역할
    • capacity : 전체 공간 , limit : 쓸 수 있는 공간 (처음엔 capacity와 동일)
    • flip : 기록 / 읽기 변환 - 뒤집다라는 의미 , position을 0으로 초기화하고 limit은 현재 적힌 공간만큼으로 바뀜
    • rewind : position을 0으로 초기화
    • clear : position을 0으로 초기화
    • mark : 현재 포인터에 체크 포인트, reset 하면 마지막 mark자리로 position 이동
    • compact : 현재 까지 읽은 데이터를 지우고 앞으로 땡긴다. write상태로 바뀌며 , put 하면 현재포인터에 새 값이 채워짐
    • put (쓰기)
    • get (읽기)
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.IntBuffer;

public class JavaNIO {
    public static void printBuffer(ByteBuffer buffer) {
        // 버퍼에 있는 데이터 출력
        for (int i = 0; i < buffer.limit() - 1; i++) {
            System.out.print(buffer.get(i) + ", ");
        }
        System.out.println(buffer.get(buffer.limit() - 1));
        System.out.print("position : "+buffer.position()+", ");
        System.out.print("limit : " + buffer.limit()+", ");
        System.out.println("capacity : " + buffer.capacity());
    }
    public static void main(String[] args) {
        // 버퍼생성 - allocate 방식
        ByteBuffer buf = ByteBuffer.allocate(20); // 넌다이렉트
        ByteBuffer bufDir = ByteBuffer.allocateDirect(20); //다이렉트
        CharBuffer cbuf = CharBuffer.allocate(20);
        IntBuffer ibuf = IntBuffer.allocate(20);

        // 버퍼생성 - wrap 방식
        byte[] bytes = new byte[50];
        ByteBuffer buf2 = ByteBuffer.wrap(bytes);

        // 읽기/쓰기
        buf.put((byte) 7);
        buf.put((byte) 9);
        buf.put((byte) 13);
        buf.put((byte) 16);
        printBuffer(buf);
        buf.flip(); // 쓰기 -> 읽기, limit 갯수 변경, 포지션 초기화
        printBuffer(buf);
        buf.get(new byte[2]); // 2개 읽기 , position : 2
        printBuffer(buf);
        buf.mark(); // 현재 position 2에 mark기록
        buf.get(new byte[2]); // 2개 읽기, position : 4
        printBuffer(buf);
        buf.reset(); // mark위치로 position 이동
        printBuffer(buf);
        buf.rewind(); // position : 0 초기화
        printBuffer(buf);
        buf.get(new byte[2]); // position : 2
        buf.compact(); // 2개를 지우고 앞으로 땡김, 기록상태로 바뀜
        printBuffer(buf);
        buf.put((byte) 22);
        buf.put((byte) 22);
        buf.put((byte) 22);
        buf.put((byte) 22);
        printBuffer(buf);
        buf.clear();
        printBuffer(buf);
    }
}

TCP (NIO)

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Date;

import static java.lang.Thread.sleep;

public class TimeServer {
    public static void main(String[] args) {
        System.out.println("Time Server is started.");
        try {
            ServerSocketChannel sschannel = ServerSocketChannel.open();
            // blocking 모드로 동작 (클라이언트 연결이 올 때까지 기다림)
            sschannel.configureBlocking(true);
            sschannel.socket().bind(new InetSocketAddress(5001)); // 포트 지정
            while (true) {
                System.out.println("Server is waiting for clients...");
                SocketChannel client = sschannel.accept();
                InetSocketAddress clientAddress = (InetSocketAddress) client.getRemoteAddress();
                System.out.println("Connected. " + clientAddress.getAddress() + " " + clientAddress.getPort());
                // Buffer creation
                ByteBuffer buf = ByteBuffer.allocate(64); // JVM
                for (int i = 0; i < 10; i++) {
                    String message = "Date: " + new Date(System.currentTimeMillis());
                    buf.clear(); // 이전에 입력한 데이터 삭제. position = 0, limit = capacity
                    buf.put(message.getBytes());
                    buf.flip(); // w -> r
                    while (buf.hasRemaining()) client.write(buf);
                    System.out.println("Sent: " + message);
                    sleep(1000);
                }
                client.close();

            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

import static java.lang.Thread.sleep;

public class TimeClient {
    public static void main(String[] args) {
        System.out.println("Time client is started");
        try {
            SocketAddress address = new InetSocketAddress("127.0.0.1", 5001);
            SocketChannel schannel = SocketChannel.open(address);
            System.out.println("Connected.");
            //Buffer creation
            ByteBuffer buffer = ByteBuffer.allocate(64);
            int bytes = schannel.read(buffer); // data receiving -> buffer
            System.out.println(bytes + " bytes are received.");
            while (bytes != -1) {
                buffer.flip(); // w -> r
//                while (buffer.hasRemaining()) System.out.print((char) buffer.get());
                byte[] data = new byte[buffer.limit()];
                buffer.get(data, 0, buffer.limit());
                String msg = new String(data);
                System.out.println(msg);
                sleep(1000);
                buffer.clear();
                bytes = schannel.read(buffer);
                System.out.println(bytes + " bytes are received.");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

UDP(NIO)

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

public class UDPReceiver {
    public static void main(String[] args) throws IOException {
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 5005);
        DatagramChannel channel = DatagramChannel.open();
        channel.configureBlocking(true); // blocking mode
        channel.socket().bind(address);

        ByteBuffer buffer = ByteBuffer.allocate(64);
        while (true) {
            channel.receive(buffer);
            buffer.flip();
            byte[] data = new byte[buffer.limit()];
            buffer.get(data, 0, buffer.limit());
            String msg = new String(data);
            if(msg.equalsIgnoreCase("quit")) break;
            System.out.println(msg);
            buffer.clear();
        }
    }
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Scanner;

public class UDPSender {
    private static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) throws IOException {
        DatagramChannel channel = DatagramChannel.open();
        ByteBuffer buffer = ByteBuffer.allocate(64);
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 5005);
        while (true) {
            String msg;
            System.out.print("Message: ");
            msg = sc.nextLine();
            buffer.put(msg.getBytes());
            buffer.flip();
            channel.send(buffer, address);
            buffer.clear();
            if (msg.equalsIgnoreCase("quit")) break;
        }
    }
}
반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^