Java_ 소켓
Java_ 채팅, 스레드
치개
2022. 10. 20. 15:32
- 스레드
- 프로세스 내에서 실제로 작업을 수행하는 주체
- 모든 프로세스는 한 개 이상의 스레드를 갖고 있음 -> 여러 개의 스레드를 가지면 멀티스레드
- 프로그램을 한 줄 씩 실행
- 멀티 스레드
- 여러 개의 스레드를 가짐
- 프로세스처럼 작업을 동시에 처리 가능
- 프로세스보다 오버헤드가 적음
- 적용 조건 -> 병행성, 동기화, 통신
- 자바 채팅 프로그램 (Swing)
- ChatServer.java
//서버와 포트번호만을 준비하는 클래스
//클라이언트가 접속하게되면 그 클라이언트의 정보를 ChatHandler 클래스에 전달
public class ChatServer {
private ServerSocket serverSocket; //서버 소켓 생성
private List<ChatHandler> list;
public ChatServer() {
try {
serverSocket = new ServerSocket(1234);
System.out.println("서버 실행");
list = new ArrayList<ChatHandler>();
while (true) {
Socket socket = serverSocket.accept();
ChatHandler handler = new ChatHandler(socket, list);
//스레드 시작
handler.start();
// 핸들러를 담음 (클라이언트의 갯수)
list.add(handler);
}
}catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new ChatServer();
}
}
- InfoDTO.java
//상수의 집합
//JOIN, EXIT, SEND 3가지의 명령어에 따라 다른 역할 수행
enum Info {
JOIN, EXIT, SEND
}
public class InfoDTO implements Serializable {
private String nickName;
private String message;
//enum Info{}
private Info command;
//롬복 Getter 대체 가능
public String getNickName() {
return nickName;
}
public String getMessage() {
return message;
}
public Info getCommand() {
return command;
}
//롬복 Setter 대체 가능
public void setNickName(String nickName) {
this.nickName = nickName;
}
public void setMessage(String message) {
this.message = message;
}
public void setCommand(Info command) {
this.command = command;
}
}
- ChatHandler.java
//소켓 처리단
//서버의 중요 기능을 담당
//클라이언트로부터 정보를 전달받아 사용자의 서버에 메세지 내용을 뿌려주는 서버
//스레드의 역할
public class ChatHandler extends Thread{
private ObjectInputStream reader;
private ObjectOutputStream writer;
private Socket socket;
private List<ChatHandler> list; //멀티 스레드
public ChatHandler(Socket socket, List<ChatHandler> list) throws IOException {
this.socket = socket;
this.list = list;
writer = new ObjectOutputStream(socket.getOutputStream());
reader = new ObjectInputStream(socket.getInputStream());
}
public void run() {
InfoDTO dto = null;
String nickName;
try {
while (true) {
dto = (InfoDTO)reader.readObject();
nickName = dto.getNickName();
//사용자가 접속을 종료했을 때 퇴장하였다는 메세지
if (dto.getCommand()==Info.EXIT) {
InfoDTO sendDto = new InfoDTO();
sendDto.setCommand(Info.EXIT);
writer.writeObject(sendDto);
writer.flush();
reader.close();
writer.close();
socket.close();
list.remove(this); //퇴장한 유저를 삭제함
sendDto.setCommand(Info.SEND); //퇴장 메세지 전달
sendDto.setMessage(nickName+"님 퇴장하였습니다");
broadcast(sendDto);
break;
} else if (dto.getCommand()==Info.JOIN) {
//사용자가 접속하면 입장 메세지 전달
InfoDTO sendDto = new InfoDTO();
sendDto.setCommand(Info.SEND);
sendDto.setMessage(nickName+"님 입장하였습니다");
broadcast(sendDto);
} else if (dto.getCommand()==Info.SEND) {
//사용자가 메세지를 입력하면 전달하고 출력
InfoDTO sendDto = new InfoDTO();
sendDto.setCommand(Info.SEND);
sendDto.setMessage("["+nickName+"]"+dto.getMessage());
broadcast(sendDto);
}
}
}catch (IOException e){
e.printStackTrace();
}catch (ClassNotFoundException e2) {
e2.printStackTrace();
}
}
//다른 클라이언트에게 전체 메세지 전달
public void broadcast(InfoDTO sendDto) throws IOException {
for (ChatHandler handler : list) {
//핸들러의 writer 값 보냄
handler.writer.writeObject(sendDto);
//핸들러 안의 writer 값 비워줌 -> 모두 보냄
handler.writer.flush();
}
}
}
- ChatClient.java
//JFrame 을 사용하여 윈도우 창을 띄워준다. -> JFrame 은 스윙 클래스의 일, 구현되는 하나의 창
//끊임없는 데이터 교환을 위한 Runnable 을 implements
//자바 Swing -> 자바의 GUI 패키지 ----> AWT 보다 가볍고 컴포넌트가 자바로 작성되어 있어 어떤 플랫폼에서도 일관된 화면 작성 가능
public class ChatClient extends JFrame implements ActionListener, Runnable {
//Runnable -> 스레드의 인터페이스화 된 형태
//JAVA 에서는 다중상속이 불가능하여 스레드를 상속받지 못하는 경우 Implments로 Runnable을 받아서 구현
private JTextArea output;
private JTextField input;
private JButton sendBtn;
private Socket socket; //소켓
private ObjectInputStream reader = null; //객체를 직렬화하여 저장
private ObjectOutputStream writer = null; //직렬화한 데이터를 역직렬화
private String nickName;
public ChatClient() {
//센터에 TextArea 만들기
output = new JTextArea();
output.setFont(new Font("맑은 고딕", Font.BOLD, 15));
output.setEditable(false);
JScrollPane scroll = new JScrollPane(output);
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
//하단에 버튼과 TextArea 넣기
JPanel bottom = new JPanel();
bottom.setLayout(new BorderLayout());
input = new JTextField();
sendBtn = new JButton("보내기");
//가운데 정렬
bottom.add("Center", input);
//오른쪽 정렬
bottom.add("East", sendBtn);
Container c = this.getContentPane();
//가운데 정렬
c.add("Center", scroll);
//아래쪽 정렬
c.add("South", bottom);
//윈도우 창 설정
setBounds(300, 300, 300, 300);
setVisible(true);
this.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
try {
InfoDTO dto = new InfoDTO();
dto.setNickName(nickName);
dto.setCommand(Info.EXIT);
writer.writeObject(dto);
writer.flush();
}catch (IOException e2){
e2.printStackTrace();
}
}
});
}
public static void main(String[] args) {
new ChatClient().service();
}
@Override
public void actionPerformed(ActionEvent e) {
try {
//JTextField 의 값을 서버로 보냄
String msg = input.getText();
InfoDTO dto = new InfoDTO();
if (msg.equals("exit")) {
dto.setCommand(Info.EXIT);
} else {
dto.setCommand(Info.SEND);
dto.setMessage(msg);
dto.setNickName(nickName);
}
writer.writeObject(dto);
writer.flush();
input.setText("");
}catch (IOException e2) {
e2.printStackTrace();
}
}
public void service() {
//서버 IP 입력받기
String serverIp = JOptionPane.showInputDialog(this, "서버 IP를 입력하세요", "127.0.0.1");
//값이 입력되지않으면 서버 꺼짐
if (serverIp==null || serverIp.length()==0) {
System.out.println("서버 IP가 입력되지 않았습니다.");
System.exit(0);
}
nickName = JOptionPane.showInputDialog(this, "닉네임을 입력하세요", "닉네임", JOptionPane.INFORMATION_MESSAGE);
if (nickName==null || nickName.length()==0) {
nickName="guest";
}
try {
socket = new Socket(serverIp, 1234);
reader = new ObjectInputStream(socket.getInputStream());
writer = new ObjectOutputStream(socket.getOutputStream());
System.out.println("전송 준비 완료");
} catch (UnknownHostException e) {
System.out.println("서버를 찾을 수 없습니다.");
e.printStackTrace();
System.exit(0);
} catch (IOException e2) {
System.out.println("서버와 연결되지 않았습니다.");
e2.printStackTrace();
System.exit(0);
}
try {
InfoDTO dto = new InfoDTO();
dto.setCommand(Info.JOIN);
dto.setNickName(nickName);
writer.writeObject(dto);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
Thread thread = new Thread(this);
thread.start();
input.addActionListener(this);
sendBtn.addActionListener(this);
}
@Override
public void run() {
InfoDTO dto = null;
while (true) {
try {
dto = (InfoDTO) reader.readObject();
if (dto.getCommand()==Info.EXIT) {
reader.close();
writer.close();
socket.close();
System.exit(0);
} else if (dto.getCommand()==Info.SEND) {
output.append(dto.getMessage() + "\n");
int pos = output.getText().length();
output.setCaretPosition(pos);
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e2) {
e2.printStackTrace();
}
}
}
}




GitHub - Pearlmoon997/Socket: 자바 소켓 프로그래밍
자바 소켓 프로그래밍. Contribute to Pearlmoon997/Socket development by creating an account on GitHub.
github.com