본문 바로가기

국비지원_핀테크

16일차_ [java] Socket 과 Thread 를 활용한 실시간 채팅 프로그램

 

 

 

 

 

네트워크 프로그래밍

TCP, UDP 통신

 

네트워크 프로그래밍

 

 

소켓을 사용하여 server 와 client 구성하여 데이터 수, 발신

 

package tcp;

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Ex01_server {
	public static void main(String[] args) throws Exception {
		// 서버소켓 클래스 객체 생성
		// 예외처리 필요, 12345 는 Port 번호
		ServerSocket server = new ServerSocket(12345);
		System.out.println("접속을 기다립니다!!!");
		
		// accept() - 클라이언트가 연결될때까지 기다리는 메소드
		// 즉, 클라이언트가 연결되어야 "클라이언트 연결되었습니다!!!" 문구가 실행됨
		// server.accept() 를 소켓 객체로 담아 접속 클라이언트의 정보를
		// sock 이라는 이름으로 저장한다
		Socket sock = server.accept();
		System.out.println("클라이언트 연결되었습니다!!!");
		
		// InputStream 이라는 객체 입력 클래스 객체를
		// 소켓을 통해 사용하겠다.
		InputStream is = sock.getInputStream();
		
		// 데이터 수신
		int readData = is.read();
		System.out.println("수신 데이터 : " + readData);
		
		is.close(); sock.close(); server.close();
	}
}

Server

 

package tcp;

import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Ex01_client {
	public static void main(String[] args) throws Exception {
		// 현재 학원 PC 아이피 : 192.168.42.118
		// Server socket 설정 포트번호 : 12345
		// 클라이언트는 소켓 클래스 객체로 서버에 연결 요청
		// 객체 생성 시 아이피 정보, 포트 정보가 들어감
		Socket sock = new Socket("192.168.42.118", 12345);
		System.out.println("클라이언트 실행!!!");
		
		// OutputStream ㅇ;라는 데이터 출력 클래스 객체를
		// sock 이라는 소켓을 통해 사용하겠다
		OutputStream os = sock.getOutputStream();
		
		Scanner input = new Scanner(System.in);
		System.out.print("수 입력 : ");
		int data = input.nextInt();
		
		// 데이터 전송
		os.write(data);
		os.close(); sock.close();
	}
}

Client

 

기본 스트림이 전송할 수 있는 값은 1 byte 단위이다.

1 byte = 0 ~ 255

 

만약 256 을 전송하였다면 0 으로 server 가 받으며, 257 을 전송하였다면 1 로 server 가 받는다.

그러므로 보조 스트림을 사용하여 문자열, 객체 등을 전송할 수 있게 해줘야 한다.

 

 

 

 

 

 

 

 

 

 

보조스트림을 사용하여 문자열을 수, 발신 하기

보조스트림 DataInput, DataOutput 클래스를 사용하여 문자열 수, 발신

 

package tcp;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Ex02_server {
	public static void main(String[] args) throws Exception {
		// 서버 소켓 생성, 포트번호 10000
		ServerSocket server = new ServerSocket(10000);
		System.out.println("접속 대기....");
		
		// Socket 클래스로 클라이언트의 정보를 담아둠
		Socket sock = server.accept();
		
		// 현재 서버 접속자의 정보 출력
		System.out.println(sock.getInetAddress());
		
		InputStream in = sock.getInputStream();
		DataInputStream dis = new DataInputStream(in);
		
		// 수신한 데이터(문자열) 저장
		String readData = dis.readUTF();
		
		System.out.println("수신 데이터 : " + readData);
		
		// sock 에는 이미 클라이언트에 대한 정보가 들어있으므로
		// sock 을 이용하여 클라이언트로 데이터를 전송할 수 있음
		OutputStream os = sock.getOutputStream();
		DataOutputStream dos = new DataOutputStream(os);
		
		Scanner input = new Scanner(System.in);
		System.out.print("클라이언트로 전송할 데이터 입력 : ");
		String sendData = input.nextLine();
		dos.writeUTF(sendData);
		
		dis.close(); in.close();
		dos.close(); os.close();
		sock.close(); server.close();
	}
}

Server

 

package tcp;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Ex02_client {
	public static void main(String[] args) throws Exception {
		// 로컬 루프백 주소인 127.0.0.1 로 지정, 포트 10000 으로 접속 시도
		Socket sock = new Socket("127.0.0.1", 10000);
		
		OutputStream out = sock.getOutputStream();
		DataOutputStream dos = new DataOutputStream(out);
		
		Scanner input = new Scanner(System.in);
		System.out.print("전송할 문자열 입력 : ");
		String data = input.nextLine();
		
		dos.writeUTF(data);
		
		InputStream is = sock.getInputStream();
		DataInputStream dis = new DataInputStream(is);
		String readData = dis.readUTF();
		
		System.out.println("수신 데이터 : " + readData);
		
		dos.close(); out.close();
		dis.close(); is.close();
		sock.close();
	}
}

Client

 

 

 

 

 

 

 

 

 

 

클라이언트 두 곳과 연결

클라이언트 두 곳과 연결, 우선 연결에 따라 연결 정보가 sock1, sock2 로 저장된다

 

package tcp;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Ex03_server {
	public static void main(String[] args) throws Exception {
		ServerSocket server = new ServerSocket(12345);
		System.out.println("접속을 기다립니다...");
		
		// 접속 순서에 따라 sock01, sock02 로 클라이언트에 대한 정보를 저장한다
		Socket sock01 = server.accept();
		Socket sock02 = server.accept();
		System.out.println("접속 되었습니다.");
		
		// sock01 로부터 날아오는 데이터를 저장한다.
		// sock02 에서 날아온 데이터는 따로 저장되지 않는다...!!
		InputStream in = sock01.getInputStream();
		DataInputStream dis = new DataInputStream(in);
		
		String readData = dis.readUTF();
		System.out.println("수신 데이터 : " + readData);
		
		dis.close(); in.close(); sock01.close(); sock02.close();
		server.close();
	}
}

Server

 

package tcp;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Ex03_client01 {
	public static void main(String[] args) throws Exception {
		Scanner input = new Scanner(System.in);
		Socket sock = new Socket("127.0.0.1", 12345);
		
		OutputStream out = sock.getOutputStream();
		DataOutputStream dos = new DataOutputStream(out);
		
		System.out.print("송신 데이터 입력 : ");
		String data = input.nextLine();
		dos.writeUTF(data);
		
		dos.close(); out.close(); sock.close(); input.close();
	}
}

Client01

 

package tcp;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Ex03_client02 {
	public static void main(String[] args) throws Exception {
		Scanner input = new Scanner(System.in);
		Socket sock = new Socket("127.0.0.1", 12345);
		
		OutputStream out = sock.getOutputStream();
		DataOutputStream dos = new DataOutputStream(out);
		
		System.out.print("송신 데이터 입력 : ");
		String data = input.nextLine();
		dos.writeUTF(data);
		
		dos.close(); out.close(); sock.close(); input.close();
	}
}

Client02

 

 

 

 

 

 

 

 

 

 

객체를 직렬화하여 전송하기

직렬화 - 객체를 byte 형식으로 변환

객체는 byte 형식으로 직렬화하여 보내지 않으면 오류가 발생하기 때문에 꼭 직렬화 를 하여 전송해야 한다

직렬화하여 객체를 전송!!!! 직렬화 필수

 

package tcp;

import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Ex04_server {
	public static void main(String[] args) throws Exception {
		ServerSocket server = new ServerSocket(12345);
		System.out.println("접속 대기");
		Socket sock = server.accept();
		System.out.println("접속 되었음");
		
		InputStream in = sock.getInputStream();
		ObjectInputStream ois = new ObjectInputStream(in);
		
		Ex04_DTO dto = (Ex04_DTO)ois.readObject();
		System.out.println("수신 name : " + dto.getName());
		System.out.println("수신 addr : " + dto.getAddr());
		
		ois.close(); in.close(); sock.close();
		server.close();
	}
}

Server

 

package tcp;

import java.io.Serializable;

public class Ex04_DTO implements Serializable{
					// Serializable 상속하여 꼭 직렬화 해주기!
	private String name, addr;

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAddr() {
		return addr;
	}
	public void setAddr(String addr) {
		this.addr = addr;
	}
}

DTO

 

package tcp;

import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Ex04_client {
	public static void main(String[] args) throws Exception {
		Socket sock = new Socket("127.0.0.1", 12345);
		
		Scanner sc = new Scanner(System.in);
		
		OutputStream out = sock.getOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(out);
		
		Ex04_DTO dto = new Ex04_DTO();
		System.out.print("이름 입력 : ");
		dto.setName(sc.next());
		System.out.print("주소 입력 : ");
		dto.setAddr(sc.next());
		
		oos.writeObject(dto);
		oos.close(); out.close(); sock.close();
	}
}

Client

 

 

 

 

 

 

 

 

 

 

쓰레드를 사용하여 다중 client 에게 데이터 전송받기

Thread 를 사용하지 않으면 클라이언트에게 응답을 받을때 하나씩 받거나 응답이 오지않으면 무한대기에 빠지기 때문에 쓰레드를 사용하여 다중의 데이터를 받아 처리한다

 

 

 

 

 

 

 

 

 

 

다자간 채팅 서버

다자간 채팅 서버 구현

 

기억할 것 : 소켓으로 클라이언트가 접속하면 서버는 ArrayList 를 사용하여 클라이언트의 Socket 정보를 배열로 저장한뒤 수신 받은 데이터를 변수로 다시 선언하여 OutputStream 을 사용하여 ArrayList 안에 담긴 Socket 정보로 다시 전송하는 형태

 

Server 의 Thread : 다중 클라이언트의 데이터를 전송받고 다시 연결된 클라이언트들에게 데이터를 전송하기 위해 사용

Client 의 Thread : 발신은 Thread 를 사용하지 않고 클라이언트에서 진행되며, Thread 를 사용하여 실시간으로 들어오는 데이터를 수신

 

package tcp;

import java.net.ServerSocket;
import java.net.Socket;

public class Ex06_server {
	public static void main(String[] args) throws Exception {
		
		ServerSocket server = new ServerSocket(12345);
		
		while(true) {
			System.out.println("접속을 기다립니다...");
			Socket s = server.accept();
			System.out.println(s.getInetAddress() + "님 접속");
			
			// 쓰레드 객체를 생성
			new Ex06_serverThread(s);
		}
	}
}

Server

 

package tcp;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;

public class Ex06_serverThread extends Thread {
	// arr 변수를 static 으로 선언하여 모든 쓰레드가 공유하여 사용할 수
	// 있게끔 생성한다
	public static ArrayList<Socket> arr;
	static {
		arr = new ArrayList<>();
	}
	private Socket s;
	public Ex06_serverThread(Socket s) {
		// ArrayList 에 사용자들의 정보(소켓)를 저장
		arr.add(s);
		this.s = s;
		start(); // start 메소드 실행 시 run 메소드 자동 실행
	}
	@Override
	public void run() {
		try {
			// InputStream in = arr.get( arr.size()-1 ).getInputStream();
			
			// 현재 접속한 사용자로부터 데이터를 수신
			InputStream in = s.getInputStream();
			DataInputStream dis = new DataInputStream(in);
			while(true) {
				// 수신한 데이터를 변수에 저장
				// readUTF 메소드는 입력값이 들어올때까지 대기하다가 입력 값이
				// 들어오면 그때 메소드가 동작한다!!!!
				// 그러므로 빈 값을 클라이언트에게 무한으로 던지지 않는다
				String msg = dis.readUTF();
				for(Socket ss : arr) {
					// 배열로 저장된 현재 연결된 클라이언트들에게 수신 데이터를
					// 다시 전송
					OutputStream out = ss.getOutputStream();
					DataOutputStream dos = new DataOutputStream(out);
					dos.writeUTF(msg);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

Server Thread

 

package tcp;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Ex06_client01 {
	public static void main(String[] args) throws Exception {
		// 소켓 생성
		Socket sock = new Socket("127.0.0.1", 12345);
		OutputStream out = sock.getOutputStream();
		DataOutputStream dos = new DataOutputStream(out);
		
		Scanner sc = new Scanner(System.in);
		String msg = null;
		
		// 수신되는 데이터는 쓰레드 객체를 생성하여 수신
		Ex06_clientThread rcv = new Ex06_clientThread(sock);
		
		while(true) {
			// 클라이언트에서 전송하는 데이터는 while 문으로 계속 입력받음
			System.out.print("전송 데이터 입력 : ");
			msg = sc.nextLine();
			dos.writeUTF(msg);
		}
	}
}

Client01

 

package tcp;

import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Ex06_client02 {
	public static void main(String[] args) throws Exception {
		// 소켓 생성
		Socket sock = new Socket("127.0.0.1", 12345);
		OutputStream out = sock.getOutputStream();
		DataOutputStream dos = new DataOutputStream(out);
		
		Scanner sc = new Scanner(System.in);
		String msg = null;
		
		// 수신되는 데이터는 쓰레드 객체를 생성하여 수신
		Ex06_clientThread rcv = new Ex06_clientThread(sock);
		
		while(true) {
			// 클라이언트에서 전송하는 데이터는 while 문으로 계속 입력받음
			System.out.print("전송 데이터 입력 : ");
			msg = sc.nextLine();
			dos.writeUTF(msg);
		}
	}
}

Client02

 

package tcp;

import java.io.DataInputStream;
import java.io.InputStream;
import java.net.Socket;

public class Ex06_clientThread extends Thread {
	Socket sock;
	public Ex06_clientThread(Socket sock) {
		// 클라이언트가 객체 생성할때 들고온 Socket 정보를 sock 으로 저장
		// 서로 다른 클라이언트가 접근하면 클라이언트 1개 당
		// 쓰레드 객체가 1개씩 신규로 생성된다
		this.sock = sock;
		start(); // 쓰레드에서 start 메소드가 실행되면 run 메소드도 자동으로 실행된다
	}
	@Override
	public void run() {
		try {
			// Socket 으로 연결된 서버에서 전달 받는 정보를 쓰레드를 사용하여
			// 메세지를 발신하면서도 독립적으로 계속 수신받게끔 설정
			InputStream in = sock.getInputStream();
			DataInputStream dis = new DataInputStream(in);
			while(true) {
				String data = dis.readUTF();
				System.out.println("수신 데이터 : " + data);
			}
		}catch (Exception e) {
		}
	}
}

Client Thread

 

 

- 프로그램 작동 순서 -

Server 에서 서버 소켓 생성 후 클라이언트가 접속하는 것을 대기

 

> 클라이언트 1 이 서버 소켓에 접속

> 서버쓰레드가 공유하는 arr 배열에 클라이언트 1의 소켓 정보를 저장

> 서버 쓰레드 1 과 클라이언트 1 쓰레드가 동시에 생성 및 작동

 

> 클라이언트 2 가 서버 소켓에 접속

> 서버쓰레드가 공유하는 arr 배열에 클라이언트 2의 소켓 정보를 저장

> 서버 쓰레드 2 와 클라이언트 2 쓰레드가 동시에 생성 및 작동

 

> 클라이언트 1에서 데이터를 전송

> 서버 쓰레드 1 에서 전송받은 데이터를 arr 배열에 존재하는 사용자정보(Socket)에 모두 전송

> 클라이언트 쓰레드 1, 2 는 서버쓰레드 1 에서 전송받은 데이터를 출력

 

> 클라이언트 2에서 데이터를 전송

> 서버 쓰레드 2 에서 전송받은 데이터를 arr 배열에 존재하는 사용자정보(Socket)에 모두 전송

> 클라이언트 쓰레드 1, 2 는 서버쓰레드 2 에서 전송받은 데이터를 출력

728x90