웹 소켓 단순 구현

10866 단어 httpwebSocket
WebSocket 소개
인터넷 이 발전 함 에 따라 전통 적 인 HTTP 프로 토 콜 은 웹 응용 이 날로 복잡 해 지 는 수 요 를 만족 시 키 기 어렵다.최근 몇 년 동안 HTML 5 가 탄생 하면 서 웹 소켓 프로 토 콜 은 브 라 우 저 와 서버 의 모든 양 방향 통신 을 실현 하고 브 라 우 저 와 서버 의 통신 기능 을 확대 하여 서버 도 자발적으로 클 라 이언 트 에 데 이 터 를 보 낼 수 있 도록 했다.
우 리 는 전통 적 인 HTTP 프로 토 콜 이 무상 태 라 는 것 을 알 고 있 습 니 다. 매번 요청 (request) 은 클 라 이언 트 (예 를 들 어 브 라 우 저) 가 주동 적 으로 시작 해 야 합 니 다. 서버 가 처리 한 후에 response 결 과 를 되 돌려 주 고 서버 는 클 라 이언 트 에 데 이 터 를 주동 적 으로 보 내기 어렵 습 니 다.이러한 클 라 이언 트 는 주동 적 인 측 이 고 서비스 측은 수 동적 인 측의 전통 적 인 웹 모델 로 정보 변화 가 빈번 하지 않 은 웹 응용 에 있어 발생 하 는 번 거 로 움 이 비교적 적 으 며 실시 간 정보 와 관련 된 웹 응용 에 큰 불편 을 가 져 왔 다. 예 를 들 어 실시 간 통신, 실시 간 데이터, 구독 푸 시 등 기능 을 가 진 응용 이다.웹 소켓 규범 이 제기 되 기 전에 개발 자 들 은 이러한 실시 간 으로 비교적 강 한 기능 을 실현 하려 면 절충 적 인 해결 방법 인 폴 링 (polling) 과 Comet 기술 을 자주 사용한다.사실 후 자 는 본질 적 으로 도 일종 의 폴 링 으로 개선 되 었 을 뿐이다.
폴 링 은 실시 간 웹 응용 을 실현 하 는 가장 원시 적 인 해결 방안 이다.폴 링 기술 은 클 라 이언 트 가 설정 한 시간 간격 으로 주기 적 으로 서버 에 요청 을 보 내 고 새로운 데이터 변경 여 부 를 자주 조회 하도록 요구한다.이런 방법 은 불필요 한 요청 을 너무 많이 하고 데이터 와 서버 자원 을 낭비 하 는 것 이 분명 하 다.
Comet 기술 은 긴 폴 링 과 스 트림 기술 로 나 눌 수 있다.긴 폴 링 은 상술 한 폴 링 기술 을 개선 하여 쓸모없는 요 구 를 줄 였 다.일부 데이터 에 만 료 시간 을 설정 하고 데이터 가 만 료 된 후에 야 서버 에 요청 을 보 낼 수 있 습 니 다.이런 메커니즘 은 데이터 의 변경 에 적합 하 다. 특별히 빈번 한 상황 은 아니다.스 트림 기술 은 클 라 이언 트 가 숨겨 진 창 을 사용 하여 서버 와 HTTP 긴 연결 을 만 드 는 것 을 말 합 니 다. 서버 는 연결 상 태 를 계속 업데이트 하여 HTTP 긴 연결 을 유지 합 니 다.이렇게 하면 서버 는 이 긴 연결 을 통 해 자발적으로 데 이 터 를 클 라 이언 트 에 보 낼 수 있다.스 트림 기술 은 병발 환경 에서 서버 의 성능 을 시험 할 수 있다.
이 두 가지 기술 은 모두 요청 - 응답 모델 을 바탕 으로 하 는 것 으로 진정한 의미 의 실시 간 기술 이 아니다.그들의 모든 요청, 응답 은 똑 같은 머리 정보 에 일정한 데 이 터 를 낭비 하고 개발 의 복잡 도 도 비교적 크다.
HTML 5 가 출시 한 웹 소켓 과 함께 웹 의 실시 간 통신 을 진정 으로 실현 하여 B / S 모드 가 C / S 모드 의 실시 간 통신 능력 을 갖 추 게 되 었 다.웹 소켓 의 작업 절 차 는 다음 과 같 습 니 다. 브 라 우 저 는 자바 스 크 립 트 를 통 해 서버 에 웹 소켓 연결 을 요청 합 니 다. 웹 소켓 연결 이 성공 하면 클 라 이언 트 와 서버 는 TCP 연결 을 통 해 데 이 터 를 전송 할 수 있 습 니 다.웹 소켓 연결 은 본질 적 으로 TCP 연결 이기 때문에 전송 할 때마다 반복 되 는 머리 데 이 터 를 가 져 올 필요 가 없 기 때문에 폴 링 과 Comet 기술 보다 데이터 전 송 량 이 훨씬 적다.본 고 는 웹 소켓 규범 을 상세 하 게 소개 하지 않 고 주로 웹 소켓 이 자바 웹 에서 의 실현 을 소개 한다.
자바 EE 7 에서 JSR - 356: 자바 API for WebSocket 규범 이 나 왔 습 니 다.Tomcat, Nginx, Jetty 등 많은 웹 용기 들 이 웹 소켓 을 지원 한다.톰 캣 은 7.0.27 부터 웹 소켓 을 지원 하고 7.0.47 부터 JSR - 356 을 지원 하 며, 아래 데모 코드 역시 톰 캣 7.0.47 이상 에 배 치 된 버 전이 있어 야 실행 할 수 있다.
 코드
pom 문서
 
         javax
         javaee-api
         7.0
         provided
 

백 엔 드 코드:
package com.websocket;


import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ServerEndpoint            ,                 websocket    ,
 *                                    URL  ,         URL    WebSocket    
 */
@ServerEndpoint("/websocket/{clientId}")
public class WebSocket {


	//     ,           。            。
	private static AtomicInteger onlineCount = new AtomicInteger(0);

	// concurrent      Set,            MyWebSocket  。
	//                 ,    Map   ,  Key       
	// private static CopyOnWriteArraySet webSocketSet = new
	// CopyOnWriteArraySet();
	//            ,              
	//            ,         map   
	//private Session session;

	private static Map webSocketMap = new ConcurrentHashMap();


	/**
	 *            
	 * 
	 * @param session      。session            ,              
	 */
	@OnOpen
	public void onOpen(@PathParam("clientId") String clientId, Session session) {
		//         sessionId     webSocket       
		String key = getWebSocketMapKey(clientId, session);
		webSocketMap.put(key, session);
		addOnlineCount(); //     1
		System.out.println("WebSocket      !       " + getOnlineCount());
	}

	/**
	 *          
	 */
	@OnClose
	public void onClose(@PathParam("clientId") String clientId, Session session, CloseReason closeReason) {
		String key = getWebSocketMapKey(clientId, session);
		webSocketMap.remove(key, session);
		subOnlineCount(); //     1
		System.out.println("WebSocket      !       " + getOnlineCount());
	}

	/**
	 *              
	 * 
	 * @param message           
	 * @param session      
	 */
	@OnMessage
	public void onMessage(@PathParam("clientId") String clientId, String message, Session session) {
		System.out.println("WebSocket          :" + message);
		sendMessageByClientId(clientId, message);
	}

	/**
	 *   webSocketMap   Key
	 * 
	 * @param clientId     
	 * @param session  webSocket Session
	 * @return
	 */
	private String getWebSocketMapKey(String clientId, Session session) {
		if (StringUtil.isNil(clientId)) {
			return session.getId();

		} else {
			return clientId + "_" + session.getId();
		}
	}
	
	/**
	 *        
	 * 
	 * @param session
	 * @param error
	 */
	@OnError
	public void onError(Session session, Throwable error) {

		System.out.println("WebSocket    ");
	}


	//     
	public static void doSend(String message) {
		if (webSocketMap.size() > 0) {
			for (Map.Entry entry : webSocketMap.entrySet()) {
				try {
					sendMessage(entry.getValue(), message);
				} catch (IOException e) {
					System.out.println("WebSocket doSend is error:");
					continue;
				}
			}
		}
	}


	public static void sendMessage(Session session, String message) throws IOException {
		session.getBasicRemote().sendText(message);
	}

	public static int sendMessageByClientIdList(List clientIdList, String message) {
		int status = 0;
		for (String clientId : clientIdList) {
			status = sendMessageByClientId(clientId, message);
		}
		return status;
	}

	/**
	 *           webSocket  
	 * 
	 * @param clientId
	 * @param message
	 */
	public static int sendMessageByClientId(String clientId, String message) {
		int status = 0;
		if (webSocketMap.size() > 0) {
			for (Map.Entry entry : webSocketMap.entrySet()) {
				try {
					String key = entry.getKey();
					//   webSocketMap  clientId    clientId    
					//           
					String key1 = key.substring(0, key.lastIndexOf("_"));
					if (key1.equals(clientId)) {
						sendMessage(entry.getValue(), message);
						status = 200;
					}
				} catch (IOException e) {
					System.out.println("WebSocket doSend is error:");
					continue;
				}
			}
		}
		return status;
	}

	public static void sendSpeechMessageByClientId(String clientId, String message) {
		if (webSocketMap.size() > 0) {
			for (Map.Entry entry : webSocketMap.entrySet()) {
				try {
					String key = entry.getKey();
					//   webSocketMap  clientId    clientId    
					//           
					String key1 = key.substring(0, key.lastIndexOf("_"));
					if (key1.equals(clientId)) {
						sendMessage(entry.getValue(), message);
					}
				} catch (IOException e) {
					System.out.println("WebSocket doSend is error:");
					continue;
				}
			}
		}
	}

	public static synchronized AtomicInteger getOnlineCount() {
		return onlineCount;
	}

	public static synchronized void addOnlineCount() {
		WebSocket.onlineCount.getAndIncrement();
	}

	public static synchronized void subOnlineCount() {
		WebSocket.onlineCount.getAndDecrement();
	}
}

전단 코드:




    Java  WebSocket Tomcat  



    Welcome



var websocket = null; function connect() { // WebSocket if ('WebSocket' in window) { var value = $("#b").val(); websocket = new WebSocket("ws://localhost:8080/ws/websocket/"+value); // websocket.onerror = function () { setMessageInnerHTML("WebSocket "); }; // websocket.onopen = function () { setMessageInnerHTML("WebSocket "); } // websocket.onmessage = function (event) { setMessageInnerHTML(event.data); } // websocket.onclose = function () { setMessageInnerHTML("WebSocket "); } // , , websocket , ,server 。 window.onbeforeunload = function () { closeWebSocket(); } } else { alert(' Not support websocket') } }; // function setMessageInnerHTML(innerHTML) { document.getElementById('message').innerHTML += innerHTML + '<br/>'; } // WebSocket function closeWebSocket() { websocket.close(); } // function send() { var message = document.getElementById('text').value; websocket.send(message); }

또한 웹 소켓 연결 을 만 들 면 http 응답 코드 는 101 입 니 다.
알림
1XX 시리즈 응답 코드 는 HTTP 서버 와 소통 할 때 만 사 용 됩 니 다.
  • 100 ("Continue") 의 중요 도: 중간 이지 만 (조작 을 쓸 때) 거의 사용 되 지 않 습 니 다.

  • 이것 은 HTTP LBYL (look - before - you - leap) 요청 에 대한 가능 한 응답 입 니 다.이 응답 코드 는 클 라 이언 트 가 초기 요청 을 다시 보 내 고 요청 에 첫 번 째 요청 을 할 때 제공 하지 않 은 (크 거나 민감 한 정 보 를 포함 할 수 있 음) 표 시 를 첨부 해 야 한다 고 밝 혔 다.클 라 이언 트 가 이번에 보 낸 요청 은 거절 되 지 않 습 니 다.LBYL 요청 에 대한 또 다른 응답 은 417 ("Expectation Failed") 입 니 다.
    요청 헤더: LBYL 요청 을 하려 면 클 라 이언 트 가 expect 요청 헤 더 를 문자열 '100 - continue' 로 설정 해 야 합 니 다.그 밖 에 클 라 이언 트 는 다른 헤더 도 설정 해 야 한다. 서버 는 이 헤더 에 따라 응답 100 인지 417 인지 결정 할 것 이다.
  • 101 ("Switching Protocols") 의 중요 도: 매우 낮다.

  • 클 라 이언 트 가 요청 에서 Upgrade 헤 더 를 사용 하여 서버 에 HTTP 프로 토 콜 을 제외 한 다른 프로 토 콜 을 바 꾸 려 고 할 때 클 라 이언 트 는 이 응답 코드 를 받 습 니 다.101 응답 코드 는 "그래, 나 는 지금 다른 협 의 를 바 꾸 었 다" 고 표시 했다.보통 HTTP 클 라 이언 트 는 서버 로부터 101 응답 을 받 은 후 서버 와 의 TCP 연결 을 닫 습 니 다.101 응답 코드 는 이 클 라 이언 트 가 더 이상 HTTP 클 라 이언 트 가 아니 라 다른 클 라 이언 트 가 될 것 임 을 의미한다.업그레이드 헤 더 를 통 해 HTTP 에서 HTTPS 로 전환 하거나 HTTP 1.1 에서 미래의 버 전 으로 전환 할 수 있 지만 실제 업그레이드 헤 더 를 사용 하 는 경 우 는 적다.Upgrade 헤 더 는 HTTP 에서 완전히 다른 프로 토 콜 (예: IRC) 로 전환 하 는 데 도 사용 할 수 있 습 니 다. 그러나 웹 서버 에서 IRC 서버 로 전환 하 는 동시에 웹 클 라 이언 트 는 IRC 클 라 이언 트 로 전환 해 야 합 니 다. 서버 가 즉시 같은 TCP 연결 에서 새로운 프로 토 콜 을 사용 하기 시작 하기 때 문 입 니 다.
    요청 헤더: 클 라 이언 트 는 Upgrade 헤 더 를 원 하 는 프로 토 콜 로 설정 합 니 다.응답 헤더: 서버 가 프로 토 콜 전환 에 동의 하면 Upgrade 헤더 로 돌아 가 프로 토 콜 로 전환 하고 빈 줄 을 첨부 합 니 다.서버 는 TCP 링크 를 닫 지 않 고 이 TCP 연결 에서 새 프로 토 콜 을 직접 사용 합 니 다.

    좋은 웹페이지 즐겨찾기