IP와 포트번호
로 이루어져 있다.serverSocket = new ServerSocket(50002);
클라이언트와 서버 둘 다 소켓을 생성
하여 연결해 주는 것chatClient
socket = new Socket("localhost", 50002);
chatServer
serverSocket = new ServerSocket(50002);
독립적으로 지속적인 양방향 연결(Stream)
을 해주는 기술upgrade
헤더와 connection
헤더를 포함하는 HTTP요청을 보낸다.HTTP
많은 URI와 Http Method를 통해
웹 어플리케이션과 상호작용synchronizedSet( )
메서드는 멀티 스레드 환경에서도 안전한(thread-safe) Set 컬렉션을 생성동기화
package 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;
@ServerEndpoint("/ChatingServer") //1. 서버를 만들어 웹 소켓임을 지정
public class ChatServer {
// 2. 여러 클라이언트가 동시에 접속해도 문제가 생기지 않도록 동기화
private static Set<Session> clients
= Collections.synchronizedSet(new HashSet<Session>());
@OnOpen // 3. 클라이언트가 접속했을 때 실행할 메서드를 정의
public void onOpen(Session session) {
// clients 컬렉션에 클라이언트의 세션을 추가
clients.add(session);
System.out.println("웹소켓 연결:" + session.getId());
}
@OnMessage // 메시지를 받으면 실행
public void onMessage(String message, Session session) throws IOException {
System.out.println("메시지 전송 : " + session.getId() + ":" + message);
//5. 동시에 여러명[클라이언트들]에게 메세지를 전송해야 함
synchronized (clients) {
for (Session client : clients) { // 모든 클라이언트에 메시지 전달
// sendText안에 금요일에 작업한 outputStream, inputStream 등이 다 들어 강 있음
//6. 메세지를 다수에게 돌리려면 세션에서 하나씩 뽑아서 돌림
if (!client.equals(session)) { //7. set은 순서가 없기에 또 들어올 수 있으니 메세지 보낸 클라이언트는 제외
client.getBasicRemote().sendText(message);
}
}
}
}
// 8. 클라이언트가 접속을 종료했을 때 실행할 메서드를 정의 clients에서 해당 클라이언트의 세션을 삭제
@OnClose
public void onClose(Session session) {
clients.remove(session);
System.out.println("웹소켓 종료 : " + session.getId());
}
// 9, 에러가 발생했을 때 실행할 메서드를 정의
@OnError
public void onError(Throwable e) {
System.out.println("에러 발생");
e.printStackTrace();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" id="WebApp_ID" version="4.0">
<display-name>MutiBackend14</display-name>
<context-param>
<param-name>CHAT_ADDR</param-name>
<param-value>ws://localhost:8090/webChatting</param-value>
</context-param>
</web-app>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head>
<title>웹소켓 채팅</title>
<script>
/* 1. 선언해둔 웹소켓 접속 URL 뒤에 요청명을 덧붙여 웹 소켓 생성 */
var webSocket
= new WebSocket("<%= application.getInitParameter("CHAT_ADDR") %>/ChatingServer");
/* 밑에 선언해둔 것들을 불러옴 */
var chatWindow, chatMessage, chatId;
/* 2. 채팅창이 열리면 대화창, 메세지 입력창, 대화명 표시란으로 사용할 DOM객체[body안에 element 값을 가져옴] 저장 */
window.onload = function() {
chatWindow = document.getElementById("chatWindow");
chatMessage = document.getElementById("chatMessage");
chatId = document.getElementById('chatId').value;
}
/* 3. 메세지 전송 기능 */
/* <button id="sendBtn">에 있는 이름 그대로*/
function sendMessage() {
// 대화창에 표시
chatWindow.innerHTML += "<div class='myMsg'>" + chatMessage.value + "</div>"
webSocket.send(chatId + '|' + chatMessage.value); // 서버로 전송
chatMessage.value = ""; // 메시지 입력창 내용 지우기
chatWindow.scrollTop = chatWindow.scrollHeight; // 대화창 스크롤
}
/* 4. 서버와의 연결 종료 */
function disconnect() {
webSocket.close();
}
/* 5. 엔터 키 입력처리 enterKey() 엔터키를 눌리면 sendMessage() 호출 매번[전송]버튼을 누리지 않아도 엔터를 누르면 즉시 */
function enterKey() {
if (window.event.keyCode == 13) { // 13은 'Enter' 키의 코드값
sendMessage();
}
}
/* 6. 웹소켓 서버에 연결됐을 때 실행 */
webSocket.onopen = function(event) {
chatWindow.innerHTML += "웹소켓 서버에 연결되었습니다.<br/>";
};
/* 7. 웹 소켓이 닫혔을 때(서버와의 연결이 끊어졌을 때) 실행 */
webSocket.onclose = function(event) {
chatWindow.innerHTML += "웹소켓 서버가 종료되었습니다.<br/>";
};
/* 8. 에러 발생 시 실행 */
webSocket.onerror = function(event) {
alert(event.data);
chatWindow.innerHTML += "채팅 중 에러가 발생하였습니다.<br/>";
};
/* 9. 메세지를 받았을 때 실행 */
webSocket.onmessage = function(event) {
var message = event.data.split("|"); // 대화명과 메시지 분리
var sender = message[0]; // 보낸 사람의 대화명
var 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>
<style> <!-- 대화창 스타일 지정 -->
#chatWindow{border:1px solid black; width:270px; height:310px; overflow:scroll; padding:5px;}
#chatMessage{width:236px; height:30px;}
#sendBtn{height:30px; position:relative; top:2px; left:-2px;}
#closeBtn{margin-bottom:3px; position:relative; top:2px; left:-2px;}
#chatId{width:158px; height:24px; border:1px solid #AAAAAA; background-color:#EEEEEE;}
.myMsg{text-align:right;}
</style>
</head>
<body> <!-- 대화창 UI 구조 정의 -->
대화명 : <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>
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<html>
<head><title>웹소켓 채팅</title></head>
<body>
<script>
function chatWinOpen() {
var id = document.getElementById("chatId");
if (id.value == "") {
alert("대화명을 입력 후 채팅창을 열어주세요.");
id.focus();
return;
}
window.open("ChatWindow.jsp?chatId=" + id.value, "", "width=320,height=400");
id.value = "";
}
</script>
<h2>웹소켓 채팅 - 대화명 적용해서 채팅창 띄워주기</h2>
대화명 : <input type="text" id="chatId" />
<button onclick="chatWinOpen();">채팅 참여</button>
</body>
</html>
Q. 자바스크립트로 작성한 코드와 자바에 작성하는 코드는 뭐가 달라? 둘 다 동적인데 분리해서 쓰는 이유가 뭐야?
자바스크립트로 작성한 코드와 자바로 작성하는 코드는 목적과 환경에 따라 사용되는 언어가 다르다.
언어적 차이
:
- 자바스크립트: 자바스크립트는 클라이언트 측에서 웹 브라우저 내에서 실행되는 스크립트 언어입니다. 주로 HTML과 함께 사용되며, 웹 페이지의 동적인 동작, 사용자 상호작용, 데이터 검증 등을 처리하는데 사용됩니다.
- 자바: 자바는 주로 서버 측에서 실행되는 객체 지향 프로그래밍 언어입니다. 웹 서버, 애플리케이션 서버, 모바일 앱, 소프트웨어 개발 등 다양한 분야에서 활용됩니다. 자바는 클라이언트 측에도 사용할 수 있지만, 주로 서버 측에서 사용되는 경우가 많습니다.
역할과 책임의 분리
:
- 자바스크립트: 자바스크립트는 주로 클라이언트 측에서 사용자 인터페이스와 상호작용을 담당합니다. 예를 들어, 웹 페이지의 동적인 내용 변경, 사용자 입력의 유효성 검사, 애니메이션, 이벤트 처리 등을 수행합니다.
- 자바: 자바는 주로 서버 측에서 비즈니스 로직과 데이터 처리를 담당합니다. 데이터베이스와의 상호작용, 계산, 데이터 처리, 비즈니스 규칙 구현 등을 주로 처리합니다.
- 관심사 분리: 자바스크립트는 클라이언트 측에서 주로 사용자 인터페이스와 상호작용을 다루는데 적합하고, 자바는 서버 측에서 비즈니스 로직과 데이터 처리에 적합합니다. 각 언어가 자신의 역할을 수행하면 코드가 더욱 읽기 쉽고 유지 보수하기 쉬워집니다. 또한, 클라이언트와 서버가 분리되어 있기 때문에 웹 애플리케이션의 확장성과 유연성이 증가합니다.
- 보안성 측면: 자바스크립트는 클라이언트 측에서 실행되기 때문에 코드가 노출될 수 있고, 악의적인 사용자에 의해 변조될 수 있습니다. 반면 자바는 서버 측에서 실행되기 때문에 보안 측면에서 더욱 안전합니다. 중요한 비즈니스 로직과 데이터 처리는 서버 측에서 보호되므로 보안성이 높아집니다.
따라서, 자바스크립트와 자바를 각각 적절한 영역에서 사용하면서 역할과 책임을 분리하여 웹 애플리케이션을 구성하는 것이 좋습니다. 이렇게 하면 코드의 유지 보수성과 보안성을 향상시킬 수 있습니다.
Q . getBasicRemote()는 왜 사용하는 걸까요?
if(!client.equals(session)) { //7. set은 순서가 없기에 또 들어올 수 있으니 메세지 보낸 클라이언트는 제외 client.getBasicRemote().sendText(message);}
client를 서버와 연결하여 메시지를 보내는 기능. getBasicRemote()
메세지는 text,binary,pong 3가지 타입을 보낼수 있다.
JSON 포맷
을 가장 많이 사용{ ←객체(중괄호)
"name": "낙자쌤", "age":45, "address": "서울시 금천구 가산동"
}
[ ←배열(대괄호)
"Java", "Oracle", "HTML5", "JSP", "JavaScript"
]
{ ←객체
"firstName": "Mush", "lastName": "Have",
"Books": ["JAVA","JSP","GO","Python"] ←배열
}
[ ←배열
{"City": "서울", "HotPlace": "이태원"}, ←객체
{"City": "부산", "HotPlace": "해운대"},
{"City": "대구", "HotPlace": "삼덕동"}
]
⇒ API 신청
package api;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/NaverSearchAPI.do")
public class SearchAPI extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
// 1. 인증 정보 설정
String clientId = "D8KD695q70780YufsUVl";
String clientSecret = "JgTlht42uX";
// 2. 검색 조건 설정
int startNum = 0; // 검색 시작 위치
String text = null; // 검색어
try {
startNum = Integer.parseInt(req.getParameter("startNum"));
String searchText = req.getParameter("keyword");
text = URLEncoder.encode(searchText, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("검색어 인코딩 실패", e);
}
// 3. API URL 조합
String apiURL = "https://openapi.naver.com/v1/search/blog?query=" + text
+ "&display=10&startNum=" + startNum; // json 결과
//String apiURL = "https://openapi.naver.com/v1/search/blog.xml?query=" + text; // xml 결과
// 4. API 호출
Map<String, String> requestHeaders = new HashMap<>();
requestHeaders.put("X-Naver-Client-Id", clientId);
requestHeaders.put("X-Naver-Client-Secret", clientSecret);
String responseBody = get(apiURL, requestHeaders);
// 5. 결과 출력
System.out.println(responseBody); // 콘솔에 출력
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().write(responseBody); // 서블릿에서 즉시 출력
}
private static String get(String apiUrl, Map<String, String> requestHeaders){
HttpURLConnection con = connect(apiUrl);
try {
con.setRequestMethod("GET");
for(Map.Entry<String, String> header :requestHeaders.entrySet()) {
con.setRequestProperty(header.getKey(), header.getValue());
}
int responseCode = con.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) { // 정상 호출
return readBody(con.getInputStream());
} else { // 에러 발생
return readBody(con.getErrorStream());
}
} catch (IOException e) {
throw new RuntimeException("API 요청과 응답 실패", e);
} finally {
con.disconnect();
}
}
private static HttpURLConnection connect(String apiUrl){
try {
URL url = new URL(apiUrl);
return (HttpURLConnection)url.openConnection();
} catch (MalformedURLException e) {
throw new RuntimeException("API URL이 잘못되었습니다. : " + apiUrl, e);
} catch (IOException e) {
throw new RuntimeException("연결이 실패했습니다. : " + apiUrl, e);
}
}
private static String readBody(InputStream body){
InputStreamReader streamReader = new InputStreamReader(body);
try (BufferedReader lineReader = new BufferedReader(streamReader)) {
StringBuilder responseBody = new StringBuilder();
String line;
while ((line = lineReader.readLine()) != null) {
responseBody.append(line);
}
return responseBody.toString();
} catch (IOException e) {
throw new RuntimeException("API 응답을 읽는데 실패했습니다.", e);
}
}
}
서버 문제인지 자꾸 nullpointException이 난다... 강사님도 처음에는 오타문제인것 같다고 하셨는데 확인해보시더니 서버 문제인 것 같다고 지금까지도 찾아보고 있는데 안돼서 이클립스 다시 깔고 서버 구축 다시 하는 중이다...
좋은 정보 감사합니다