websocket的發送接收數據的實現(java實現)


上一篇文章( <https://www.cnblogs.com/thankvincisdaily/p/16009535.html >)是我在寫java服務端遇到的問題,本篇文章是完成了發送功能后寫出來的。

首先上數據幀格式(幀格式來源:https://www.cnblogs.com/laohaozi/p/12537571.html

 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+

---------------------------------------------------------發送功能-------------------------------------------------------------------------------------

在上一篇文章中我深刻地認識了websocket數據幀的格式,還是從數據幀入手,我剛入門,就不寫太復雜的,以免自己以后回頭看也看不懂。只要懂得如何接收,就可以知道如何發送,接收數據是拆包嘛,那發送數據就要進行包裝,

第一個字節是0x81(1000 0001)或者0x82(1000 0010),1是字符數據,2是二進制數據,如果單純發送文字,就1,如果還要發送文件就2,8就沒什么好說的,數據不分片(我也不知道要怎么分);

如下如下第二個字節是掩碼一位+PayLoadLen,服務端發送給客戶端的數據沒有嚴格要求一定要掩碼,那就設為0,PayLoadLen的值就等於數據的長度或者126(126<=數據長度<=0xff)或者127(0x100<=數據長度<=0xffffffff);

如果PayLoadLen的值等於數據的長度,那接下來從第三個字節開始就是數據部分PayLoadData;

那如果PayLoadLen的值等於126,則接下來第三,第四字節就是extendedPayLoadLen的值;

如果PayLoadLen的值等於127,則接下來第三到第十字節就是extendedPayLoadLen的值;在extendPayLoadLen后的字節就是PayLoadData。

因為發送的數據幀沒有掩碼,所以maskingkey4個字節也不用寫。

代碼如下:

public void sendMsg(String msg) throws IOException {
    byte[] data = msg.getBytes("UTF-8");
    int payLoadlen = data.length;
    byte[] first = new byte[1];
    first[0] = (byte) 0x81; //1000 0010 ,表示最后一個分片,數據為字符
    byte mask = 0; //先試試沒有mask的情況,之后再試試有mask的情況
    byte[] second = new byte[1];
    byte[] extendedLoad = null;
    if (payLoadlen < 126){
        payLoadlen = payLoadlen; //脫褲子放屁
    }else if (payLoadlen < (0xffff)){
        //00000000 00000000 11111111 11111111
        extendedLoad = new byte[2];
        int index = 0;
        int rshift = 8;
        while(index < 2){
            extendedLoad[index++] = (byte) (payLoadlen>>rshift&0xff);
            rshift-=8;
        }
        payLoadlen = 126;
    }else{
        //范圍:0x00010000 - 0x0fffffff,最高位為0
        //這里其實有一個問題,就是發送的數據理論上能達到2^64-1,
        // 但是就現實而言,能達到2^31-1(即int類型的最大值)都難
        // 因為數組length的返回值是int類型,最大也就32位了,所以,我只能將4個高位設置成全0)
        extendedLoad = new byte[8];
        int index = 0;
        while(index < 4){
            extendedLoad[index++] = 0;
        }
        int rshift = 24;
        while(index < 8){
            extendedLoad[index++] = (byte) (payLoadlen>>rshift&0xff);
            rshift-=8;
        }
        payLoadlen = 127;
    }
    second[0] = (byte) (payLoadlen&((mask<<8)+0x7f));
    byte[] msgbyte = null;
    if (extendedLoad == null){
        msgbyte = new byte[first.length+second.length+data.length];
        System.arraycopy(first,0,msgbyte,0,first.length);
        System.arraycopy(second,0,msgbyte,first.length,second.length);
        System.arraycopy(data,0,msgbyte,first.length+second.length,data.length);
    }else{
        msgbyte = new byte[first.length+second.length+extendedLoad.length+data.length];
        System.arraycopy(first,0,msgbyte,0,first.length);
        System.arraycopy(second,0,msgbyte,first.length,second.length);
        System.arraycopy(extendedLoad,0,msgbyte,first.length+second.length,extendedLoad.length);
        System.arraycopy(data,0,msgbyte,first.length+second.length+extendedLoad.length,data.length);
    }
    outs.write(msgbyte);
    outs.flush();
}

---------------------------------------------------------------------接收功能-------------------------------------------------------------------------

接收功能改動不大,只是把while里的代碼寫成一個方法,然后是payloadlen=127那里改成了實際上只有四個字節的數據,高位當作0讀取,和上面發送功能的一樣。

理由如下:

payloadlen=126時,最大的接收數據是0xffff,只有16位,2個字節,一位表示1字節的話也就是64KB,如果發送的文件稍大一點可能就不行了,所以127有必要用,但是127時最大的接收數據有64位,占數據幀8個字節,而java中int類型的數據占32位,4個字節,然后int類型的最大值是2147483647,能表示最大大約2G的數據長度,以我需求來說,4個字節的數據長度完全夠用,就不用long類型了。發送和接收數據的時候應該是很少遇到超過int類型表示的范圍的。

//接收數據的方法
public void recvMsg() throws IOException {
    byte[] first = new byte[1];
    ins.read(first); //將流中第一個字節的數據讀入first
    int finflag = (first[0]&0x80)>>7; //終止位信息fin = (xyyyyyyyy & 10000000)>>7 = x,
    int opcode = first[0]&0x0f; //表示接收的信息類型opcode = yyyyxxxx & 0000ffff = 0000xxxx
    //opcode為0時是接收附加數據,1是文本數據,2是二進制流數據,8是關閉連接
    if (opcode == 8){
        System.out.println("連接被瀏覽器關閉");
        this.close();
        return ;
    }
    byte[] second = new byte[1];
    ins.read(second); //讀入第二個字節
    int mask = (second[0]&0x80)>>7; //是否有掩碼,從瀏覽器發送過來的信息一定有,mask = (xyyyyyyyy & 10000000)>>7 = x
    int payloadlen = second[0]&0x7f; // payloadlen = yxxxxxxxx & 011111111 = 0xxxxxxxx
    int extendPayloadLen = 0; //
    if (payloadlen == 126){
        // 若payloadlen == 126,則接收的數據長度為后兩個字節的值,
        int index = 0;
        byte[] extended = new byte[2];//附加長度,下同
        ins.read(extended);
        while(index < 2){
            extendPayloadLen = extendPayloadLen<<8;
            extendPayloadLen += (extended[index++]&0xff);
        }
        payloadlen = extendPayloadLen;
    }else if(payloadlen == 127){
        // 若payloadlen == 127,則接收的數據長度為后八個字節的值。
        int index = 0;
        byte[] extended = new byte[8];
        ins.read(extended);
        while (index < 4){
            index++;
        }
        while(index < 8){
            extendPayloadLen = extendPayloadLen<<8;
            extendPayloadLen += (extended[index++]&0xff);
        }
        payloadlen = extendPayloadLen;
    }else{
        // 若payloadlen < 126,接收的數據長度為0~125,
        payloadlen = payloadlen;
    }
    byte[] maskingkey = new byte[4];
    ins.read(maskingkey); //讀入maskingkey
    byte[] themsg = new byte[payloadlen]; //假設數據段最多收1024字節
    //bains.read(themsg,0,payloadlen); //從themsg的0下標出發,讀入payloadlen字節
    ins.read(themsg,0,payloadlen);
    int index = 0;
    if (mask == 1){
        while(index < payloadlen){
            themsg[index] = (byte) ((themsg[index]^maskingkey[index%4])&0xff);
            index++;
        }
    }
    System.arraycopy(themsg,0,themsg,0,payloadlen);
    String msg = new String(themsg,"UTF-8");
    System.out.println(msg);
}

然后既然websocket是全雙工,那我就把兩個功能合起來測試,但是不論是inputstream的read還是scanner.next都會阻塞進程,所以我就寫了一個給接收數據專用的線程,發送數據就還是主線程。

運行結果:

HandShake OK
connect success
hello peter
hi?do i know u?
hi?do
i
know
u?

這里說明一下:‘hello peter‘是瀏覽器輸入框輸入后發送的;‘hi?do i know u?’是在idea運行的終端輸入的,空格就被當作分隔符,下面的分段是瀏覽器返回的,js里面設置了把接收到的數據發送回去。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM