[back-end]/자바-소켓

소켓프로그래밍 - 멀티 유저 채팅 프로그램

broship 2021. 1. 2. 18:13

다중의 클라이언트끼리 채팅하는 프로그램

- 각각 클라이언트마다 서버가 1:1로 연결되어 있으나 서버는 입력,출력하는 역활을 하지 않고 클라이언트가 입력한 값을 받아서 서버에 연결되어 있는 모든 클라이언트에게 전송하는 쓰레드를 만들고, 관리하는 역활만 함

 

TCP 서버

package ex06multiuser2;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

//tcp 서버 클래스
class ServerClass {
	private ArrayList<ThreadServerClass> threadList = new ArrayList<>();
	
	public ServerClass(int port) throws IOException  {
		Socket s = null;
		ServerSocket ss = new ServerSocket(port);
		System.out.println("서버 가동");
		
		while(true) {
			s = ss.accept();
			System.out.println("접속주소: "+s.getInetAddress()+", 접속포트: "+s.getPort());
			ThreadServerClass tServer = new ThreadServerClass(s, threadList);
			tServer.start();//새로운 클라이언트가 접속할때마다 서버쓰레드 생성 
			
			threadList.add(tServer); //실행된 쓰레드를 리스트에 add
			System.out.println("접속자 수: " + threadList.size());
		}
	}
}

//메인 클래스
public class TcpMulServer {

	public static void main(String[] args) throws IOException {
		if(args.length!=1) {
			System.out.println("사용법: java [패키지명].[파일명] [포트번호]");
			System.exit(1);
		}
		new ServerClass(Integer.parseInt(args[0]));
		//서버는 채팅하지 않고 들어오는 사람들에게 뿌려주는 역활만 수행
	}

}

 

서버에서 클라이언트에게 받아온 값을 모든 클라이언트에게 다시 전달하는 역활을 하는 쓰레드

package ex06multiuser2;

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

class ThreadServerClass extends Thread {
	private Socket socket;
	private DataInputStream inputStream;
	private DataOutputStream outputStream;
	private ArrayList<ThreadServerClass> threadList;
	
	public ThreadServerClass(Socket s, ArrayList<ThreadServerClass> threadList) throws IOException {
		socket = s;
		inputStream = new DataInputStream(s.getInputStream());
		outputStream = new DataOutputStream(s.getOutputStream());
		this.threadList = threadList;
	}
	
	public void sendChat(String chat) throws IOException {
		for(int i=0;i<threadList.size();i++) {
			threadList.get(i).outputStream.writeUTF(chat);
		}
	}
	
	@Override
	public void run() {
		String nickname = "";
		try {
			if(inputStream != null) {
				nickname = inputStream.readUTF();
				sendChat(nickname + " 님이 채팅방에 입장하셨습니다.");
			}
			while(inputStream != null) {
				sendChat(inputStream.readUTF());
				//클라이언트가 보낸 채팅 내용을 접속한 모두에게 보냄
			}
			//while문 때문에 try문에서는 finally로 갈 일이 없음
		} catch (IOException e) { //에러가 생겼을때 -> 유저가 나갔을때
//			e.printStackTrace(); //주석시키면 에러메세지 안나옴
		} finally {//try문에서는 절대 finally로 안넘어옴, catch문에서만 finally로 옴
			//나간 쓰레드의 인덱스 찾기
			for(int i=0;i<threadList.size();i++) {
				if(socket.equals(threadList.get(i).socket)) {
					threadList.remove(i);
					try {
						sendChat(nickname + " 님이 채팅방에서 나가셨습니다.");
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			}
			System.out.println("접속자수: " + threadList.size() + " 명");
		}
	}
	
}

 

TCP 클라이언트(클라이언트 GUI 클래스를 생성 후 자기의 소캣 정보를 보냄)

package ex06multiuser2;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class TcpMulClient {

	public static void main(String[] args) {
		if(args.length!=3) {
			System.out.println("사용법: java [패키지명].[파일명] [ip주소] [포트번호] [닉네임]");
			System.exit(1);
		}
		
		try {
			//                    ip 주소                    포트번호
			Socket s = new Socket(args[0], Integer.parseInt(args[1])); //해당 ip주소에 해당 포트번호로 연결하겠다
			System.out.println("서버에 연결");//connect
			
			//i/o network stream: readUTF(), writeUTF() 사용을 위해 준비
			DataOutputStream outputStream = new DataOutputStream(s.getOutputStream());
			DataInputStream inputStream = new DataInputStream(s.getInputStream());
			outputStream.writeUTF("##" + args[2]);
			//gui를 담당하는 .java 파일 하나 만들기, 거기에 보낼 인자를 준비
			
			new ClientGUI(outputStream, inputStream, args[2]); 
			
		} catch (Exception e) {
			e.printStackTrace(); //주석을 달아야 에러가 화면에 안나타남
		}
	}

}

 

클라이언트 GUI

package ex06multiuser2;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class ClientGUI extends JFrame implements Runnable, ActionListener {
	//넘어오는 io스트림이랑 닉네임을 받을 필드들
	DataOutputStream outputStream;
	DataInputStream inputStream;
	String nickname;
	
	//gui
	JLabel label1 = new JLabel("*** 채팅창 ***");
	JTextArea area1 = new JTextArea();
	JTextField field1 = new JTextField();
	JScrollPane scroll = new JScrollPane(area1);
	
	public ClientGUI(DataOutputStream outputStream, DataInputStream inputStream, String nickname) {
		this.outputStream = outputStream;
		this.inputStream = inputStream;
		this.nickname = nickname;
		
		setLayout(new BorderLayout());
		// North, South, West, East, Center
		
		//라벨
		label1.setFont(new Font("굴림", Font.BOLD, 22));
		add("North", label1);
		
		//채팅 문자열이 출력되는 곳
		area1.setBackground(Color.yellow);
		area1.setForeground(Color.BLUE);
		area1.setFont(new Font("굴림", Font.BOLD, 22));
		area1.setEditable(false); //편집 못하게 하기, 한번 올라간 텍스트는 수정할 수 없음
		add("Center", scroll); //scroll이랑 area1이랑 연결되어 있음
		
		//채팅 치는곳
		field1.setBackground(Color.white);
		field1.setForeground(Color.black);
		field1.setFont(new Font("굴림", Font.BOLD, 25));
		add("South", field1);
		field1.addActionListener(this);//이벤트 등록
		
		setSize(800, 800);
		setVisible(true);
		
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				dispose();
				System.exit(0);//이걸 해주어야 퇴장이 됨
				setVisible(false);
			}
		});
		
		Thread th = new Thread(this); //리시브 쓰레드, 메인 쓰레드는 보내는 용도
		th.start();
		
	}//생성자 end
	
	@Override
	public void actionPerformed(ActionEvent e) {
		if(e.getSource() == field1) {//field1에서 이벤트가 발생하면(채팅을 치면)
			try {
				outputStream.writeUTF(nickname + ">>> " + field1.getText()); //닉네임과 방금 친 메세지를 전송
			} catch (IOException e2) {
//				e2.printStackTrace();
			}
			field1.setText(""); //칸 비우기
		}
	}
	
	Toolkit tk = Toolkit.getDefaultToolkit();
	//채팅 올때마다 소리 들리게 하는 것
	
	@Override
	public void run() {//출력용 쓰레드
		try {
			while(true) {
				String strServer = inputStream.readUTF();//서버로부터
				if(strServer==null) {
					area1.append("\n"+"종료");
					return;
				}
				area1.append("\n" + strServer); //서버가 전달해준 문자열 채팅창(area1)에 추가
				
				//커서를 맨 뒤로 나오게 하기
				int endText = area1.getText().length();
				area1.setCaretPosition(endText); //커서를 맨 뒤로(setCaretPosition: 커서 위치 변경)
				
				tk.beep(); //채팅 전달될 때마다 소리 울림
			}
		} catch (Exception e3) {
			area1.append("\n" + e3);
		}
	}
}

'[back-end] > 자바-소켓' 카테고리의 다른 글

소켓프로그래밍 - 양방향 소켓  (0) 2021.01.01
소켓프로그래밍 - 단방향 소켓 프로그램  (0) 2021.01.01
program, process, thread  (0) 2020.12.28
소켓 프로그램  (0) 2020.12.24
웹 프로그램  (0) 2020.12.24