public class ServerStart { //================ // 변수들. //================ public static HashMap <String,SelectionKey> LoginMap; //로그인할때 Channel 값저장. public static HashMap <Long,LinkedList <String>> GroupMap; //그룹화할때 사용자들 그룹. public static JSFrame Frame = null; //GUI. private long GroupCnt = 0; //그룹번호. private long GroupCheck = 0; //3명이상올라갔을시 다음 그룹번호로. public ServerStart() {} public void ServerOn(){ new JNetServer(); LoginMap = new HashMap<String,SelectionKey>(); GroupMap = new HashMap<Long,LinkedList<String>>(); } //받은 이벤트처리 protected void RegMessage(SelectionKey NetId, ByteBuffer RecvBuf, JNetServer JServer) { if (RecvBuf.limit() == 0) {/* 재전송 */} //패킷생성. JNetPacket RecvPacket = new JNetPacket(); //버퍼 포지션값 초기화->처음부터읽을수 있도록. RecvBuf.flip(); JSFrame.RegCnt(); String Msg_1, Msg_2, Msg_3, Msg_4, Msg_5 = ""; switch (RecvBuf.getShort()) { case JNetPacket.LOGIN: Msg_1 = JNetPacket.RecvOut(RecvBuf); LoginMap.put(Msg_1, NetId); RecvPacket.Type(JNetPacket.LOGININFOR); RecvPacket.Add(LoginMap.size()); LoginState(RecvPacket); JServer.SendAll(RecvPacket); break; case JNetPacket.CHAT: Msg_1 = JNetPacket.RecvOut(RecvBuf); Msg_2 = JNetPacket.RecvOut(RecvBuf); RecvPacket.Type(JNetPacket.CHAT); RecvPacket.Add(Msg_1); RecvPacket.Add(Msg_2); //로그인한 사람들에게 보내기 JServer.SendAll(RecvPacket); break; case JNetPacket.TESTPACKET: Msg_1 = JNetPacket.RecvOut(RecvBuf); Msg_2 = JNetPacket.RecvOut(RecvBuf); RecvPacket.Type(JNetPacket.TESTPACKET); RecvPacket.Add(Msg_1); RecvPacket.Add(Msg_2); //자기자신만 보내는 부분. JServer.Send(RecvPacket, NetId); break; case JNetPacket.GROUPWAIT: // 그룹대기,이름 Msg_1 = JNetPacket.RecvOut(RecvBuf); LinkedList<String> List = new LinkedList<String>(); //null일경우(값을 처음 넣을 경우. if (GroupMap.get(GroupCnt) == null) { List.add(Msg_1); GroupMap.put(GroupCnt, List); } else if (GroupMap.get(GroupCnt) != null) { List.addAll(GroupMap.get(GroupCnt)); List.add(Msg_1); GroupMap.put(GroupCnt, List); } GroupCheck++; //3명이상 올라갈시 다음 그룹으로 넘어감. if (GroupCheck % 3 == 0) { GroupCnt++; } JServer.RecvSend(NetId); break; case JNetPacket.GROUPSEND: // 그룹전송 Msg_1 = JNetPacket.RecvOut(RecvBuf); Msg_2 = JNetPacket.RecvOut(RecvBuf); RecvPacket.Type(JNetPacket.GROUPSEND); RecvPacket.Add(Msg_1); RecvPacket.Add(Msg_2); //그룹으로 전송 JServer.SendGroup(RecvPacket,Msg_1); break; case JNetPacket.DISCONNECT : //연결해제 JServer.disConnect(NetId); break; default: JServer.RecvSend(NetId); break; } }
//로그인할때마다 로그인한 사용자들이 누군지 알려주기위함 private void LoginState(JNetPacket Packet){ Iterator <String> itr = LoginMap.keySet().iterator(); while(itr.hasNext()) { Packet.Add((String)itr.next()); } } public static void main(String args[]) { Frame = new JSFrame(); } } //GUI부분 @SuppressWarnings("serial") class JSFrame extends JFrame implements ActionListener , Runnable { //================ // 변수들. //================ JLabel conectTitle,conectLabel; JLabel regTitle,regLabel; JLabel disTitle,disLabel; JLabel memoryTitle,memoryLabel; JLabel nameLabel; JLabel ipLabel; JLabel timeLabel; JButton startButton; JButton endButton; public static long conectCnt = 0; //연결한클라이언트Count public static long regCnt = 0; //클라이언트간의 송수신메시지 Count public static long disconetCnt = 0; //연결해제된 클라이언트 Count private long memoryUse = 0; //메모리 사용량 private short useTimeMin = 0; //서버가 가동된 시간(분) private short useTimeSec = 0; //서버가 가동된 시간(초) private JFrame MainFrame = new JFrame("-JServer 2.0 version-"); private Container contentPane = MainFrame.getContentPane(); //Server Class private ServerStart SerOn = null; public JSFrame(){ Font font = new Font("궁서",Font.ITALIC,20); Font font1 = new Font("돋움",Font.BOLD,15);
Color grayColor = new Color(62,57,64); Color orangeColor = new Color(255,221,207); Color blueColor = new Color(74,197,232); Color greenColor = new Color(58,232,67);
JPanel northPanel = new JPanel(new GridLayout(2,1)); nameLabel = new JLabel("JSieun Server OFF..",JLabel.CENTER); ipLabel = new JLabel("ip..",JLabel.CENTER); nameLabel.setForeground(Color.white); ipLabel.setForeground(Color.WHITE); northPanel.add(nameLabel); northPanel.add(ipLabel); northPanel.setOpaque(false);
JPanel centerPanel = new JPanel(); centerPanel.setOpaque(false);
JPanel statePanel = new JPanel(new GridLayout(4,2)); statePanel.setPreferredSize(new Dimension(300,200)); statePanel.setBackground(orangeColor);
conectTitle = new JLabel("-Connect : ",JLabel.LEFT); conectLabel = new JLabel("" + conectCnt,JLabel.CENTER); disTitle = new JLabel("-DisConect : ",JLabel.LEFT); disLabel = new JLabel("" + disconetCnt,JLabel.CENTER); regTitle = new JLabel("-RegMsg : ",JLabel.LEFT); regLabel = new JLabel(""+regCnt,JLabel.CENTER); memoryTitle = new JLabel("-MemUse : ",JLabel.LEFT); memoryLabel = new JLabel(""+memoryUse,JLabel.CENTER); timeLabel = new JLabel("..start time..",JLabel.CENTER);
} public static void ConectCnt(){++conectCnt;} public static void RegCnt(){++regCnt;} public static void DisConectCnt(){++disconetCnt;} }
이것도 마찬가지로, 소스중간중간에 아주 자세히 주석처리로 설명이 되어있습니다.
아 추가적으로 여기엔 메인 메소드가 있어서 이 파일을 실행시켜야 하는데요?
그때 폴더 형식을 좀 추가해야 합니다.
요렇게 icon 폴더 만든후, icon.png 를 넣어야 하는데요
그 이미지 파일은
<icon.png> 이걸로 사용하시면 되겠습니다.
====키포인트====
이 소스파일에서는 가장 중요하다고 생각 되는 곳은 바로
이 부분이라고 생각이 듭니다.
이부분은 protected 로 보호처리를 해서 상속되어 있는 곳 아니면 사용할수 없게 만들었습니다.
위 사진처럼 ServerStart를 상속걸어논후,
<JNetServer.java 일부분.>
패킷을 읽는 부분에 RegMessage 메소드를 실행하도록 했습니다.
제 서버 플랫폼을 사용해서 사용자에게 맞게 설정할경우, RegMessage 이 부분을 가장 많이 사용할것으로 보여
사용법에 대해서 설명해 드리겠습니다.
우서 패킷의 구조는
타입
(2bytes)
데이터사이즈
(4bytes)
데이터
(?bytes)
데이터사이즈
(4bytes)
데이터
(?bytes)
...
...
...
...
이렇게 되어있습니다.
즉, 클라이언트나 서버가 보낼때 로그인을 할지, 채팅 메시지를 보내는 것인지 등등..이러한 "경로"를 정해주는 타입 부분을 앞에 2bytes 먹고, 그다음으론 어떠한 메시지를 보내는 지에 대한 정보값을 넣도록 했습니다.
그리고 그나머지 데이터 부분은
" 데이터사이즈 + 데이터 " 이렇게 구성하였는데, 그 이유는 데이터를 하나만 집어 넣는 것이 아니고, 여러개를 집어 넣고, 그리고 Bytes 형식을 문자열이나 정수형태로 형변환 할때 좀더 깔끔하게 처리 하기 위함입니다.
만약 앞에 데이터 사이즈를 넣지 않고, 그냥 데이터만 넣고
"123","하이요" 이렇게 데이터를 추가를 하면 나중에 형변환 할때 데이터가 깔끔이 처리가 안되는 경우가 있기 때문입니다.
※어차피 바이너리 방식이면 (Stream) 그냥 ObjectStream 형식으로 편히 하면 되지않나..라고 하시는 분들 가끔 계시는데, 그건 그냥 제 맘입니다.ㅋ 저는 저렇게 하는것이 편하고 저렇게 패킷을 하면, 장점이란게 있습니다.
값을 하나하나 깔끔하게 뽑을수 있습니다. 예를 들어 ObjectStream 이러한 형태로 뽑으면, 데이터를 한번에 뽑을수 있지만, 데이터를 경우에 따라서 한번에 뽑거나, 여러개를 뽑을 경우 문제가 생기기 때문에 저는 "제방식" 대로 구상을 했습니다. 머 아무튼.. 이래저래 글이 길었는데,
한마디로 제 맘입니다. ㅋㅋㅎ 그저 저게 더 편해서입니다 ㅎ
서론이 좀 많이 길었습니다.
다시 본론으로 넘어와서
저 RegMessage 사용법에 대해서 설명해드리도록 하겠습니다.
RegMessage안에 스위치 문 안에서 데이터를 뽑고 추가해서 보낼수 있습니다.
"패킷을 만드는 방법에 대해서 알려드리겠습니다."
처음에 RecvPacket.Type() 으로 타입을 정합니다.
그후에 RecvPacket.Add() 해서 넣고 싶은 데이터를 넣습니다.(int형,String형만 지원합니다. 뭐 long double 이것도 넣으라면 할수 있는데 굳이..필요 없을거 같아서 않넣었습니다.)
그후에 자기 자신에게 보내고싶다? 그럴경우
JServer.Send(RecvPacket, NetId);
로그인한 클라들한테 전체적으로 보내고싶다.
JServer.SendAll(RecvPacket);
그룹된 클라들한테 보내고싶다.
JServer.SendGroup(RecvPacket,"사용자이름");
여기서 유의해야 할 것은 클라이언트가 서버에게 보내고 아무것도 안보내는 경우가 있을 것입니다.
예를 들면 클라이언트내용들을 맵에저장하거나, DB에 저장하고 끝낼경우, 그러면 클라이언트(동기식) 이기 때문에 대기 상태가 됩니다. 그래서