728x90
먼저 이런 프레임을 만든다. 스크롤을 만드는 코드는 이렇다. scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); |
|
프레임을 만든 ChatClientObject클래스는 사용자가 계속 입력할 곳이니깐 여기에 서버, 닉네임등을 입력는 기능을 구현한다. 우선 접속할 서버IP를 입력하고 사용할 닉네임을 입력한다. 입력은 JOptionPane()을 사용한다. JOptionPane()은 팝업창으로 메세지를 띄어주는 메소드다. 그럼 이제 변수 severIP와 nickName에 값이 저장된다. |
|
서버 접속에 필요한 소켓을 생성한다. 앞서 입력한 severIP와 학원컴퓨터의 주소값? 인 9500을 변수 socket에 넣는다. 그리고 클라이언트는 서버에 입력값을 보낸다음 응답을 받아와야 하니깐 ObjectInputStream을 객체로하는 변수 ois에 socket의 입력스트림을 반환해서 저장한다. 그리고 ObjectOutputStream객체의 oos에 socket의 출력스트림을 반환해서 저장한다. 이건 그냥 정보가 오갈 수 있는 길을 만든거다. 서버는 클라이언트가 오길 대기하고 있고 클라이언트는 접속해서 서버에 먼저 정보를 보내기 때문에 입력, 출력순으로 작성한다. |
|
굳이 순서를 따지자면 저렇지만 거의 동시에 일어난다. 서버는 먼저 켜 있는 상태에서 대기하고 있고 클라이언트가 소켓을 생성해서 보내면 서버에서 소켓을 잡아서(ss.accept) 변수 socket에 넣고 그걸로 list를 추가해서 핸들러에 적혀있는 코드(2번)을 통해 객체가 만들어지는데 이게 위에 그림에 보이는 서버쪽에 가까운 소켓을 만드는 과정이다. 클라이언트의 소켓과 서버의 소켓은 1대1 관계로 만들어진다. 사용자들이 여러명 들어와도 동작할 수 있게 스레드를 만들어 둔다. 근데 클라이언트에서 입력,출력 순으로 루트를 만들었으면 대기하고 있다 정보를 받는 서버,핸들러의 소켓은 반대로 출력, 입력순으로 루트를 만든다. |
|
위에서 입력한 닉네임이 Info클래스에 있는 enum클래스에 선언한 join을 인식하고 dto.setnickName을 통해 입력한 닉네임이 dto에 저장되고 출력스트림 oos,writeObject를 통해 핸들러의 소켓으로 넘어간다. 그리고 2번으로 가서 if문을 만나 join부분을 실행한다. 여기서 새로운 변수 sendDTO에 입장했다는 메세지를 넣고 출력하는데 3번째 사진처럼 이건 이 채팅방에 있는 모두에게 메세지를 보내는 메소드이다. 누군가가들어오고, 나가고, 메세지를 보내면 전부에게 메세지를 보내야 하기 때문에 만들었다. 이제 여기까지가 클라이언트 메소드에서 서버로 보내는 정보들이다. 클라이언트의 ip, 소켓과 닉네임. |
|
이제 채팅방에 ip입력하고 닉네임치고 들어가서 채팅내용을 입력하는데 입력한 내용이 사진의 순서로 돌아다니고 6번 이후로 화면에 출력된다. 메세지를 입력해서 보내면 1번에서 enum클래스의 SEND를 인식하고 oos(출력).writeObject(dto)를 통해 데이터를 루트로 보낼수 있게 직렬화?? 해준다. 여기에는 SEND값과 채팅창에 입력한 문자열 정보가 있다. (클라이언트의 소켓인 B에서 입출력루트를 통해 서버,핸들러의 소켓인 B로 정보를 보내 핸들러클래스의 코드대로 움직인다.) 이게 2번으로가서 읽히는데 dto보다 objet가 더 상위개념이기 때문에 다운캐스팅을 해준다. 그리고 이 정보에는 SEND값이 있으니까 if문을 통해 3번으로 내려간다 여기서 [닉네임]+메세지 의 정보를 sendDTO로 보낸다. 4번 4번 broadcast는 채팅방의 모두에게 정보를 출력해주는 메소드다. writeObject를 통해 데이터를 다시 출력할 수 있게 직렬화 반대?? 작업하고 향상된for문을 이용해 list를 돌리는데 이게 나 말고 채팅방에 있는 다른 참가자들을 돌리는것이다. 그렇게 보내서 5번으로가서 (여기서 다시 A소켓에서 출,입력 루트를 통해 B소켓으로 정보를 전송한다) 정보를 읽어들이고 6번으로가서 메세지를 출력한다. 6번에서 setCaretPosition는 getText를 통해 얻은 문자열 length 대로 스크롤을 움직이는 메소드다. 이런 과정으로 화면에 입력한 메세지가 출력이된다. |
|
|
그리고 종료하기 위해서 quit를 하면 옆의 이동처럼 움직여서 3번에서 아래 사진에 해당하는 a 소켓의 입출력 루트를 닫고(문닫고)ois.close(), oos.close(); 소켓도 없애고 socket.close(); 4번으로 가서 모두에게 퇴장한다는 메세지를 출력하고 6번으로 가서 b소켓의 입출력 루트 ois.close(); oos.close();를 닫고 소켓도 없애고 socket.close(); 프로그램을 종료한다. System.exit(0); 종료할때는 항상 입출력루트, 소켓을 close로 없애서 깔끔하게 지우고 종료한다. |
채팅방에 참가하는 1명의 정보를 저장하는 Info클래스이다. 여기에 enum클래스로 join, exit, send를 구별할 수 있게하고 닉네임이나 메세지가 입출력 루트, 스트림으로 이동할 수 있도록 Serializable을 implement해준다. 그리고 private으로 선언한 필드의 정보를 받고 보낼 수 있게 셋터 겟터를 만든다. |
|
채팅방의 서버를 담당하는 ChatServerObject 클래스이다. 한번에 여러명의 클라이언트가 들어올 수 있기 때문에 스레드를 구현해주고 서버는 핸들러랑 더 밀접한 관계? 이기 때문에 바로 소켓을 받아서 리스트로 만들어 핸들러로 보내준다. |
|
핸들러 클래스이다. 소켓과 닉네임등 정보가 담긴dto를 받기 위해 제일 처음에 기본클래스를 만들고 그안에 입출력 루트를 만들어준다. 그리고 핸들러는 클라이언트 1개당 핸들러 1개가 생성된다, 그리고 핸들러의 동작들은 전부 동시에 이뤄져야 하기 때문에 스레드안에 구현을 한다. run메소드 시작에 dto를 new하지 않고 null을 한 이유는 새로운값을 받는게 아니라 클라이언트 클래스에서 보낸 dto를 바탕으로 만들어지기 때문에 null값을 주고 enum으로 만든 기준을 통해 동작이 나눠진다. |
InfoDTO 클래스
package network;
import java.io.Serializable;
enum Info{
JOIN, EXIT, SEND
}
//데어터를 실어 나르는 역활
public class InfoDTO implements Serializable{
//객체를 통해 데이터가 오갈려면 Serializable(직렬화)를 해야한다.
private static final long serialVersionUID = 1L;
//객체가 생성될때마다 새로운게 생길텐데 위에 코드는 시리얼 넘버같은 역활
private String nickName;
private String message;
private Info command;
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Info getCommand() {
return command;
}
public void setCommand(Info command) {
this.command = command;
}
}
ChatClientObject 클래스
package network;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;
public class ChatClientObject extends JFrame implements ActionListener, Runnable{
private JTextArea output;
private JTextField input;
private JButton send;
private Socket socket;
private ObjectInputStream ois; //객체가 이동할 거니깐
private ObjectOutputStream oos;
public ChatClientObject() {
send = new JButton("보내기");
input = new JTextField();
JPanel p = new JPanel();
p.setLayout(new BorderLayout());
p.add("Center", input);
p.add("East", send);
output = new JTextArea();
JScrollPane scroll = new JScrollPane(output);
scroll.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
output.setEditable(false);//대화창 수정 막기
Container c = this.getContentPane();
c.add("South", p);
c.add("Center", scroll);
setBounds(800, 200, 500, 300);
setVisible(true);
//이벤트
addWindowFocusListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if(ois == null || oos == null) System.exit(0);
//입출력값이 둘다 없다면 시스템을 끈다
InfoDTO dto = new InfoDTO();
dto.setCommand(Info.EXIT);
try {
oos.writeObject(dto);
oos.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
}
});
}//ChatClientObject() 생성자
public void service() {
//서버입력----------------------------------------------------
String serverIP = JOptionPane.showInputDialog(this, "서버IP를 입력하세요", "192.168.0.");
if(serverIP == null || serverIP.length() == 0) {
System.out.println("서버IP가 입력되지 않았습니다.");
System.exit(0);
}
//닉네임------------------------------------------------------------------------------------------
String nickName = JOptionPane.showInputDialog(this,
"닉네임을 입력하세요",
"닉네임",
JOptionPane.INFORMATION_MESSAGE);
if(nickName == null || nickName.length() == 0) {
nickName = "guest";
}
//소켓생성------------------------------------------------------------
try {
socket = new Socket(serverIP, 9500);
ois = new ObjectInputStream(socket.getInputStream()); //입력먼저받고
oos = new ObjectOutputStream(socket.getOutputStream());//출력
} catch (UnknownHostException e) {
System.out.println("서버를 찾을 수 없습니다.");
e.printStackTrace();
System.exit(0);
} catch (IOException e) {
System.out.println("서버와 연결이 안되었습니다.");
e.printStackTrace();
System.exit(0);
}
//서버로 닉네임 보내기----------------------------------------------------------
try {
InfoDTO dto = new InfoDTO();
dto.setCommand(Info.JOIN);
dto.setNickName(nickName);
oos.writeObject(dto);
oos.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//스레드 생성
Thread t = new Thread(this); //여기서 this는 ChatClient()
t.start();
send.addActionListener(this);
input.addActionListener(this);//JTextField에서 엔터
}
@Override
public void run() {
//서버로부터 받는곳 //채팅창
InfoDTO dto = null;
//InfoDTO dto = new InfoDTO()를 할 필요없다.
//서버에서 정보가 넘어올떄 dto자체가 넘어오기 때문에 new해서 값을 다시 입력할 필요가 없다.
while(true) {
try {
dto = (InfoDTO)ois.readObject();
//Objext에서 바로 올수 없기 때문에 자식으로 형변환한다.
if(dto.getCommand() == Info.EXIT) {
ois.close();
oos.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);
}
int pos = output.getText().length();//글자수를 뽑아오면
output.setCaretPosition(pos);//그거에따라 스크롤이 움직인다.
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}//while 내가 가만히 있어도 채팅창에 대화는 계속 올라오니깐
}//run
@Override
public void actionPerformed(ActionEvent e) { //서버로 보내는 쪽
String msg = input.getText();
InfoDTO dto = new InfoDTO();
if(msg.toLowerCase().equals("quit")) {
dto.setCommand(Info.EXIT);
}else {
dto.setCommand(Info.SEND);
dto.setMessage(msg);
}
try {
oos.writeObject(dto);
oos.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
input.setText("");
}
public static void main(String[] args) {
new ChatClientObject().service();
}
}
ChatHandlerObject 클래스
package network;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.List;
public class ChatHandlerObject extends Thread{
private Socket socket;
private List<ChatHandlerObject> list;
private ObjectInputStream ois; //객체가 이동할 거니깐
private ObjectOutputStream oos;
public ChatHandlerObject(Socket socket, List<ChatHandlerObject> list) throws IOException {
this.socket = socket;
this.list = list;
oos = new ObjectOutputStream(socket.getOutputStream());//출력스트림이 먼저 잡혀야지 입장메세지가 나온다.
ois = new ObjectInputStream(socket.getInputStream());
}
@Override
public void run() {
//클라이언트로 부터 받는 쪽
InfoDTO dto = null;
String nickName = null;
//클라이언트에서 직접dto가 넘어오기 때문에 new를 할 필요없다.
//dto로 데이터가 왔기 때문에 정보가 뭔지 모른다. 그래서 아래 if문으로 나눈다.
while(true) {
try {
dto = (InfoDTO)ois.readObject();
if(dto.getCommand() == Info.JOIN) {
//모든 클라이언트(나 포함)에게 입장메세지를 보내기
InfoDTO sendDTO = new InfoDTO(); //새로하는 이유는 새로 안한면 아래쪽? 다른 DTO랑 겹친다.
sendDTO.setCommand(Info.SEND);
nickName = dto.getNickName();
sendDTO.setMessage(nickName+"님 입장하였습니다.");
broadcast(sendDTO);
}else if(dto.getCommand() == Info.EXIT) {
InfoDTO sendDTO = new InfoDTO();
//quit를 보낸 클라이언트에세 quit를 보내기(나에게)
sendDTO.setCommand(Info.EXIT);
oos.writeObject(sendDTO);
oos.flush();
ois.close();
oos.close();
socket.close();
//남은 클라이언트에게 퇴장메세지 보내지
list.remove(this);
sendDTO.setCommand(Info.SEND);
sendDTO.setMessage(nickName+"님이 퇴장하였습니다.");
broadcast(sendDTO);
break;
}else if(dto.getCommand() == Info.SEND) {
InfoDTO sendDTO = new InfoDTO();
sendDTO.setCommand(Info.SEND);
sendDTO.setMessage("["+nickName+"]"+dto.getMessage());
broadcast(sendDTO);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void broadcast(InfoDTO sendDTO) {
//입장을 했으면 입했다는 메세지는 모두에게 보내야 하기 때문에
//for를 통해 모든 데이터를 불러와서 메세지를 보낸다.
for(ChatHandlerObject handler :list) {
try {
handler.oos.writeObject(sendDTO);
handler.oos.flush();// 버퍼 비우기
} catch (IOException e) {
e.printStackTrace();
}
}//for
}
}
ChatServerObject 클래스
package network;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class ChatServerObject {
private ServerSocket ss;
private ArrayList<ChatHandlerObject> list;
public ChatServerObject() {
try {
ss = new ServerSocket(9500);
System.out.println("서버 준비 완료");
list = new ArrayList<ChatHandlerObject>();
while(true) {
Socket socket = ss.accept(); //낚아챈다
ChatHandlerObject handler = new ChatHandlerObject(socket, list);//스레드 생성
handler.start();//스레드 시작 - 스레드 실행(run())
list.add(handler);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new ChatServerObject();
}
}
728x90
'JAVA' 카테고리의 다른 글
10/29 수업 (0) | 2020.10.29 |
---|---|
10/28 수업 (0) | 2020.10.28 |
10/26 수업내용 (DB) (0) | 2020.10.26 |
Frame - BorderLayout(), FlowLayout(), GridLayout() (0) | 2020.10.23 |
10/23 수업내용 (0) | 2020.10.23 |