본문 바로가기
Backend

06월 18일 화 | SW활용 02 - 웹서버, JAVA로 채팅프로그램 만들기

by 구라미 2019. 6. 18.

1. 웹서버

웹 환경을 이해하기 위한 요소

1. SERVER, CLIENT의 관계

2. HTTP라는 약속

3. 서버와 클라이언트 간 request, response를 통해 소통한다.

 

Server

- 요청을 받고 응답을 할 수 있는 시스템

- Web Server : 웹브라우저를 통해 웹서비스를 제공

- 종류 :

IIS (MS기반 언어(ASP, .net))

Tomcat (JSP,무료)

JBoss/Resin (JSP,유료)

Linux 기반  (PHP,무료)

등이 있다. 

 

 

Client

요청하는 시스템

 

Database Server

대용량 저장장소

- 관계형 데이터베이스 : ORACLE DB, MySQL DB, Maria DB, MS-SQL DB, OrientDB, SQLite

- No SQL 데이터베이스 : MongoDB, 카산드라

 

Mail Server

대용량

 

 


 

 

Java로 채팅프로그램 만들기

1. 소켓 프로그래밍

- 소켓을 통한 통신 프로그래밍

- 소켓은? : 프로세스 간의 통신에 사용되는 양쪽 끝단을 의미.

- java.net패키지를 통해 소켓프로그래밍을 지원. TCP와 UDP를 이용한 소켓프로그래밍이 대표적.

 

IP: 주소는 전 세계 컴퓨터에 부여되는 유일한 식별자이다. IP는 각 나라의 공인 기관에서 할당하고 관리하는데, 우리나라의 경우에는 한국인터넷진흥원(www.krnic.or.kr) 에서 관리한다.
IPv4는 인터넷 초기부터 현재까지 쓰고 있는 주소 체계이며 000.000.000.000과 같이 12자리로 표시하며 약 43억 개를 부여할 수 있다. 최근에는 디바이스의 증가로 IPv4가 가진 주소 의 양이 부족할 수 있어 IPv6를 공표하였다.
2018년 현재는 IPv4와 IPv6가 공존하며 두 개의 주소 체계를 변환하여 사용하고 있으며 이를 담당하는 것을 NAT(Network Address Translator)라고 한다. IPv6는 이전 버전에 비하여 효율적인 패킷을 처리하고 보안이 강화된 특징이 있다.

 

2. TCP/IP

TCP/IP이란 TCP와 IP 프로토콜만을 지칭하는 것이 아니라

UDP(User Datagram Protocol), ICMP(Internet Control Message Protocol), 

ARP(Address Resolution Protocol), RARP(Reverse ARP) 등 관련된 프로토콜을 통칭한다. 

TCP와 UDP로 구분되는 프로토콜은 트랜스포트 계층에서 응용 계층과 인터넷 계층 사이의 통신을 담당한다.

 

1. TCP(Transmission Control Protocol)
- CRC 체크와 재전송 기능을 통해 신뢰성 있는 전송을 확보한다.
- Flow Control 기능을 수행하여 단계별 데이터 전송 상황을 체크한다.
- 논리적인 1:1 가상 회선을 지원하여 해당 경로로만 데이터가 전달되도록 한다.
- 대표 서비스: FTP, Telnet, Http, SMTP, POP, IMAP 등
2. UDP(User Datagram Protocol)
- 연결되어 있어도 데이터를 송신할 수 있다. 단, 수신 측의 수신 여부는 확인하기 어렵다.
- Flow Control, Error Control을 하지 않아 신뢰성 있는 데이터 전송에는 부적합하다.
- 하나의 송신 정보를 다수의 인원이 수신해야 할 경우 UDP를 사용한다.
- 대표 서비스: SNMP, DNS, TFTP, NFS, NETBIOS, 인터넷 게임/방송/증권 등
3. TCP/UCP의 헤더 구조
위와 같은 프로토콜의 차이는 헤더의 구조에 잘 나타나 있다.
- 송/수신자 포트번호: 송신-수신 프로세스에 할당되는 포트 주소 55
- 순서 번호: 송신자가 전하는 데이터 전송 순서
- 응답 번호: 제대로 수신했는지 여부를 수신자 측으로부터 전달받음.
- 데이터 오프셋: 헤더의 크기
- 예약 필드: 다른 사용 목적으로 확보된 필드로 실제 사용 안 함.
- 윈도 크기: 수신 윈도의 버퍼 크기 지정
- Checksum: 헤더와 데이터의 오류 검출
- 긴급 위치: 긴급 데이터 처리용
- 제어 비트: 긴급 필드 설정, 응답 번호 유효 여부 등 체크

 

TCP 소켓 프로그래밍

서버와 클라이언트 프로그래밍을 위해서는 ServerSocket이라는 클래스와 Socket이라는 클래스를 사용한다.

ServerSocket은 서버를 위한 클래스이고, Socket은 서버와 클라이언트 간의 접속을 제어하는 클래스이다. 이들의 객체를 만들어 간단히 프로그래밍을 할 수 있다. 

- Socket : 프로세스간의 통신을 담당, InputStream과 OutputStream을 가지고 있다. 이 두 스트림을 통해 프로세스간의 통신(입출력)이 이루어진다.

- ServerSocket: 서버의 포트를 할당하여 클라이언트의 서비스 요청을 기다리다가 요청이 들어오면 소켓 객체를 생성해서 소켓 간에 통신이 되도록 한다.

- Socket: 서버와 클라이언트 간의 통신을 제어하며, 입력 스트림과 출력 스트림을 통해 서버와 클라이언트간의 입출력을 처리한다.

 

한 포트의 하나의 ServerSocket만 연결할 수 있다. (프로토콜이 다르면 같은 포트를 공유할 수 있다.)

1. 서버는 서버소켓을 사용해서 서버 컴퓨터의 특정 포트에서 클라이언트의 연결요청을 처리할 준비를 한다.
2. 클라이언트는 접속할 서버의 IP주소와 포트정보로 소켓을 생성해서 서버에 연결을 요청한다.
3. 서버소켓은 클라이언트의 요청을 받으면 서버에 새로운 소켓을 생성해서 클라이언트의 소켓과 연결되도록 한다.
4. 이제 클라이언트의 소켓과 새로 생성된 서버의 소켓은 서버소켓과 관계없이 1:1통신을 한다.

 

 

 

 


 

클라이언트를 만들어 서버에 접속하여 서버메시지 받기.

1) Server

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
	// 
		/*
		실행순서 (명평프롬프트에서 테스트 합니다.)
		시작 -> cmd
		-> d:
		-> cd java0514
		-> cd workspace
		-> cd basicNetwork
		-> cd src
		->
		*/

public class Server1 {

	public static void main(String[] args) {
		//서버구축
		//클라이언트보다 먼저 실행되어
		//클라이언트의 접속 요청을 기다리며,
		//클라이언트가 접속하면 양방향 통신을 할 수 있는
		//socket 객체를 생석합니다.
				
		ServerSocket server = null;
		
		try{
			
			server = new ServerSocket(2019);
			while(true){
				System.out.println("\n클라이언트 접속 대기 중....");
				//다른 Socket과 데이터를 송수신합니다.
				Socket socket = server.accept();
				System.out.println("[접속]접속 IP:" + socket.getInetAddress().getHostAddress());
				
				//요청한 사용자 PC에 메세지 출력
				BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
				writer.write("[강하은]/1.토이스토리/2.알라딘/3.기생충 ...");
				writer.newLine();
				writer.flush();				
				
				socket.close();//접속지 연결 종료
			}//while end
			
			//port 전용문 0~65535
			//Server1 클래스만 사용하는 포트번호 - 전용문만들기
		} catch (Exception e) {
			System.out.println("서버문제발생: " +e);
		} finally {
			
			try {
				server.close(); //자원반납
			} catch(Exception e){
				System.out.println("서버프로그램 실행종료!!");
			}
			
		}//finally end
		
		
		
	}//main end
}//class end

1. 서버를 실행하고

2. 클라이언트 접속을 기다린다.

 

 

2) Client

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;

/*
 *실행순서 (명령프롬프트에서 테스트합니다.)
 *시작 ->cmd
 *->d
 *->cd
 *->java0514\workspace\basicNetwork
 *->javac Client1.java
 *->java Client1 요청서버 IP
 * 
 */

public class Client1 {

	public static void main(String[] args) {
		// 클라이언트 구축
		Socket socket = null;
		try{
			//>java Client1 172.16.83.100
			socket = new Socket(args[0], 2019);
			System.out.println("[접속]서버:"+socket.getInetAddress().getHostAddress());
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String line = reader.readLine();
			String[] movie = line.split("/");
			for(int idx=0; idx<movie.length; idx++){
				System.out.println(movie[idx]);
			}
		}catch(Exception e){
			System.out.println("서버문제발생: " +e);
		}finally {
			try{
				socket.close();
			}catch(Exception e){
				
			}
			System.out.println("클라이언트 프로그램 실행 중: ");
		}
		
	}//main end
}//class end

 

*열린 서버 접속하기

1. java Client1 (해당pc ip)

2. 서버에 있는 메세지가 뜬다.

 

클라이언트가 서버에 메시지 보내기.

1) Server

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;


public class Server2 {

	public static void main(String[] args) {
		//2) 클라이언트가 서버측에 메시지 전송
		//-> Server2.java와 Client2.java
		//1과 반대로 하면 되지 않을까?
		
		ServerSocket server = null;
		
		try{
			server = new ServerSocket(2020);
			System.out.println("\n클라이언트 접속 대기중...");
			while(true){
				
				//Client1.java에서 가져온 내용
				Socket socket = server.accept();
				System.out.println("[접속]서버IP:"+socket.getInetAddress().getHostAddress());
				
				BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				String line = reader.readLine();
				String[] str = line.split("/");
				System.out.println("------------------------------");
				for(int idx=0; idx<str.length; idx++){
					System.out.println(str[idx]);}
				System.out.println("------------------------------");
				
				try{
					socket.close(); //접속자 연결 종료.
				}catch(Exception e){}
			}//while end
			
			
			
		} catch (Exception e) {
			System.out.println("서버문제발생: " +e);
		} finally {
			
			try {
			} catch(Exception e){
				System.out.println("서버프로그램 실행종료!!");
			}
			
		}//finally end

	}//main() end
}//class() end

 

 

2) Client

import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Client2 {

	public static void main(String[] args) {
		//클라이언트 구축
		
		Socket socket = null;
		
		try{
			socket = new Socket(args[0], 2020);
			System.out.println("[접속]접속 IP:" + socket.getInetAddress().getHostAddress());
				
			//요청한 사용자 PC에 메세지 출력
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			writer.write("[하나투어]/1.코타키나발루/2.보라카이/3.사이판/4.블라디보스톡 ...");
			writer.newLine();
			writer.flush();				
			
		} catch (Exception e) {
			System.out.println("클라이언트문제발생: " +e);
		} finally {
			
			try {
				socket.close(); //자원반납
			} catch(Exception e){
				System.out.println("클라이언트프로그램 실행종료!!");
			}
			
		}//finally end
		
		
	}//main end
}//class end

 

 

 

채팅프로그램 만들기

1) Server

import java.io.*;
import java.net.*;

class ChatServerRead extends Thread{ //데이트 읽어오기.
	//Server2.java 참조
	private Socket socket;
	private String line;
	
	public ChatServerRead(){}
	public ChatServerRead(Socket socket) {
		this.socket = socket;
	}
	
	@Override
	public void run() {
		try{
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			while(true){
				line=reader.readLine(); //읽어오기
				if(line==null) break;   //읽을 거 없음 끝내기.
				System.out.println("\n"+line);
				System.out.print("[ 서 버 ]");
			}//while end
		} catch (Exception e) {
			System.out.println("데이터 전송 실패!!" + e);
		} finally {
			try{
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}//finally end
	}//run() end	
	
} //class end 29행 ------------------------------------------------------

class ChatServerSend extends Thread {//데이터보내기
	//Server1.java 참조
	private Socket socket;
	private BufferedWriter writer;
	private BufferedReader in;
	private String str ="";
	
	public ChatServerSend(){}
	public ChatServerSend(Socket socket) {
		this.socket = socket;
	}

	@Override
	public void run() {
		try{
			writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			//키보드에서 입력한다.
			in = new BufferedReader(new InputStreamReader(System.in));
			
			while(true){
				System.out.print("[ 서 버 ]");
				str = in.readLine(); //키보드로부터 입력 대기
				if(str.equals("exit")) break;
				
				writer.write("[ 서 버 ]"+str);
				writer.newLine(); //줄바꿈 기호가 있어야 BufferedReader의 readLine()이 인식함.
				writer.flush();   //client로 전송
			}//while end
			
		} catch (Exception e) {
			System.out.println("데이터 전송실패!"+e);
		} finally{
			try{
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}//run() end
} //class end 62행 ------------------------------------------------------



public class ChatServerThread { //class명
	
	private ServerSocket server = null;
	private Socket socket = null;
	
	public void serverStart(){
		System.out.println("접속자를 기다리는 중...");
		try{
			server = new ServerSocket(6901);
			socket = server.accept();
			
			//socket으로부터 데이터를 읽어오는 쓰레드
			//ChatServerRead 클래스: Server2.java 참조
			ChatServerRead read = new ChatServerRead(socket);
			read.start();
			
			//키보드에서 입력받아 socket으로 데이터를 보내는 쓰레드
			//ChatServerSend 클래스: Server1.java 참조
			ChatServerSend send = new ChatServerSend(socket);
			send.start();
			
		} catch(Exception e){
			System.out.println("연결이 되어있지 않습니다." + e);
		} finally {
			try {
				server.close();
			} catch (IOException e){
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) { //main 메소드
		/*
		서버시작 : java ChatServerThread
		*/
		
		ChatServerThread cs = new ChatServerThread();
		cs.serverStart();
		
		
	}//main end
}//class end

서버가 어떤 요소들로 구성되어있는지 파악해야한다.

진짜 외울 정도로 익숙해져야 할 것 같다.

...더보기

▶ 풀이

1. 상단에 import로 java.io와 java.net을 호출

2. 데이터를 읽어오는 ChatServerRead (Thread 클래스를 상속받는다.)

   2-1. private Socket socket, private String line 선언하고 ChatServerRead의 생성자 메소드 선언

   2-2. socket을 매개변수로 받는 ChatServerRead 메소드 선언.

3. Thread는 run()메소드 잊지말자 run()메소드 선언하고 오버라이드, try-catch-finally문 선언

   3-1. Try문안에 BufferedReader 객체를 생성하여 InputStreamReader(socket.getInputStream) 

   3-2. while문으로 한줄 씩 데이터 읽어오고 if문으로 읽어올 내역이 없으면 종료

   3-3. 읽어와서 출력

   3-4. Catch문 안에 예외발생 시 "데이터 전송 실패!!"

   3-5. finally문 안에 try-catch문 선언

   3-6. try문 안에 socket.close() 소켓 종료

   3-7. catch문 안에 입출력예외발생 시 e.printStackTrace();선언

4. 

 

 

2) Client

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;


class ChatClientRead extends Thread {
	private Socket socket;
	private String line;
	
	public ChatClientRead() {}
	public ChatClientRead(Socket socket) {
		this.socket = socket;
	}
	@Override
	public void run() {
		try{
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			while(true){
				line = reader.readLine();
				if(line==null) break;
				System.out.println("\n"+line);
				System.out.print("[운영자]");
			}
			
		}
		catch(Exception e){
			System.out.println("연결되어있지 않습니다."+e);
		}finally{
			try{
				socket.close();
			}catch (IOException e){
				e.printStackTrace();
			}
		}
	}//run() end
}//class end ------------------------------------------------------

class ChatClientSend extends Thread {
	private Socket socket;
	private BufferedWriter writer;
	private BufferedReader in;
	private String str;
	
	public ChatClientSend() {}
	public ChatClientSend(Socket socket) {
		this.socket = socket;
	}
	@Override
	public void run() {
		try{
			writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			in = new BufferedReader(new InputStreamReader(System.in));
			
			while(true){
				System.out.println("[운영자]");
				str=in.readLine();
				if(str.equals("exit")) break;
				writer.write("[운영자]"+str);
				writer.newLine();
				writer.flush();
			}
			
		} catch (Exception e){
			System.out.println("데이터 전송 실패!!"+e);
		} finally {
			try{
				socket.close();
			} catch(IOException e){
				e.printStackTrace();
			}
		}
	}//run() end	
}//class end ------------------------------------------------------

public class ChatClientThread {
	private Socket socket = null;
	
	public void clientStart(String ip, int port){
		System.out.println("클라이언트 프로그램 작동...");
		
		try{
			socket = new Socket(ip,port);
			
			//socket으로부터 데이터를 읽어오는 쓰레드
			//ChatClientRead 클래스 : Client2.java 참조
			ChatClientRead read = new ChatClientRead(socket);
			read.start();
			
			//키보드에서 입력받아 socket으로 데이터를 보내는 쓰레드
			ChatClientSend send = new ChatClientSend(socket);
			send.start();
			
		} catch(Exception e){
			System.out.println("서버와 연결 실패:"+e);
		} finally {
			try{
				if(socket==null) socket.close();
			} catch(IOException e){
				e.printStackTrace();
			}
		}
	}//clientStart() end


	public static void main(String[] args) {

		ChatClientThread cc = new ChatClientThread();
		cc.clientStart(args[0], 6901);
		
	}

}

 

 

 

 

 

 

 

댓글