728x90
반응형
✔ JAVA 채팅 프로그램
쓰레드 병렬처리
ServerSocket의 accept() 실행하면 해당 작업이 완료되기전까지 블로킹(blocking)이 일어남
쓰레드를 사용하면 블로킹이 일어나는 현상을 해결할 수 있음
DataInputStream
- 기본 데이터타입 단위로 데이터를 읽을 수 있음
- byte 단위로 데이터를 읽는 것이 아님
- readUTF() : UTF-8(모든 언어가 사용가능한) 형식으로 코딩된 문자열을 읽을 수 있음
DataOutputStream
- 기본 데이터타입 단위로 데이터를 쓸 수 있음
- byte 단위로 데이터를 쓴는 것이 아님
- writeUTF() : UTF-8(모든 언어가 사용가능한) 형식으로 코딩된 문자열을 출력할 수 있음
서버
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
public class ChatServer {
private HashMap<String, OutputStream> clients;
// <유저네임, 데이터>
// 키는 아이디를 저장하고 내보네는 스트림을 아웃풋으로 하는 해시맵을 클라이언트라고한다.
// 키와 벨류로 있는것이 헤시멥
// 필드만 만들어 놓움
public static void main(String[] args) {
new ChatServer().start();
// 메인이 실행되자 마자 생성자 호출하고 쳇서버, 스타트 실행
}
public ChatServer() {
clients = new HashMap<>();
}
public void start() {
ServerSocket serverSocket = null;
//서버 프로그램을 개발할 때 쓰이는 클래스
Socket socket = null;
//client에서 서버로 접속하거나 Server에서 accept 하는데 필요한 클래스
try {
serverSocket = new ServerSocket(3579);
//클라이언트가 아이피 주소와 포트번호까지 써줘야 이 문장으로 들어올 수 있다.
System.out.println("서버가 시작되었습니다");
while (true) {
socket = serverSocket.accept();
// 서버가 사용자가 들어올 때까지 대기하고 있는 장소로
// 들어오면 일반 소켓을 만들어지게 된다. 사용자의 소켓, 포트 등 정보를 알 수 있다.
System.out.println("IP : " + socket.getInetAddress() +
", PORT : " + socket.getPort() + "에서 접속했습니다.");
ServerReceiver thread = new ServerReceiver(socket);
thread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 모든 접속한 사람들에게 동일한 내용을 보네기 위한 메소드를 만들어보자
public void sendToAll(String msg) {
// 클라이언트가 해시맵, 키셋은 유저네임
// 키셋()은 메소드를 사용하여 set객체를 반환받은 후 이터레이터 사용
Iterator<String> it = clients.keySet().iterator();//탐색 / 이터레이터 해시멥을 탐색하는
// 전체 출력을 하는 경우 반복문을 사용하지 않고 Iterator을 사용하여 출력한다.
while (it.hasNext()) { //자료가 있다면(접속된 사용자 만큼 반복)
try {
DataOutputStream out = (DataOutputStream) clients.get(it.next());
// it.next() 는 유저네임
//clients.get(it.next()는 아웃풋 스트림 -> 데이터 아웃풋 스트림으로
// 들어가게 하기 위해 (DataOutputStream)사용하여 변환하게 한다.
out.writeUTF(msg); // 받은걸 출력
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ServerReceiver extends Thread {
private Socket socket; // 참조변수
private DataInputStream in;
private DataOutputStream out;
public ServerReceiver(Socket socket) {// 소켓을 메개변수로 넣고 서로 연관시키기
this.socket = socket;
try {
in = new DataInputStream(socket.getInputStream());
// 소켓에 데한 인풋 스트림을 만들어 입력가능하게
out = new DataOutputStream(socket.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override // 스레드를 만들었기 때문에 만들어야 한다.
public void run() {
String userName = "";
try {
userName = in.readUTF(); //이름 읽어오기
// 상대가 입력하지 않으면 계속 대기한다(스캐너 역할)
sendToAll("😁" + userName + "님이 들어오셨습니다"); // 모든 클라이언트에 전송
clients.put(userName, out);
// 서버가 있고 클라이언트가 있으면 접속을 하게되면 유저네임을 집어넣고 접속한 사람에게 아웃풋 스트림을 전송하면
// 메시지를 보넬 통로를 만든것이다. 이는 해시맵을 만든것으로 통로가 서로 달라 각 쌍으로 키와 벨류로 저장한 것이다.
//아이디 별로 아웃풋 스트림을 해시멥에 담아준것이다.
System.out.println("현재 접속자 : " + clients.size()); //수는 해시맵의 사이즈로 확인 가능하다.
while (in != null) {
String msg = in.readUTF();
if (msg.contains("quit")) break; // 종료
sendToAll(msg);//아니면 모든 사용자에게 메시지를 전송한다.
}
} catch (IOException e) {
e.printStackTrace();
} finally {
sendToAll("😢" + userName + " 님이 나가셨습니다");
clients.remove(userName);
System.out.println("IP : " + socket.getInetAddress() +
", PORT : " + socket.getPort() + "에서 접속종료.");
System.out.println("현재 접속자 : " + clients.size());
}
}
}
}
클라이언트
import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class ChatClient {
public static void main(String[] args) {
System.out.println("대화명을 입력하세요");
Scanner sc = new Scanner(System.in);
String userName = sc.next();
try {
Socket socket = new Socket("192.168.6.13", 3579);
////client에서 서버로 접속하거나 Server에서 accept 하는데 필요한 클래스
System.out.println("서버와 연결되었습니다");
Thread sender = new Thread(new ClientSender(socket, userName));
Thread receiver = new Thread(new ClientReceiver(socket));
sender.start();
receiver.start();
} catch (IOException e) {
e.printStackTrace();
}
}
static class ClientSender extends Thread { // 단일로 돌아감
private Socket socket;
private DataOutputStream out;
private String userName;
public ClientSender(Socket socket, String userName) {
this.socket = socket;
try {
out = new DataOutputStream(socket.getOutputStream());
this.userName = userName;
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
// 터미널에서 입력한 데이터를 가저오는 것이다. 인풋스트림 리더를 통해 버퍼에 저장하고 버퍼에서 저장된 라이트
if (out != null) out.writeUTF(userName);
while (out != null) {
out.writeUTF("[" + userName + "]" + br.readLine());
}// 메세지가 돌며 버퍼에서 있는것을 읽어 서버쪽으로 전송하는 구문
// [apple] 안녕하세요를 라인별로 읽어 서버쪽으로 계속 전달
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) try {
br.close();
} catch (IOException e) {
}
// 입출력 예외처리
if (out != null) try {
out.close();
} catch (IOException e) {
}
}
}
}
static class ClientReceiver extends Thread {// 서버의 글을 받악가지고 처리하는 부분
private Socket socket;
private DataInputStream in;
public ClientReceiver(Socket socket) {
this.socket = socket;
try {
in = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (in != null) {
try {//읽어온것을 그저 찍어낸다
System.out.println(in.readUTF());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
반응형
'Java > Java' 카테고리의 다른 글
Java 음성 소켓 통신(서버, 클라이언트) 기초 (0) | 2023.04.21 |
---|---|
JAVA (TCP/IP, 서버 클라이언트) (0) | 2022.10.20 |
JAVA 네트워크 (0) | 2022.10.20 |
자바 제네릭(Generic) (0) | 2022.10.12 |
자바 스레드 (0) | 2022.10.07 |