* 멀티채팅
Server 1개 + Client n개 connected
1. BroadCasting 기능 (방송 기능) : 자기자신을 포함해 접속해있는 모든 유저에게 메시지를 전달
(A가 hi를 전달-> Server에서 메시지를 받아 현재 접속해있는 A,B,C에게 hi를 전달-> A,B,C에게 hi 출력 )
2. Thread 동기화 기능
(A가 hi를 전송하고 B가 hello를 전송했을 때, A 메시지가 먼저 A,B,C 화면에 출력된 뒤 B 메시지가 출력되도록 제어 -> Thread 동기화로 A 처리 전부 끝난 뒤 B 처리 (대기상태)
* Thread의 동기화 기법 synchronized싱크로나이즈
: 한 번에 하나의 쓰레드만 객체에 접근할 수 있도록 객체에 락(lock)을 걸어서 데이터의 일관성을 유지하는 것
스레드는 동시에 여러작업이 가능하지만 특정 작업에 대해서는 하나의 스레드만이 동작하도록 해야하는 경우가 있다. 이렇게 멀티스레드 프로그램에서 하나의 스레드에 의해서만 처리할 수 있도록 하는 영역을 임계영역(Critical Section)이라고 한다.
-> 하나의 스레드가 임계 영역에 진입할때 락을 걸어서 다른 스레드가 접근하지 못하도록 하고, 이 영역에서 벗어날 경우 락을 해제하여 다른 스레드가 접근/수행할 수 있게 된다.
특정 블록의 동기화 방법 (특정 객체에 lock 을 설정)
public void Method(){
synchronized(동기화할 객체 또는 동기화할 클래스명){
임계영역 처리구문
}
}
//GUI 부분 생략, socket 연결 부분만 정리. 당연함 GUI 안 배웠음
1. Client 부분 ----------
class BroadCastingClient implements Runnable | Thread 구동 위해 Run() 메소드 오버라이딩 할 것임 |
public void connectServer( String id, String address, int port ) { try { socket = new Socket( address, port ); dis = new DataInputStream( socket.getInputStream() ); dos = new DataOutputStream( socket.getOutputStream() ); dos.writeUTF( id ); } catch ( IOException e ) { System.out.println( "서버가 없습니다." ); System.exit( 0 ); } listen = new Thread( this ); listen.start(); } |
Server와의 연결을 시도하는 메소드 Socket을 이용해 Server와 연결 시도 연결에 성공한다면 접속한 Server와 Stream을 형성 String형으로 id 작성해 넘김 (대화명[ㅇㅇ님]) 서버 연결 실패 시 예외처리 this=BroadCastingClient class 자신이 속한 클래스를 불러올 땐 객체를 생성할 필요가 없음 -> this로 클라이언트에 대한 Thread 객체 생성 run() 메소드 호출 |
public void run() { try { while( true ) { String id = dis.readUTF(); String line = dis.readUTF(); if( id.equals( "NONE" )) { talkScreen.append( line + "\n" ); } else { talkScreen.append( "[" + id + "] : " + line + "\n" ); } } } catch ( IOException ex ) { talkScreen.append( "서버에서 데이터를 읽는 중 에러가 발생" ); try { Thread.sleep( 1000 ); } catch ( InterruptedException e ) { System.exit( 0 ); } } |
서버로부터 받은 데이터를 TextArea에 출력하는 메소드 id, message 읽음 (in) id 값이 존재하지 않는 경우 -> massage만 출력 ServerThread.java compareState( "LogOut/" + talkName + "님이 퇴장하셨습니다 \n" ); 값 존재한다면 id와 함께 넘어온 massage를 출력 서버 에러 시 예외처리 1초 간 blocked 상태 Thread 처리가 안 된다면 프로그램 나가기 |
public void actionPerformed( ActionEvent av ) { if( av.getSource() == tf ) { try { dos.writeUTF( "Message/" + tf.getText() ); tf.setText( " " ); } catch ( IOException e ) { System.out.println( e ); } } else if( av.getSource() == connection ) { String address = ipField.getText(); String id = idField.getText(); first.setVisible( false ); first.dispose(); secondFrame( id ); connectServer( id, address, 6666 ); } } |
Input 과정에서 액션이 발생할 경우 처리하는 메소드 // 접속 후 멀티채팅을 하는 두번째 창에 대한 이벤트 1. TextField(tf)에 값 입력 후 엔터 (tf에 Event 발생) tf에 입력된 텍스트를 Message/를 붙여 서버로 보내고 setText()를 이용해 tf를 비움 // id/address 입력하는 첫번째 창에 대한 이벤트 2. id/address 입력 후 버튼 클릭 (Button에 Event 발생) 각 필드에 입력받은 id와 address를 저장 첫번째 창(입력한 창)을 사라지게 만들고 해제함 두번째 창(멀티채팅창)에 id를 보낸 뒤 열고 connectServer 메소드로 입력받은 값과 port number를 넘겨줌 |
2. ServerThread 부분 ----------
class ServerThread implements Runnable | Thread를 이용해 모든 클라이언트에게 데이터를 전송해주는 Broadcasting을 구현 |
private static Vector list = new Vector(); private Socket socket; private TextArea state; private DataInputStream dis; private DataOutputStream dos; String talkName; |
접속한 클라이언트를 저장하는 vector 객체 생성 BroadCastringServer가 넘겨주는 argument를 저장하기 위한 멤버 변수 선언 사용자의 대화명을 저장하는 변수 선언 |
public ServerThread( Socket socket, TextArea state, DataInputStream dis, DataOutputStream dos, String talkName) throws IOException{ this.socket = socket; this.state = state; this.dis = dis; this.dos = dos; this.talkName = talkName; } |
//생성자 -> 초기화 BroadCastingServer에서 넘겨주는 argument를 멤버 변수에 할당 각 argument를 미리 선언해둔 Field에 할당 |
public void run() { state.append( talkName + "님이 입장하셨습니다.\n" ); compareState( "Login/" + talkName + "님이 입장하셨습니다.\n" ); try { list.addElement( this ); while( true ) { String msg = dis.readUTF(); compareState( msg ); } } catch( IOException ie ) { state.append( talkName + "님이 퇴장하셨습니다\n" ); compareState( "LogOut/" + talkName + "님이 퇴장하셨습니다 \n" ); list.removeElement( this ); try { socket.close(); } catch( IOException e ) { System.out.println( "소켓 닫는 중 에러 발생" ); } } } |
서버에 접근하는 클라이언트의 상태를 출력하는 메소드 Thread로 계속 대기 상태 -> 서버에 새로 클라이언트가 연결되거나 나갈 때마다 계속 동작 서버의 TextArea에 입장 메시지 출력 모든 클라이언트 TextArea에 입장 메시지 출력 현재 접속한 클라이언트를 벡터에 추가 전송받은 메시지를 받아서 클라이언트 TextArea로 출력 IOException -> 소켓 끊어지거나 스트림 해제된 경우 즉 퇴장한 경우이므로 서버에 퇴장 메시지 출력 모든 클라이언트 TextArea에 퇴장 메시지 출력 벡터에서 현재 Exception(퇴장)한 Object(클라이언트) 제거 소켓 닫음 |
public void compareState( String message ) { StringTokenizer st = new StringTokenizer( message, "/" ); String protocol = st.nextToken(); String msg = st.nextToken(); System.out.println( msg ); if( protocol.equals( "Login" ) || protocol.equals( "LogOut" )) { broadCasting( "NONE", msg ); } else { broadCasting( talkName, msg ); } } |
Login 관련인지 메시지를 보내는 것인지 판단하는 메소드 Tokenizer 클래스를 이용해 "/"를 기준으로 문자열 나눔 첫번째 토큰(protocol)=Login, Logout, Message 두번째 토큰=message 메시지 출력 //브로드캐스팅 관련 login 또는 logout인 경우 대화명 없이 메시지만 broadCasting 메소드로 넘김 일반 대화인 경우 대화명과 메시지를 넘김 |
public void broadCasting( String talkName, String message ) { synchronized( list ) { Enumeration e = list.elements(); while( e.hasMoreElements() ) { ServerThread temp = (ServerThread)e.nextElement(); try { temp.dos.writeUTF( talkName ); temp.dos.writeUTF( message ); } catch( IOException ie ) { System.out.println( ie ); } } } } |
BroadCasting 기능 구현 메소드 동기화 (synchronized) 현재 접속한 클라이언트 목록 가져옴 목록에 값이 존재한다면(=클라이언트 있으면) 클라이언트에 대해 Thread 동기화를 할 거야 리스트의 클라이언트들에게 메시지 작성한 클라이언트의 대화명과 메시지를 보낼 것임 이 작업이 모두 끝날 때까지 다른 클라이언트가 보내는 메시지는 대기 상태로 남아있다가(Blocked) 앞의 작업이 끝나면 순차적으로 이 과정이 실행됨 (반복) ---> 대화명+메시지를 묶어서 나란히 보내는 것이 목적! |
3. Server 부분 ----------
class BroadCastingServer | 클라이언트의 접속을 받아 모든 클라이언트에게 데이터 전송 |
private ServerSocket server; private Socket socket; private ServerThread st; |
클라이언트와 소켓을 형성하기 위한 클래스 선언 데이터 주고받을 수 있게 Thread 구현 클래스 선언 |
public void startServer() { try { server = new ServerSocket( 6666 ); while( true ) { socket = server.accept(); DataInputStream dis = new DataInputStream( socket.getInputStream() ); DataOutputStream dos = new DataOutputStream( socket.getOutputStream()); String name = dis.readUTF(); st = new ServerThread( socket, state, dis, dos, name ); Thread t = new Thread( st ); t.start(); } } catch( IOException ie ) { System.out.println( ie.getMessage() ); } } |
서버를 실행시키는 메소드 정해진 port number를 가진 ServerSocket 객체 생성 클라이언트와 접속 성공하면 소켓을 생성 accept()를 while 안에 둔 이유 -> A유저가 들어와도 다른 유저 들어올 수 있게 항상 대기상태를 유지하도록 함 --> 멀티 채팅! 스트림 형성 클라이언트가 전송하는 ID 값을 받아 name에 저장 broadcasting을 위한 ServerThread 객체 생성 위의 Thread 객체 생성 Thread start() 상태 |
'개인 > 정리' 카테고리의 다른 글
데이터 페이징 처리 (0) | 2022.07.19 |
---|---|
ajax 썸네일 출력하기 (0) | 2022.07.14 |
220713 file -> db -> json (0) | 2022.07.13 |
220114 File 내보내기 정리 (0) | 2022.01.16 |
220113 Thread 과제 정리 (0) | 2022.01.16 |