Daily coding

Thread 사용한 소켓 프로그래밍 예제 - server 본문

Language/Java_basic

Thread 사용한 소켓 프로그래밍 예제 - server

sunnnkim 2019. 12. 11. 18:27

Thread 사용한 소켓 프로그래밍 예제 

 

 

 

< Server > 

 

1. main 클래스

- 필요한 변수

1) 문지기 소켓 : ServerSocket

- 특정 port 번호를 설정하고, 해당 포트(서버)에 접속하는 클라이언트를 확인한다.

- accept( ) : 문지기 소켓의  listen과 binding 기능을 한꺼번에 처리해주고 클라이언트가 있는 지 확인한다

               접속하는 클라이언트가 생기면 Socket 타입의 담당자 소켓을 생성한다.

               생성된 담당자 소켓은 Arraylist에 저장하여 관리하도록 한다.

- 클라이언트가 들어오면 클라이언트로부터 오는 메세지를 받기 위한 스레드를 생성하고

   while문이 실행되어 새로운 클라이언트를 대기한다.

 

2) ServerThread 초기화

- 서버에 클라이언트가 들어오면 ServerThread가 생성되고, 메세지를 대기한다.

 

package day17.Lecture01_thread.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Server_mainClass {
	public static void main(String[] args) throws IOException {

		// 서버의 메인 화면
		
	Socket clientSocket = null; 
    // Client Socket, 담당자 소켓 , 접속하는 클라이언트의 정보를 받아서 가지고 있음
	
	
    // 포트넘버 9000을 지키고 있는 문지기소켓 serSocket
	ServerSocket serSocket = new ServerSocket(9000);
	
	
    // 접속하는 클라이언트의 정보를 담고 있는 리스트 만들기
	List<Socket> list = new ArrayList<Socket>();
	
	
	// while 루프가 시작되는 부분 : 
	while(true) { // 이렇게 하면 계속대화를 할 수 있음
		
		System.out.println("접속대기중...");
		// 접속 확인 ...
		
		clientSocket = serSocket.accept();
		// 클라이언트와 연결이 되면 밑으로 넘어간다.
		// 확인 작업
		System.out.println("client IP : " + clientSocket.getInetAddress()
					+ " Port: " + clientSocket.getPort());
				
		
		// 접속한 클라이언트의 정보를 list에 담아준다.
		list.add(clientSocket);
		
		
		
		
		// 서버의 send와 receive는 스레드로 만들어 주어야 한다.
		// 1. receive 스레드
		// 스레드는 반드시 start 메소드를 실행해줘야 한다.
		new ServerThread(clientSocket, list).start();
		
		// 다른 클라이언트와 대화하기 위해서는 다른 클라이언트의 소켓 주소를 알아야 한다.
		// 리스트에 담긴 클라이언트의 정보를 확인한다.
		
		
	}
	

	}
}

 

 

 

 

 

2) ServerThread

 

- 생성자 : 서버 메인 클래스에서 클라이언트 담당자 소켓과 담당자소켓이 담긴 Arraylist를 파라미터로 받아온다.

- run() : 스레드가 시작되면 (start) 반드시 실행되는 메소드를 오버라이딩한다.

             

BufferedReader reader =  new BufferedReader(new InputStreamReader(socket.getInputStream()));

- 클라이언트로부터 전달받은 데이터를 담당자 소켓으로부터 꺼내온다 ( getInputStream)

- BufferedReader를 객체를 생성해서 받아온 데이터를 읽을 준비를 한다.

- readLine( ) 메소드를 통해서 데이터를 읽고 String 변수에 담아준다.

 

 

 

* 클라이언트가 보낸 메세지를 다시 클라이언트에게 보내기

- 클라이언트로 부터 받은 메세지를 다른 클라이언트에게 보내준다 ( 채팅창에 텍스트를 뿌리는 것과 같은 행위 )

- 데이터를 보낸 클라이언트도 메세지를 받거나 받지 않도록 설정할 수 있다.

< 코드 분석 >

PrintWriter writer = new PrintWriter(sock.getOutputStream());

- 담당자 소켓이 외부 데이터를 받아들이는 getOutputStream를 담은 문자열 데이터를 쓸 수 있는 PrintWriter 클래스 객체를 생성

- 객체 writer를 통해 문자열을 담당자 소켓으로 보낼 수 있다.

- 데이터를 보내기 위해서는 반드시 flush()를 걸어 데이터를 보내주어야 한다 ( 아니면 오류 발생 )

 

// 본인은 제외하고 싶으면 if문으로 걸어준다.
if(socket != sock) { <---- 어레이 리스트를 담은 foreach 문을 돌릴 때, 자기 자신은 제외한다. (자세한 코드는 아래)
      PrintWriter writer = new PrintWriter(sock.getOutputStream());
      writer.println(str); // 실질적으로 전송이 되는 문자열
      writer.flush(); // 끝맺음, 반드시 해줘야함
  }

 

 

package day17.Lecture01_thread.server;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.List;

public class ServerThread extends Thread {
	// 서버에서 받아올 소켓 필드
	Socket socket;
	List<Socket> list;

	// 생성자에 접속한 클라이언트 담당자 소켓과,
	// 접속한 모든 클라이언트 정보가 담긴 리스트를 외부로부터 가지고 온다.
	public ServerThread(Socket socket,List<Socket> list) {
	
		this.socket = socket;
		// 다른 클라이언트에 메세지를 보낼 수 있도록 
		// 서버에 접속한 클라이언트 정보를 담고 있는 리스트를 넘겨받는다.
		this.list = list;
		
	}

	
	// 스레드 작동 시 돌아갈 메소드 오버라이딩
	@Override
	public void run() {
		super.run();
		try {
			// 쓰레드를 계속 돌리기 위해 무한루프 
			while (true) {
			// 1. receive
			// while을 통해 담당자소켓이 정보를 계속 받는다.
			BufferedReader reader = 
                 new BufferedReader(new InputStreamReader(socket.getInputStream()));

			String str = reader.readLine();
			System.out.println("서버로부터 받은 메세지 : " + str);

				
			// 2. send
			for (Socket sock : list) {
					
//				// 본인도 보내고 싶으면 아래의 코드로 
//				PrintWriter writer = new PrintWriter(sock.getOutputStream());
//				writer.println(str); // 실질적으로 전송이 되는 문자열
//				writer.flush(); // 끝맺음, 반드시 해줘야함
				
				// 본인은 제외하고 싶으면 if문으로 걸어준다.
				if(socket != sock) {
					PrintWriter writer = new PrintWriter(sock.getOutputStream());
					writer.println(str); // 실질적으로 전송이 되는 문자열
					writer.flush(); // 끝맺음, 반드시 해줘야함
                 }
					
		}
				
				
				
				// CPU 부하가 안걸리고 제대로 돌아가기 위해서는 sleep() 걸어주어야 함
				Thread.sleep(300); // = 30 프레임, 30번 갱신(가장 좋음)
			}
			
			
		}  catch (Exception e) {
			// 클라이언트가 접속을 종료하면 발생함
			System.out.println("연결이 끊긴 IP: " + socket.getInetAddress());
			list.remove(socket);
			
			// 남은 클라이언트 확인
			for (Socket s : list) {
				System.out.println("접속되어있는 IP: " + s.getInetAddress()+" Port : " + s.getPort());
			}
			try {
				socket.close();
			} catch (Exception e2) {
				e2.printStackTrace();
			}
		}
	}

}