본문 바로가기

언어/Java

서버 멀티 스레드

※ 클라이언트 별 메인에 의해 서버사이드에 소켓 만들어지고, 새로운 스레드 시작
각각 스레드는 소켓과 연결된 스트림을 통해 데이터 주고받는 구조
초록색 새로운 스레드가 할 일
→ 메인스레드 1개 새로운 스레드 3개 생성되서 서버에는 총 4개의 스레드

 

package com.my.tcp.server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.BindException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
class ServerThread extends Thread{
	private Socket s;
	private InputStream is = null;
	private DataInputStream dis =null;
	private OutputStream oos = null;
	private DataOutputStream dos = null;
	private List<ServerThread> list;	// 일반화된
//	private ArrayList<ServerThread> list;
//	private Vector<ServerThread> list; 
	private String clientIP;
	ServerThread(Socket s, 
			List<ServerThread> list
			// ArrayList<ServerThread> list
			//Vector<ServerThread> list
			) throws IOException{
		this.s = s;
		this.list = list;
		is = s.getInputStream();
		dis = new DataInputStream(is);		
		oos = s.getOutputStream();
		dos = new DataOutputStream(oos);
		InetAddress ip = s.getInetAddress(); //클라이언트IP정보
		clientIP = ip.getHostAddress();
		System.out.println(clientIP+"클라이언트가 접속했습니다");
//		for(ServerThread t: list) {
//			try {
//				t.dos.writeUTF(clientIP + "님이 접속했습니다");
//			}catch(IOException e) {
//			}
//		}
		broadcast(clientIP + "님이 접속했습니다");
	}
	
	@Override
	public void run() { // throws IOException 못하는 이유
		try {
			String receiveMsg;
			while(  !(receiveMsg = dis.readUTF()).equals("quit")           ) {
				//System.out.println("클라이언트가 보낸 메시지:" + receiveMsg);
				//dos.writeUTF(receiveMsg);
				for(ServerThread t: list) {
					t.dos.writeUTF(clientIP + ">" + receiveMsg);
				}
				broadcast(clientIP + ">" + receiveMsg);
			}
		} catch(EOFException e) {
		}catch(IOException e) {
			e.printStackTrace();
		}finally {
			System.out.println("클라이언트와의 연결이 종료되었습니다");
//			for(ServerThread t: list) {
//				try {
//					t.dos.writeUTF(clientIP + "님이 나갔습니다");
//				}catch(IOException e) {
//				}
//			}
			broadcast(clientIP + "님이 나갔습니다");
			if(s != null) {
				try {
					s.close();
				} catch (IOException e) {
				}
			}
		}
	}
	
	private void broadcast(String msg) {
		for(ServerThread t: list) {
			try {
				t.dos.writeUTF(msg);
			}catch(IOException e) {
			}
		}
	}
}
public class ServerMultiThread {
	public static void main(String[] args) {
		int port = 5432;
		ServerSocket ss = null;
		Socket s = null;
		List<ServerThread> list = new Vector<>(); // new ArrayList<>(); // 일반화된 인터페이스타입, 이걸 사용하면 모든 부분 수정하는게 아니라
																		//생성 객체만 바꾸면 된다
//		ArrayList<ServerThread> list = new ArrayList<>(); // 구체화된 클래스타입
//		Vector<ServerThread> list = new Vector<>();		// 일반화된 리스트타입

		try {
			//while(){
			ss = new ServerSocket(port);
			System.out.println("클라이언트 접속을 기다리기");
			while(true){
				s = ss.accept();
				ServerThread t = new ServerThread(s, list);
				list.add(t);
				t.start();
			}	
		} catch(BindException e) {
			System.out.println(port +"포트가 이미 사용중입니다");
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}
}
package com.my.tcp.client;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Scanner;

class ClientThread extends Thread{
	private Socket s;
	private InputStream is = null;
	private DataInputStream dis = null; 
	
	ClientThread(Socket s) throws IOException {
		this.s = s;
		is = s.getInputStream();
		dis = new DataInputStream(is);
	}
	
	@Override
	public void run() {
//		while(true) {
//			try {
//				String receiveMsg;
//				receiveMsg = dis.readUTF();	// while 반복문 안에서 catch 한번 들어가면 망가짐. 소켓 한번 망가지면 복구 안됨
											// 그래서 아래처럼 try catch를 밖으로 꺼냄
//			} catch (IOException e) {
//				e.printStackTrace();
//			}
//		}
		
		try {
			while(true) {
				String receiveMsg;
				receiveMsg = dis.readUTF();
				System.out.println("서버가 되돌려준 메시지:" + receiveMsg);
			}
		} catch (SocketException e) {
		} catch (EOFException e) {
		} catch (IOException e) {
				e.printStackTrace();
		}					
	}
}

public class ClientMultiThread {

	public static void main(String[] args) {
		Socket s = null;	// 네트워크는 무조건 혼자 test해봐야함
//		String serverIP = "127.0.0.1"; // localhost, ip는 cmd창에서 확인
//		String serverIP = "192.168.1.83";	// 옆자리 컴퓨터
		String serverIP = "192.168.1.84";	// 교수님 컴퓨터
		int serverPort = 5432;
		OutputStream oos = null;
		DataOutputStream dos = null;
		
		Scanner sc = new Scanner(System.in); 
		try {
			s = new Socket(serverIP, serverPort);
			System.out.println("서버와 연결 성공");
			
			new ClientThread(s).start(); // 새로운 스레드 시작
				
			oos = s.getOutputStream();	
			dos = new DataOutputStream(oos); 
			
			String sendMsg;
			do {
				System.out.print("서버로 보낼 메시지 (종료하려면 quit을 입력하세요.):");
				sendMsg = sc.nextLine();
				dos.writeUTF(sendMsg);

			} while (!sendMsg.equals("quit"));
			
		} catch (EOFException e) {
			e.printStackTrace();
		} catch (UnknownHostException e) {
			System.out.println(serverIP + " 서버는 존재하지 않습니다. 서버IP를 확인하세요");
		} catch (ConnectException e) {
			System.out.println("서버가 실행되지 않았습니다. 서버실행을 확인하세요");
		} catch (SocketException e) {
			System.out.println("서버가 강제종료되었습니다. 서버확인하세요");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(s != null) {
				try {
					s.close();	
				} catch (IOException e) {
				}
			}
		}
	}
}

 

※ // 반복의 시작점
1번째는 포트를 또 여는것. 
2번째 클라이언트 접속 기다리는것만 다시 반복해주면 된다.
ctrl+c 로 강제종료하면 catch나 finally 처리 못해줌
while(true) {
    s = ss.accept();
    new ServerThread().start();

이건 예외 발생안하는 이상 finally 못만난다. 무한반복이라

 

※ 예외처리 throws아니면 try catch

@Override
public void run() { // throws IOException 못하는 이유
부모쪽에는 throws 안되있어서 자식쪽에서도 throws 하면 안됨
부모에서 선언한 예외만 자식 쪽에서 못함

해결방법은 여기서는 try-catch로
while 반복문 내부에서 try-catch 하면 반복문이 계속 돈다
여기서는 예외발생할 경우 소켓이 망가져버리면 입출력이 망가짐, 1번 망가진 소켓과 연결. 계속 예외가 발생한다

while 반복문 밖에서 try-catch 하면 while 반복문 내부에서 예외발생할 경우
반복문 빠져나와서 가장 가까운 예외처리로 감. 반복문 빠져나감

 

※ ServerMultiThread 여러명 동시 접속 가능
java -cp bin com.my.tcp.server.ServerMultiThread
java -cp bin com.my.tcp.client.ClientMultiThread

 

새로운 스레드의 2번이 Broadcast, 보내진 메시지 하나가 접속한 모든 클라이언트에게 전달하는 것 writeUTP() ; 그 소켓에 보내기 가능&nbsp;스레드를 생성 삭제할때 cpu 점유율을 다른 스레드에 뺏기지마라

※ 컴파일시 autoboxing

List<int> list; // X
List<Integer< list;
list = new ArrayList();
list.add(3); // Integer.valueOf(3) 으로 AutoBoxing

'언어 > Java' 카테고리의 다른 글

스트림  (0) 2023.08.20
네트워크 예제 4  (0) 2023.08.20
네트워크 예제 2  (0) 2023.08.16
네트워크 예제 1  (0) 2023.08.16
네트워크  (0) 2023.08.16