1. 소켓(Socket)
1) 네트워크에서 동작하는 프로그램의 종착점(endpoint).
2) ip 주소와 port 번호로 이루어짐
3) 서버와 클라이언트가 양방향 통신할수 있게 해주는 소프트웨어 장치
- 둘 다 소켓을 생성하여 연결해줘야 함
2. 소켓 통신 절차
1) 서버에서 서버용 소켓(ServerSocket)을 생성, 클라이언트의 접속 대기함
2) 클라이언트가 소켓(Socket) 생성하여 서버로 연결 요청함
3) 서버가 접속을 허가(accept)함
4) 서버와 클라이언트는 각각 통신을 위한 I/O 스트림 생성함
5) 스트림을 통해 서버와 클라이언트가 통신함(write, read)
6) 클라이언트가 모든 작업을 마친 후 소켓 종료(close)함
7) 서버는 새로운 클라이언트 접속을 위해 대기(accept)하거나, 종료(close)할수 있음.
3. 웹 소켓
1) 일반적인 웹 환경은 클라이언트의 요청을 받으면 응답 후 바로 연결을 종료하는 비연결(connectionless) 동기 소켓 방식을 사용함.
2) 웹소켓(WebSocket)은 클라이언트의 요청에 응답한 후에도 연결을 그대로 유지하는 연결 지향 방식(connection oriented)임.
- 별도의 요청이 없어도 서버는 원하면 언제든 클라이언트로 데이터를 전송 가능함.
4. 웹 소켓 서버 구현시 사용하는 애너테이션
1) @ServerEndPoint : 웹소켓 서버의 요청명을 설정함
2) @OnOpen : 클라이언트 접속 시 실행되는 메서드를 정의
3) @OnMessage : 클라이언트로부터 메시지가 전송되었을 때 실행되는 메서드 정의
4) @OnClose : 클라이어트의 접속이 종료되면 실행되는 메서드 정의
5) @OnError : 에러 발생시 실행되는 메서드 정의
5. 채팅 서버 구현
https://tomcat.apache.org/whichversion.html
ws://호스트:포트번호/컨텍스트루트/ChatingServer
6. 채팅 클라이언트 구현
1) 채팅 참여 화면
2) 채팅 창 화면
<multiChattingMain.jsp>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<c:set var="contextPath" value="${pageContext.request.servletContext.contextPath}" />
<%
request.setCharacterEncoding("UTF-8");
%>
<html>
<head>
<title>웹소캣 채팅</title>
<script type="text/javascript">
function chatWindowOpen() { // 채팅창을 팝업창으로 열어주는 함수
let id = document.getElementById("chatId");
if (id.value == "") { // 프로필명이 입려되지 않았다면 alert 띄움
alert("프로필명을 입력 후 채팅창을 열어주세요!");
id.focus();
return;
}
window.open("chattingWindow.jsp?chatId=" + id.value, "", "width=300, height=400");
id.value = ""; // 새로운 프로필명 입력할수 있도록 기존 내용 제거
}
</script>
</head>
<body>
<h2>웹소켓 채팅 - 채팅창 생성하기</h2>
프로필명 : <input type="text" id="chatId">
<button onclick="chatWindowOpen();">채팅 참여하기</button>
</body>
</html>
<chattingWindow.jsp>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<c:set var="contextPath" value="${pageContext.request.servletContext.contextPath}" />
<%
request.setCharacterEncoding("UTF-8");
%>
<html>
<head>
<title>웹소켓 채팅창</title>
<style type="text/css">
#chatWindow {
border: 1px solid black; width: 280px; height: 280px; overflow: scroll;padding: 5px;
}
#chatMessage {
width: 240px; height: 30px;
}
#sendBtn {
height: 30px; position: relative; top: 2px; left: -2px;
}
#closeBtn {
margin-bottom: 3px; position: relative; top: 2px; left: -2px;
}
.myMsg {
text-align: right;
}
</style>
<script type="text/javascript">
let webSocket = new WebSocket("<%=application.getInitParameter("CHATSERVER_ADDR") %>/ChatingServer");
let chatWindow, chatMessage, chatId;
// 채팅창이 열리면 대화창, 메세지 입력창, 대화명 표시란으로 사용할 DOM 객체 저장
window.onload = function() {
chatWindow = document.getElementById("chatWindow");
chatMessage = document.getElementById("chatMessage");
chatId = document.getElementById("chatId").value;
}
// 클라이언트의 메시지 전송하는 메서드임
function sendMessage() {
//대화창에 표시
chatWindow.innerHTML += "<div class='myMsg'>" +chatMessage.value+ "</div>";
webSocket.send(chatId +'|'+ chatMessage.value); //서버로 전송 (대화명|메시지)
chatMessage.value = ""; //메시지 입력창 내용 지움
chatWindow.scrollTop = chatWindow.scrollHeight; //스크롤바는 항상 아래로 내려줌
}
//서버 연결 종료
function disconnect() {
webSocket.close();
}
//엔터 키 입력 처리
function enterKey() {
if (window.event.keyCode == 13) { //13은 'Enter' 키 코드값
sendMessage();
}
}
//웹소켓과 관련한 이벤트가 발생시 호출되는 함수들 정의
//각 상황은 이벤트로 전달됨. => 리스너가 감지하여 이 메서들을 호출해줄것임.
//호출시 인수로 이벤트 객체가 전달됨.
//웹소켓 서버에 연결됐을 때 실행.
webSocket.onopen = function(event) {
chatWindow.innerHTML += "웹소켓 서버에 연결되었습니다. <br>";
}
//웹소켓이 닫혔을때 실행.
webSocket.onclose = function(event) {
chatWindow.innerHTML += "웹소켓 서버가 종료되었습니다. <br>";
}
//에러 발생시 실행
webSocket.onerror = function(event) {
alert(event.data);
chatWindow.innerHTML += "채팅 중 에러가 발생했습니다. <br>";
}
//메시지를 받았을 때 실행
webSocket.onmessage = function(event) {
let message = event.data.split("|"); //프로필명과 메시지 분리
let sender = message[0]; //보낸사람의 프로필명
let content = message[1]; //메시지 내용
if (content !="") {
if (content.match("/")) { //귓속말
if (content.match(("/" + chatId))) {
var temp = content.replace(("/" + chatId), "[귓속말] : ");
chatWindow.innerHTML += "<div>" +sender+ "" +temp+ "</div>";
}
}
else { //일반대화
chatWindow.innerHTML += "<div>" +sender+ " : " +content+ "</div>";
}
}
chatWindow.scrollTop = chatWindow.scrollHeight;
};
</script>
</head>
<body>
프로필명 : <input type="text" id="chatId" value="${param.chatId }" readonly>
<button id="closeBtn" onclick="disconnect();" >채팅 종료</button>
<div id="chatWindow"></div>
<div>
<input type="text" id="chatMessage" onkeyup="enterKey();">
<button id="sendBtn" onclick="sendMessage();">전송</button>
</div>
</body>
</html>
<ChatServer.java>
package kr.co.ezenac.websocket;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
/*
* ws://호스트:포트번호/컨텍스트루트/ChatingServer
* @ServerEndpoint : 위 요청명으로 접속하는 클라이언트를 아래 클래스가 처리하게 함
*/
@ServerEndpoint("/ChatingServer")
public class ChatServer {
/*
* 새로 접속한 클라이언트의 세션을 저장할 컬렉션 생성.
* 멀티 스레드 환경에서 안전한 컬렉션 생성해줌. => 여러 클라이언트 동시 접속해도 문제가 않생김.
*/
private static Set<Session> clients = Collections.synchronizedSet(new HashSet<>());
@OnOpen // 클라이언트 접속 시 실행
public void opOpen(Session session) {
clients.add(session); // clients 컬렉션에 클라이언트의 세션 추가함.
System.out.println("웹소켓 연결 : " + session.getId());
}
@OnMessage // 클라이언트로부터 메시지를 받으면 실행 -- 클라이언트가 보낸 메시지, 클라이언트와 연결된 세션
public void onMessage(String message, Session session) throws IOException {
System.out.println("메시지 전송 : " +session.getId()+ ":" + message);
synchronized (clients) {
for (Session client : clients) { // 모든 클라이언트에게 메시지를 전송함.
if (!client.equals(session)) { // 단, 메시지를 보낸 클라이언트는 제외.
client.getBasicRemote().sendText(message);
}
}
}
}
@OnClose //클라이언트와의 연결이 끊기면 실행
public void onClose(Session session) {
clients.remove(session); //해당 클라이언트의 세션 삭제함
System.out.println("웹 소켓 종료 : " + session.getId());
}
@OnError //에러 발생 시 실행
public void onError(Throwable e) {
System.out.println("에러 발생");
e.printStackTrace();
}
}
'Web > Java' 카테고리의 다른 글
Java(31) - 멀티스레드2 (0) | 2022.01.28 |
---|---|
Java(30) - review4 (0) | 2022.01.28 |
Java(28) - IO입출력 (0) | 2022.01.28 |
Java(27) - 예외처리 (0) | 2022.01.28 |
Java(26) - Stream (0) | 2022.01.28 |
댓글