웹소켓 프로토콜 판단, 악수 및 피드백

메시지 센터의 배치는 웹소켓을 통해 백엔드 서버와 긴 연결을 구축하는 방식으로 이루어진다. 이런 방식의 장점은 첫째, 네트워크 대역폭을 절약하는 것이다. 둘째, 사용자는 백엔드에서 보낸 메시지를 실시간으로 받을 수 있고 백엔드의 실현은 NETTY를 사용한다. 압력 테스트를 통해 서버당 50만 개의 긴 연결을 감당할 수 있다. 즉, 동시에 50만 명의 사용자(각 사이트 사용자에게만 긴 연결을 구축할 수 있다).성능은 그래도 비교적 좋다.
긴 연결을 하려면 먼저 클라이언트가 서버와의 악수 동작을 시작해야 한다. 다음은 위키백과에서 찾은 예이다.
브라우저 요청:
GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Sec-WebSocket-Key2: 12998 5 Y3 1  .P00
Sec-WebSocket-Protocol: sample
Upgrade: WebSocket
Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5
Origin: http://example.com
^n:ds[4U

요청에 있는 "Sec-WebSocket-Key1", "Sec-WebSocket-Key2", 마지막 "^n:ds[4U"는 랜덤입니다. 서버 측은 이 데이터로 16바이트의 응답을 구성합니다. 그 중에서: ^n:ds[4U는 요청한 내용입니다. 다른 것은 http 요청 헤더입니다.
주: Sec-WebSocket-Key1과 Sec-WebSocket-Key2는 낡은 웹소켓 프로토콜에 없습니다. 현재 요청이 웹소켓인지 아닌지를 판단하기 때문입니다. 주로 요청 헤더의 연결이 업그레이드인지, 업그레이드가 웹소켓인지 여부를 판단하기 때문입니다. 즉, 하나의 요청이 웹소켓인지 아닌지를 판단하려면 요청 헤더의 연결과 업그레이드만 판단하면 됩니다.새 버전은 Sec-WebSocket-Key1과 Sec-WebSocket-Key2를 포함할 수 있는지 여부를 판단합니다.다음은 웹소켓 요청 여부를 판단하는 코드입니다.
// : Netty 
private boolean isWebSocketReq(HttpRequest req) {
        return (HttpHeaders.Values.UPGRADE.equalsIgnoreCase(req.getHeader(HttpHeaders.Names.CONNECTION)) && HttpHeaders.Values.WEBSOCKET.equalsIgnoreCase(req.getHeader(HttpHeaders.Names.UPGRADE)));
    }

서버 응답:
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: http://example.com
Sec-WebSocket-Location: ws://example.com/demo
Sec-WebSocket-Protocol: sample
8jKS’y:G*Co,Wxa-
요청한 첫 번째 키의 숫자를 첫 번째 키의 공백 문자로 나누고, 두 번째 키도 마찬가지다.그리고 이 두 결과를 요청의 마지막 8바이트 문자열과 연결시켜 하나의 문자열로 만들고, 서버 응답 본문 ("8jKS'y:G*Co, Wxa-") 즉 이 문자열의 MD5sum입니다.Netty 기반 응답 JAVA 코드는 다음과 같습니다.
    // Netty WEBSOCKET 
    private HttpResponse buildWebSocketRes(HttpRequest req) {
        HttpResponse res = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                                                   new HttpResponseStatus(101, "Web Socket Protocol Handshake"));
        res.addHeader(HttpHeaders.Names.UPGRADE, HttpHeaders.Values.WEBSOCKET);
        res.addHeader(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.UPGRADE);
        // Fill in the headers and contents depending on handshake method.
        if (req.containsHeader(Names.SEC_WEBSOCKET_KEY1) && req.containsHeader(Names.SEC_WEBSOCKET_KEY2)) {// 7.5、7.6 
            // New handshake method with a challenge:
            res.addHeader(Names.SEC_WEBSOCKET_ORIGIN, req.getHeader(Names.ORIGIN));
            res.addHeader(Names.SEC_WEBSOCKET_LOCATION, getWebSocketLocation(req));
            String protocol = req.getHeader(Names.SEC_WEBSOCKET_PROTOCOL);
            if (protocol != null) {
                res.addHeader(Names.SEC_WEBSOCKET_PROTOCOL, protocol);
            }

            // Calculate the answer of the challenge.
            String key1 = req.getHeader(Names.SEC_WEBSOCKET_KEY1);
            String key2 = req.getHeader(Names.SEC_WEBSOCKET_KEY2);
            int a = (int) (Long.parseLong(getNumeric(key1)) / getSpace(key1).length());
            int b = (int) (Long.parseLong(getNumeric(key2)) / getSpace(key2).length());
            long c = req.getContent().readLong();
            ChannelBuffer input = ChannelBuffers.buffer(16);
            input.writeInt(a);
            input.writeInt(b);
            input.writeLong(c);
            ChannelBuffer output = null;
            try {
                output = ChannelBuffers.wrappedBuffer(MessageDigest.getInstance("MD5").digest(input.array()));
            } catch (NoSuchAlgorithmException e) {
                logger.error("no such Algorithm : MD5. ", e);
            }

            res.setContent(output);
        } else {// websocket 
            // Old handshake method with no challenge:
            if (req.getHeader(Names.ORIGIN) != null) {
                res.addHeader(Names.WEBSOCKET_ORIGIN, req.getHeader(Names.ORIGIN));
            }
            res.addHeader(Names.WEBSOCKET_LOCATION, getWebSocketLocation(req));
            String protocol = req.getHeader(Names.WEBSOCKET_PROTOCOL);
            if (protocol != null) {
                res.addHeader(Names.WEBSOCKET_PROTOCOL, protocol);
            }
        }

        return res;
    }
    //  
    private String getNumeric(String str) {
        return str.replaceAll("\\D", "");
    }

    //  
    private String getSpace(String str) {
        return str.replaceAll("\\S", "");
    }

후기:
최근 크롬14 및 FF6.5에서 최신 웹소켓 초안 10 프로토콜을 사용한 것을 발견했다. 즉, 위의 예시된 코드는 초안 10의 악수 프로토콜을 지원하지 못한다. 초안 손의 프로토콜 변수가 비교적 크다. 예를 들어 전송은 프레임을 통해 이루어지고 프레임의 위치를 검사할 권리가 있다는 등이다. 상세하게 나의 다른 글을 볼 수 있다.http://blog.csdn.net/fenglibing/article/details/6852497
풍립빈의 블로그

좋은 웹페이지 즐겨찾기