Client 구현
클라이언트는 기존의 MainGUI 객체를 기본으로 하여 구현하였다. 소켓 통신을 위해 필요한 메서드들을 추가해주었다.
- startClient() : 채널그룹 객체와 소켓채널 객체를 생성한다. 서버채널에 연결을 요청하고 클라이언트를 시작한다.
- stopClient() : 작업 과정에 예외가 발생했거나 모든 작업을 마쳤을 경우, 클라이언트를 종료한다. 채널그룹과 소켓채널을 닫는다.
- receive() : 서버채널로부터 데이터를 받는다.
- send(String data) : data를 서버채널로 보낸다.
- handleResult(List<Integer> args) : receive()에 대한 버튼 별 콜백함수로 읽은 데이터를 기반으로 GUI를 변경하거나 유저에게 알림을 보낸다.
startClient()
public class Client extends JFrame {
private String name;
private int id;
private AsynchronousChannelGroup channelGroup;
private AsynchronousSocketChannel socketChannel;
private JButton[] deco = new JButton[17];
// control button
private Ground ground;
private EndTurnButton endTurnButton;
private DeclareImpButton declareImpButton;
void startClient() {
try {
channelGroup = AsynchronousChannelGroup.withFixedThreadPool(
Runtime.getRuntime().availableProcessors(),
Executors.defaultThreadFactory()
);
socketChannel = AsynchronousSocketChannel.open(channelGroup);
ground = new Ground(socketChannel);
endTurnButton = new EndTurnButton(socketChannel);
declareImpButton = new DeclareImpButton(socketChannel);
socketChannel.connect(new InetSocketAddress("localhost", 5001), name, new CompletionHandler<Void, String>() {
@Override
public void completed(Void result, String attachment) {
new AlertWindow("alert", "연결이 되었습니다!");
receive();
}
@Override
public void failed(Throwable exc, String attachment) {
// TODO: handle Exception
new AlertWindow("error", "서버와의 연결에 실패했습니다");
stopClient();
}
});
} catch (Exception e) {
if (socketChannel.isOpen()) { stopClient(); }
return;
}
}
}
- 18-23 : 채널그룹과 소켓채널을 생성한다.
- 25-27 : 생성한 소켓채널을 필드로 갖는 컨트롤 버튼들을 생성한다.
- 29 : 서버에 연결을 요청한다.
- 33-34 : 연결에 성공했을 경우, 연결 되었음을 유저에게 알리고, 데이터 받기를 시작한다.
- 38-41 / 45-46 : 연결에 실패하거나, 연결과정 중 예외 발생시 클라이언트를 종료한다.
stopClient()
public class Client extends JFrame {
void stopClient() {
try {
if (channelGroup != null && !channelGroup.isShutdown()) {
channelGroup.shutdownNow();
}
} catch (IOException e1) {}
}
}
- 5-7 : 채널그룹이 열려있을 경우, 종료한다.
receive()
public class Client extends JFrame {
void receive() {
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
socketChannel.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
attachment.flip();
Charset charset = Charset.forName("UTF-8");
String message = charset.decode(attachment).toString();
System.out.println("[응답 받음] " + message);
String[] messageList = message.split(" ");
try {
if (messageList.length < 2) { throw new IOException(); }
String flag = messageList[0];
String command = messageList[1];
if (flag.equals("TRUE")) {
List<Integer> args = new ArrayList<>();
for (int i = 2; i < messageList.length; ++i) {
int arg = Integer.parseInt(messageList[i]);
args.add(arg);
}
switch (command) {
case "PutTile":
ground.handleResult(args);
break;
case "EndTurn":
endTurnButton.handleResult(args);
break;
case "DeclareImp":
declareImpButton.handleResult(args);
break;
case "Wait":
new AlertWindow("alert", "상대 플레이어를 기다리는 중입니다");
break;
case "BeginGame":
new AlertWindow("alert", "게임을 시작합니다");
id = args.get(0);
if (id == 1) { Mode.getInstance().openLock(); }
break;
default:
System.out.println("[잘못된 커멘드입니다.]");
}
}
} catch (IOException e) {
System.out.println("[서버에서 보낸 정보가 부족합니다] " + message);
}
ByteBuffer _byteBuffer = ByteBuffer.allocate(100);
socketChannel.read(_byteBuffer, _byteBuffer, this);
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
// TODO: handle exception
}
});
}
}
- 4 : ByteBuffer를 생성한다.
- 6 : 소켓채널이 서버에 읽기를 요청한다.
- 9 : 읽기에 성공했을 경우, 버퍼의 position을 초기화한다.
- 10-11 : 버퍼를 디코딩하여 메세지를 문자열 형태로 받는다.
- 14-19 : 문자열을 파싱하여 메세지로 부터 요청결과(flag), 요청명령(command), 첨부인자(args)를 받는다.
- 21-48 : 요청결과가 TRUE(성공)인 경우, 요청 명령에 따른 버튼별 콜백함수(handleResult)를 실행한다.
- 54-55 : 다시 ByteBuffer를 생성해 read 요청을 보냄으로써 읽기를 무한반복할 수 있게 한다.
send(String data)
send() 메서드의 경우 버튼을 클릭 시 클릭 이벤트에 해당하는 요청을 서버에게 보내는 것이므로 추상 객체인 ControlButton의 디폴트 메서드로 선언하여 ControlButton을 상속한 각종 버튼들이 클릭 이벤트 발생시 호출할 수 있도록 구현하였다.
public abstract class ControlButton {
AsynchronousSocketChannel socketChannel;
void send(String data) {
if (Mode.getInstance().getLock()) { return; }
Charset charset = Charset.forName("UTF-8");
ByteBuffer byteBuffer = charset.encode(data);
socketChannel.write(byteBuffer, null, new CompletionHandler<Integer, Void>() {
@Override
public void completed(Integer result, Void attachment) {}
@Override
public void failed(Throwable exc, Void attachment) {}
});
}
}
- 7 : Mode의 lock 필드는 서버 접근 권한에 관한 필드로 이것이 true일 경우, 락이 걸려 서버에 접근이 안된다. 이경우 바로 메서드를 리턴한다.
- 9-10 : 매게인자로 받은 data를 인코딩하여 byteBuffer로 변환한다.
- 12 : 인코딩한 데이터를 서버채널로 보낸다.
- 추가적인 콜백함수는 없음
handleResult(List<Integer> args)
handleResult() 메서드는 서버 채널에서 데이터를 클라이언트 채널로 보낼 경우, 그것을 Client 객체가 받고 이후, 버튼 별로 실행하는 콜백함수이다. 버튼 별로 다르게 정의되어야 하므로 ControlButton의 추상 메서드로 선언하였다.
public abstract class ControlButton {
public abstract void handleResult(List<Integer> args);
}
Ground.handleResult()
public class Ground extends ControlButton {
public void handleResult(List<Integer> args) {
System.out.println(args);
int x = args.get(0);
int y = args.get(1);
int type = args.get(2);
groundBtn[17*x + y].setIcon(ImgStore.getInstance().getRail(type));
groundBtn[17*x + y].setName("-1");
selectMode.initTileType();
}
}
- 5-7 : 메게인자로 받은 데이터로 부터 타일을 놓을 위치와 철로 타입을 입력 받는다.
- 9-11 : 해당 위치의 타일 이미지를 해당하는 철로로 변경하고, 선택된 철로 타입을 초기화한다.
EndTurnButton.handleResult()
public class EndTurnButton extends ControlButton {
@Override
public void handleResult(List<Integer> args) {
curPlayer = (curPlayer + 1) % 2;
if (Mode.getInstance().getLock()) { Mode.getInstance().openLock(); }
else { Mode.getInstance().closeLock(); }
turnPlayerButton.setIcon(
(curPlayer == 0) ?
ImgStore.getInstance().getImg("P1Img") :
ImgStore.getInstance().getImg("P2Img")
);
}
}
- 5 : 현재 턴인 플레이어를 바꿔준다.
- 7 : 내가 현재 턴인 경우 락을 풀고, 나의 턴이 아닌경우 락을 걸어준다.
- 10-14 : 턴 변경에 따라 현제 턴 이미지도 변경해준다.
'Java > Network' 카테고리의 다른 글
[Java] NIO 기반 네트워킹 : TCP 넌블로킹(non-blocking) 채널 (0) | 2022.01.18 |
---|---|
[Java] NIO 기반 입출력 : 파일 비동기 채널 (0) | 2022.01.15 |
[Java] NIO 기반 입출력 : 파일 채널 (0) | 2022.01.11 |
[Java] 네트워크(Networking) : TCP 통신 (0) | 2022.01.04 |