Socket with Multi-Thread in Java

Hawaii·2021년 12월 26일
1

Networking

목록 보기
2/2
post-thumbnail
post-custom-banner

깃헙 : https://github.com/Dev-JiwonShin/Socket-java

1대1 소켓 프로그래밍에 대한 예제는 인터넷에 많기 때문에, TCP기반 비동기식 1대N 소켓 어플리케이션을 구현해보았다. 1대N은 1개의 서버가 N개의 클라이언트와 네트워킹 하는 걸 의미한다. 급한 사람은 바로 깃헙으로 가서 클론하여 사용하면 된다.

처음부터 끝까지 설명하면 지루해지기 때문에, 핵심만 집어서 설명하겠다.




비동기 양방향 네트워킹을 위해, 소켓 프로그래밍에선 '서버'와 '클라이언트'가 필요하다. 1대N 소켓을 구현하려면, 클라이언트보다는 서버가 할 일이 많다. 우선 여러 대의 클라이언트를 비동기로 처리하기 위해선 Thread 처리를 해야한다.

여기서 스레드 처리는 이중으로 해야하는데,
1) ConnectThread : 서버와 클라이언트를 연결한 스레드
2) ClientThread : 1)의 스레드를 기반으로 클라이언트가 활동할 수 있는 스레드

이 두개가 필요하다. Server.java라는 큰 범위에서 Connect를 먼저하고, 각각의 connect에 대해 클라이언트가 활동할 자리(스레드)를 만들어주는 느낌으로 이해하면된다.

스레드를 이중으로 구현해야하는 서버와 달리 반면 클라이언트는 서버에게 '나 이런 값 너한테 보낸다아~~~'를 보내고, 서버의 응답을 처리하면 끝이다.

Server.java

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

class ClientThread extends Thread
{
    Socket socket;
    int id;
    
    ClientThread (Socket socket, int id)
    {
        this.socket = socket;
        this.id = id;
    }
    
    @Override
    public void run ()
    {
        try
        {
            while (true)
            {
                InputStream IS = socket.getInputStream();
                byte[] bt = new byte[256];
                int size = IS.read(bt);
                
                String output = new String(bt, 0, size, "UTF-8");
                System.out.println("Thread " + id + " >  " + output);
            }
        } catch (IOException e)
        {
            System.out.println("    Thread " + id + " is closed. ");
        }
    }
}

class ConnectThread extends Thread
{
    ServerSocket serverSocket;
    int count = 1;
    
    ConnectThread (ServerSocket serverSocket)
    {
        System.out.println(Server.getTime() + " Server opened");
        this.serverSocket = serverSocket;
    }
    
    @Override
    public void run ()
    {
        try
        {
            while (true)
            {
                Socket socket = serverSocket.accept();
                System.out.println("    Thread " + count + " is started.");
                ClientThread clientThread = new ClientThread(socket, count);
                clientThread.start();
                count++;
            }
        } catch (IOException e)
        {
            System.out.println("    SERVER CLOSE    ");
        }
    }
}

public class Server
{
    public static void main (String[] args)
    {
        Scanner input = new Scanner(System.in);
        ServerSocket serverSocket = null;
        try
        {   // 서버소켓을 생성, 7777 포트와 binding
            serverSocket = new ServerSocket(7777); // 생성자 내부에 bind()가 있고, bind() 내부에 listen() 있음
            ConnectThread connectThread = new ConnectThread(serverSocket);
            connectThread.start();
            
            int temp = input.nextInt(); // 스레드 생성 전에 숫자를 입력하면 바로 SERVER CLOSE!
        } catch (IOException e)
        {
            e.printStackTrace();
        }
        try
        {
            serverSocket.close();
        } catch (Exception e)
        {
            System.out.println(e);
        }
    }
    
    static String getTime ()
    {
        SimpleDateFormat f = new SimpleDateFormat("[hh : mm : ss ]");
        return f.format(new Date());
    }
}

Client.java

import java.io.*;
import java.net.ConnectException;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

public class Client
{
    public static void main (String[] args)
    {
        Scanner input = new Scanner(System.in);
        
        String serverIp = "127.0.0.1";
        System.out.println("Connecting to server. Server IP :" + serverIp);
        
        try
        {
            // 소켓을 생성하여 연결을 요청한다.
            Socket socket = new Socket(serverIp, 7777);
            
            while (true)
            {
                System.out.println("> ");
                String msg = input.nextLine();
                byte[] arrayStream = msg.getBytes(StandardCharsets.UTF_8);
                OutputStream outputStream = socket.getOutputStream();
                outputStream.write(arrayStream);
            }
        } catch (ConnectException ce)
        {
            ce.printStackTrace();
        } catch (IOException ie)
        {
            ie.printStackTrace();
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

build.gradle

plugins {
    id 'application'
    id 'java'
    id 'idea'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
}

test {
    useJUnitPlatform()
}

startScripts.enabled = false


task Server(type: CreateStartScripts)
{
    mainClass = 'Server'
    applicationName = 'server'
    outputDir = new File(project.buildDir, 'tmp')
    classpath = startScripts.classpath
}
task Client(type: CreateStartScripts)
{
    mainClass = 'Client'
    applicationName = 'client'
    outputDir = new File(project.buildDir, 'tmp')
    classpath = startScripts.classpath
}

applicationDistribution.into('bin') {
    from(Server)
    from(Client)
    fileMode = 0755
}
profile
참을 수 없는 괴짜의 가벼움
post-custom-banner

0개의 댓글