[Java] Networking, Socket, Threadpool, 크롤링(Crawling)

JH·2023년 3월 31일

Java

목록 보기
8/21

1. TIL

A. Networking

1. 클라이언트/서버(client/server)

서비스를 제공하는 쪽이 서버, 제공받는 쪽이 클라이언트

서버기반 모델 : 전용서버를 두는 것
P2P 모델 : 전용서버없이 각 클라이언트가 서버역할까지 동시에 수행하는 것

2. IP주소 (IP address)

  • 컴퓨터(host, 호스트)를 구별하는데 사용되는 고유한 주소값, 4byte의 정수 (32bit)

  • IP주소는 네트워크주소와 호스트주소로 구성됨

  • 네트워크주소가 같은 두 호스트는 같은 네트워크에 존재함

  • IP주소와 서브넷마스크를 &연산하면 네트워크주소를 얻을 수 있음

IPv4 : 32bit

  • IPv4주소는 네트워크의 크기나 호스트의 수에 따라 A, B, C, D, E 클래스로 나누어짐

IPv6 : 128bit

http Default port : 80
https Default port : 443

Ex) InetAddress : IP주소를 다루기 위한 클래스

public class IPTest {
	public static void main(String[] args) {
//		// IP
		try {
//			// 로컬 IP
			InetAddress local = InetAddress.getLocalHost();
			System.out.println(local); // 사용자의 로컬IP
			
			// 외부 IP
			InetAddress remote = InetAddress.getByName("naver.com");
			InetAddress[] remote2 = InetAddress.getAllByName("naver.com");
			
			System.out.println(remote); // naver.com/223.130.200.104
			System.out.println(remote2); // 배열
			
			for(InetAddress address : remote2) {
				System.out.println(address);
			}
            
			// URL
			URL url = new URL("https://naver.com/index.html");
			System.out.println(url.getDefaultPort()); // 443	
		
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}
}

3. URL(Uniform Resource Location)

URL : 인터넷에 존재하는 서버들의 자원(css, js, media)에 접근할 수 있는 주소

프로토콜 : 자원에 접근하기 위해 서버와 통신하는데 사용되는 통신규약 (http)
호스트명 : 자원을 제공하는 서버의 이름
포트번호 : 통신에 사용되는 서버의 포트번호
경로명 : 접근하려는 자원이 저장된 서버상의 위치
파일명 : 접근하려는 자원의 이름
쿼리 (query) : URL에서 ? 이후 부분, key = value로 구성

참조 (anchor) : URL에서 # 이후 부분



B. 소켓 프로그래밍

1. Socket

소켓 : 프로세스간의 통신에 사용되는 양쪽 끝단 (end point)
Socket : Client의 소켓
ServerSocket : Server의 소켓


2. TCP와 UDP

TCP : 연결기반 (연결 후 통신), 1:1 통신 / chat service
UDP : 비연결기반, n:n 통신 / streaming service


Ex) Clicent

public class Client {

	public static void main(String[] args) {
		// socket
		Socket socket = null;
		try {
			socket = new Socket();
			// step01 : 연결 요청
			System.out.println("연결 요청 중입니다.");
			socket.connect(new InetSocketAddress("localhost", 9999));
			System.out.println("서버에 접속했습니다.");
			
			// step02 : 통신1 (클라이언트 -> 서버) 데이터 전송
			OutputStream os = socket.getOutputStream(); // 준비
			
			String msg = "오늘 금요일이니까 일찍 끝내요";
			
			os.write(msg.getBytes()); // 문자 -> byte변환
			os.flush();
			System.out.println("메세지 전송이 완료되었습니다.");
			
			// step03 : 통신2 (서버 -> 클라이언트)
			InputStream is = socket.getInputStream();
			
			byte[] responseMsg = new byte[100];
			int countByte = is.read(responseMsg);
			
			String msg2 = new String(responseMsg, 0, countByte, "UTF-8");
			System.out.println("메세지를 정상적으로 받았습니다: " + msg2);
			
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

Ex) Server

public class Server {

	public static void main(String[] args) {
		// serverSocket
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket();
			// bind() : 서버의 ip주소와 port설정하고 통신 시작
			serverSocket.bind(new InetSocketAddress("localhost", 9999));
			
			while(true) {
				// step01 : 연결
				Socket socket = serverSocket.accept();
				InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
				System.out.println(isa.getHostName() + "님이 입장하셨습니다.");
			
				// step02 : 통신1 (클라이언트 -> 서버 : 메세지를 확인)
				InputStream is = socket.getInputStream();
				
				byte[] requestMsg = new byte[100];
				int countByte = is.read(requestMsg);
				
				String msg = new String(requestMsg, 0, countByte, "UTF-8");
				System.out.println("메세지를 정상적으로 받았습니다: " + msg);
				
				// step03 : 통신2 (서버 -> 클라이언트)
				OutputStream os = socket.getOutputStream();
				String severMsg = "서버에서 보내는 메세지 입니다.";
				
				os.write(severMsg.getBytes());
				os.flush();
				System.out.println("서버 메세지 전송 완료");
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				serverSocket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

3. Threadpool

스레드의 집합
Vector : ArrayList와 비슷하지만 Synchrolized를 안써도 안정성이 보장됨

Ex) Client

public class Client{
	static Socket socket;
	
	static void startClient() {
		Thread thread = new Thread() {
			@Override
			public void run() {
				try {
					socket = new Socket(); // 소켓 생성 및 연결 요청
					socket.connect(new InetSocketAddress("localhost", 8888));
				} catch(Exception e) {
					System.out.println("[서버와 통신이 불가능 합니다]");
					
					if(!socket.isClosed()) { 
						stopClient(); 
					}
					return;
				}
				receive(); // 서버에서 보낸 데이터 받기
			}
		};
		thread.start(); // 스레드 시작
	}
	
	static void stopClient() {
		try {
			System.out.println("[클라이언트 연결을 종료합니다]");
			if(socket!=null && !socket.isClosed()) {
				socket.close(); // 연결 끊기
			}
		} catch (IOException e) {}
	}	
	
	static void receive() {
		while(true) {
			try {
				byte[] byteArr = new byte[100];
				InputStream inputStream = socket.getInputStream();
				
				// 데이터 입력
				int readByteCount = inputStream.read(byteArr);
				
				// 정상적으로 close()
				if(readByteCount == -1) { 
					throw new IOException(); 
				}
				
				// 문자열로 변환
				String data = new String(byteArr, 0, readByteCount, "UTF-8");
				
				System.out.println("[받은 데이터] "  + data);
			} catch (Exception e) {
				System.out.println("[서버와 통신이 불가능 합니다]");
				stopClient();
				break;
			}
		}
	}
	
	static void send(String data) {
		// 스레드 생성
		Thread thread = new Thread() {
			@Override
			public void run() {
				try {
					// 데이터 전송
					byte[] byteArr = data.getBytes("UTF-8");
					OutputStream outputStream = socket.getOutputStream();
					
					// 데이터 쓰기
					outputStream.write(byteArr);
					outputStream.flush();
					System.out.println("[데이터 전송 완료]");
				} catch(Exception e) {
					System.out.println("[서버와 통신이 불가능 합니다]");
					stopClient();
				}				
			}
		};
		thread.start();
	}
	
	public static void main(String[] args) {
		startClient();
		while(true) {
			Scanner sc = new Scanner(System.in);
			String msg = sc.nextLine();
			// Stop Client 입력 : 클라이언트 연결 종료
			if(msg.equals("Stop Client"))
				break;
			send(msg);
		}
		stopClient();
	}
}

Ex) Server

public class Server{
	// 스레드풀
	static ExecutorService executorService;
	static ServerSocket serverSocket;
	static List<Client> connections = new Vector<Client>();
	
	// 서버 시작
	static void startServer() {
		// 스레드풀 생성
		executorService = Executors.newFixedThreadPool(
			Runtime.getRuntime().availableProcessors()
	    );
		
		// 서버 소켓 생성 및 바인딩
		try {
			serverSocket = new ServerSocket();		
			serverSocket.bind(new InetSocketAddress("localhost", 8888));
		} catch(Exception e) {
			if(!serverSocket.isClosed()) { 
				stopServer(); 
			}
			return;
		}
		
		// 수락 작업 생성
		Runnable runnable = new Runnable() {
			@Override
			public void run() {	
				System.out.println("[서버를 시작합니다]");
				while(true) {
					try {
						// 연결 수락
						Socket socket = serverSocket.accept();
						System.out.println("[연결을 수락합니다 : " + socket.getRemoteSocketAddress()  + ": " + Thread.currentThread().getName() + "]");		
						// 클라이언트 접속 객체 저장
						Client client = new Client(socket);
						connections.add(client);
						System.out.println("[연결된 클라이언트 수: " + connections.size() + "]");
					} catch (Exception e) {
						if(!serverSocket.isClosed()) { 
							stopServer(); 
						}
						break;
					}
				}
			}
		};
		// 스레드풀의 작업 처리
		executorService.submit(runnable);
	}
	
	// 서버 종료
	static void stopServer() {
		try {
			// 모든 소켓 close
			Iterator<Client> iterator = connections.iterator();
			while(iterator.hasNext()) {
				Client client = iterator.next();
				client.socket.close();
				iterator.remove();
			}
			// 서버 소켓 close
			if(serverSocket!=null && !serverSocket.isClosed()) { 
				serverSocket.close(); 
			}
			// 스레드풀 종료
			if(executorService!=null && !executorService.isShutdown()) { 
				executorService.shutdown(); 
			}
			System.out.println("[서버 멈춤]");
		} catch (Exception e) { }
	}	
	
	// 클라이언트
	static class Client {
		Socket socket;
		
		Client(Socket socket) {
			this.socket = socket;
			receive();
		}
		
		// 받기
		void receive() {
			// 받기 작업 생성
			Runnable runnable = new Runnable() {
				@Override
				public void run() {
					try {
						while(true) {
							byte[] byteArr = new byte[100];
							InputStream inputStream = socket.getInputStream();
							
							// 데이터 받기
							int readByteCount = inputStream.read(byteArr);
							
							// 클라이언트 Socket의 close()
							if(readByteCount == -1) {  
								throw new IOException(); 
							}
							
							System.out.println("[요청 처리 : " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]");
							
							// 데이터 변환
							String data = new String(byteArr, 0, readByteCount, "UTF-8");
							
							// Stop Server 요청시 서버 종료
							if(data.equals("Stop Server")) {
								stopServer();
							}
							
							// 모든 클라이언트에게 데이터 전송
							for(Client client : connections) {
									client.send(data); 
							}
						}
					} catch(Exception e) {
						try {
							connections.remove(Client.this);
							System.out.println("[클라이언트 통신 불가능 : " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]");
							socket.close();
						} catch (IOException e1) {}
					}
				}
			};
			// 스레드풀에서 처리
			executorService.submit(runnable);
		}
		
		// 보내기
		void send(String data) {
			// 보내기 작업 생성
			Runnable runnable = new Runnable() {
				@Override
				public void run() {
					try {
						// 데이터 보내기
						byte[] byteArr = data.getBytes("UTF-8");
						OutputStream outputStream = socket.getOutputStream();
						// 데이터 쓰기
						outputStream.write(byteArr);
						outputStream.flush();
					} catch(Exception e) {
						try {
							System.out.println("[클라이언트 통신 불가눙 : " + socket.getRemoteSocketAddress() + ": " + Thread.currentThread().getName() + "]");
							connections.remove(Client.this);
							socket.close();
						} catch (IOException e2) {}
					}
				}
			};
			// 스레드풀에서 처리
			executorService.submit(runnable);
		}
	}
	
	public static void main(String[] args) {
		startServer();
	}
}


C. 크롤링 (crawling)

jsoup, lombok 라이브러리를 이용함

크롤링 시 유의 사항 (구글)
https://developers.google.com/search/docs/crawling-indexing/robots/create-robots-txt?hl=ko

Document : 문서
Element(s) : 요소 (다수의 요소)
select, getElements 는 복수 형태로 (전부) 가져옴

메소드

  • text() : 텍스트를 가져옴
  • attr() : 자원에 대한 경로를 가져옴
  • absUrl() : 절대 경로를 가져옴

순서 : url과 Jsoup을 연결 후 변수 할당 -> 공통 요소 copy select, Element로 초기화 -> for문 (text, attr, absUrl 알맞게 사용)

Ex) Crawling (lib jsoup)

// import 생략
public class JsoupTest {

	public static void main(String[] args) {
		Document doc = null;
		
		try {
//			 step01 : 기본 사용법
			doc = Jsoup.connect("https://en.wikipedia.org/wiki/Main_Page").get();
			System.out.println(doc.title());
			System.out.println(doc.getElementsByTag("title").get(0).text());
			System.out.println(doc.select("head > title"));
			System.out.println(doc.select("#In_the_news"));
			System.out.println(doc.select(".mw-headline"));
			
			Elements inTheNews = doc.select("#mp-itn > ul > li > b > a");
			System.out.println(inTheNews);
			
			for (Element news: inTheNews) {
				System.out.println(news.attr("href"));
				System.out.println(news.absUrl("href"));
			}
			

			// step02
			doc = Jsoup.connect("https://www.w3schools.com/java/default.asp").get();
	
			Elements h2Texts = doc.select("#main > h2");
			
			for (Element h2text : h2Texts) {
				System.out.println(h2text.ownText());
			}
			
			// step03 : 원하는 태그 출력, 객체로 저장하여 출력
			doc = Jsoup.connect("https://sports.news.naver.com/wfootball/index.nhn").get();
			Elements homeNews = doc.select(".home_news");
			
			
			System.out.println("-------------------------------------");
			System.out.println("제목 : " + homeNews.get(0).text().substring(0, 4));
			System.out.println("-------------------------------------");

			ArrayList<FootballNews> news = new ArrayList<FootballNews>();
			
			for (Element newsList : homeNews.select("li")) { 
				String newsTitle = newsList.text();
				String newsUrl = newsList.getElementsByTag("a").get(0).absUrl("href");
				
				System.out.println(newsTitle);
				System.out.println(newsUrl);
				System.out.println();
				
				news.add(new FootballNews(newsTitle, newsUrl));
			}
			
			System.out.println(news);
			System.out.println("-------------------------------------");

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

Ex) model.domain

package model.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@AllArgsConstructor
@ToString

public class FootballNews {
	private String title;
	private String url;	
}


3. 보완 해야 할 것

Socket에 대한 부분은 정말 많이 사용될 것으로 보임
Thread pool은 구조를 이해하는 것으로 끝나서 사용되는 개념을 다시 공부하면 좋을 것 같음
서버와 클라이언트로 나뉘게 되므로 전체적인 로직?을 설계, 이해 할 줄 알아야 될 것 같음


4. 느낀점

Thread는 정말 까다로운 것 같다.
소켓으로 클라이언트를 3개 까지 늘렸더니 정말 채팅을 구현한 것 같았다.
Thread pool 이나 Network 기본 지식을 좀 더 찾아봐야겠다.

https://xn--3e0bx5euxnjje69i70af08bea817g.xn--3e0b707e/
한국인터넷정보센터 인데 나름 배경지식들을 찾을 수 있었다.

profile
잘해볼게요

0개의 댓글