[畢設(shè)記錄] socket自定義數(shù)據(jù)包發(fā)送圖片

寫在前面

還是畢業(yè)設(shè)計的事兒,還是發(fā)送圖片的事兒。前面已經(jīng)用了python實(shí)現(xiàn)圖片的發(fā)送,但是很蛋疼的是,上位機(jī)用的是vb.net來進(jìn)行開發(fā),python的在圖片發(fā)送和接收又變得不好使了。實(shí)驗(yàn)了若干種方法,下面記錄一下。

目標(biāo):在vb.net的環(huán)境下和Android之間使用socket進(jìn)行圖片的傳輸。

實(shí)現(xiàn):自定義一個簡單的傳輸協(xié)議,將圖片用字節(jié)流的方式發(fā)送和接收。

環(huán)境:在vb.net和Android上已經(jīng)試驗(yàn)成功,但是本文用java進(jìn)行演示(原因是:1.懶得連接Android了;2.實(shí)在很討厭vb的語言....)。邏輯是一樣的,只是具體的語法會有些少出入。

原理介紹

這次,在具體的實(shí)現(xiàn)之前,先扒一下原理。網(wǎng)絡(luò)傳輸,使用的時socket,這是一個建立在tcp協(xié)議上的通信機(jī)制。所以,有必要了解一下什么是tcp協(xié)議,什么是socket。

TCP / IP 協(xié)議

Transmission Control Protocol/Internet Protocol的簡寫,中譯名為傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議,又名網(wǎng)絡(luò)通訊協(xié)議,是Internet最基本的協(xié)議、Internet國際互聯(lián)網(wǎng)絡(luò)的基礎(chǔ),由網(wǎng)絡(luò)層的IP協(xié)議和傳輸層的TCP協(xié)議組成。TCP/IP 定義了電子設(shè)備如何連入因特網(wǎng),以及數(shù)據(jù)如何在它們之間傳輸?shù)臉?biāo)準(zhǔn)。協(xié)議采用了4層的層級結(jié)構(gòu),如下圖所示,每一層都呼叫它的下一層所提供的協(xié)議來完成自己的需求。(來自百度百科)

tcp ip協(xié)議層級示意圖

通俗而言:IP協(xié)議是給因特網(wǎng)的每一臺聯(lián)網(wǎng)設(shè)備規(guī)定一個地址,以便能夠在因特上上定位到該設(shè)備。TCP負(fù)責(zé)發(fā)現(xiàn)傳輸?shù)膯栴},一有問題就發(fā)出信號,要求重新傳輸,直到所有數(shù)據(jù)安全正確地傳輸?shù)侥康牡亍?/p>

socket

我們知道,程序在設(shè)備當(dāng)中運(yùn)行時是以進(jìn)程的形式被操作系統(tǒng)管理的。在本地,兩個進(jìn)程之間要通信,首先必須知道的是它們的PID。在網(wǎng)絡(luò)上,依靠PID來識別不同的進(jìn)程,顯然是不科學(xué)的,因?yàn)樗鼈儠貜?fù)。從上面,我們了解到每個連接到因特網(wǎng)的設(shè)備的ip都是唯一的。所以,我們可以借助ip再加上一個端口號,來進(jìn)行進(jìn)程的唯一識別。當(dāng)網(wǎng)絡(luò)進(jìn)程可以唯一識別之后,就可以使用socket進(jìn)行通信了。

socket,翻譯成中文是“套接字”。它是應(yīng)用層和傳輸層之間的一個抽象層,它把TCP/IP層復(fù)雜的操作抽象為幾個簡單的接口供應(yīng)用層調(diào)用已實(shí)現(xiàn)進(jìn)程在網(wǎng)絡(luò)中通信,如下圖所示。socket源起于UNIX,在一切皆是文件的哲學(xué)思想下,socket是“打開-讀寫-關(guān)閉”模式的實(shí)現(xiàn),服務(wù)端和客戶端共同維護(hù)一個“文件”,在連接建立之后,雙方都可以對文件進(jìn)行讀寫操作;通訊結(jié)束時,關(guān)閉該文件。

socket 示意圖

具體實(shí)現(xiàn)

有了上面的原理,我們可以著手來實(shí)現(xiàn)以下了。以下出現(xiàn)的代碼,只是為了演示,真實(shí)開發(fā)環(huán)境下不要這樣寫。

Java提供了socket的相關(guān)API,我們不需要知道怎么寫socket的底層,通過調(diào)用API就可以建立連接了。在建立連接之前,我們需要自己定義一個用于發(fā)送圖片的簡單的通信協(xié)議。我自己隨便定義了一個,如下圖中間部分所示。

自定義socket通信

通信協(xié)議,顧名思義,是用來規(guī)定通信過程的一種約定。只有服務(wù)端和客戶端都遵循同一個協(xié)議,才可以完成有效的通信。就想上圖表示的一樣,我規(guī)定了如下的內(nèi)容:

  • 每次發(fā)送的數(shù)據(jù)包長度為1029個字節(jié)(數(shù)據(jù)包是一個有符號的字節(jié)數(shù)組,下標(biāo)從0開始到1028結(jié)束)
  • 第一個字節(jié)是標(biāo)志位,該位為1時,表示后面還有數(shù)據(jù);該位為2時,表示這是最后一包數(shù)據(jù)
  • 數(shù)據(jù)包的[1 : 4]表示該數(shù)據(jù)包的有效數(shù)據(jù)長度,因?yàn)閿?shù)據(jù)包最大可以存放1024字節(jié)的數(shù)據(jù),所以[1 : 4]每一位分別表示“個十百千”位上的數(shù)字。除去最后一包數(shù)據(jù),其他包都應(yīng)該是放滿的,最后一包的數(shù)據(jù)則很大可能都不滿1024(配合流的read方法可以避免接收冗余數(shù)據(jù))。

有了上面的約定,我們就可以寫代碼了:

// SocketTest.java 下面代碼的運(yùn)行環(huán)境是Mac intellij14
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by eric_lai on 2016/3/31.
 */
public class SocketTest {

    // 服務(wù)端, 用來接收圖片
    public static void ServerSocket() {

        // 調(diào)試標(biāo)志
        String TAG = "ServerSocket: ";
        // 服務(wù)端socket
        ServerSocket serverSocket = null;
        // 端口號
        int port = 9998;
        // 緩沖區(qū)
        byte[] buffer = new byte[1029];
        int len = buffer.length;
        // 實(shí)際長度
        int dataLen = 0;
        // 文件輸出流
        FileOutputStream fileOutputStream = null;
        // client輸入流
        InputStream clientInputStream = null;
        // 文件路徑
        File file = new File("res/image2.jpg");
        try {
            fileOutputStream = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        try {
            // 綁定端口號
            serverSocket = new ServerSocket(port);
            // 堵塞線程, 等待連接
            System.out.println(TAG + "waitting for connection ...");
            Socket client = serverSocket.accept();
            System.out.println(TAG + "connection has been set up");
            // 獲取client輸入流
            clientInputStream = client.getInputStream();
            // 讀取數(shù)據(jù)
            byte[] lenInByte = new byte[4];
            clientInputStream.read(buffer, 0, len);
            byte flag = buffer[0];
            System.arraycopy(buffer, 1, lenInByte, 0, lenInByte.length);
            dataLen = ChangeByte2Int(lenInByte);
            System.out.println(TAG + "begin to receive image ...");
            while (true) {
                if (fileOutputStream != null) {
                    if (flag == 1) {
                        // 寫入文件
                        fileOutputStream.write(buffer, 5, dataLen);
                        // 讀取數(shù)據(jù)
                        clientInputStream.read(buffer, 0, len);
                        flag = buffer[0];
                        System.arraycopy(buffer, 1, lenInByte, 0, lenInByte.length);
                        dataLen = ChangeByte2Int(lenInByte);
                    } else {
                        // 寫入文件
                        fileOutputStream.write(buffer, 5, dataLen);
                        break;
                    }
                }
            }
            System.out.println(TAG + "image successfully received ...");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (fileOutputStream != null) {
                    fileOutputStream.close();
                }
                if (clientInputStream != null) {
                    clientInputStream.close();
                }
                if (serverSocket != null) {
                    serverSocket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }

    }

    public static void ClientSocket() {

        // 調(diào)試標(biāo)志
        String TAG = "ClientSocket: ";
        // 客戶端socket
        Socket clientSocket = null;
        // 服務(wù)器地址
        String ip = "127.0.0.1";
        // 端口號
        int port = 9998;
        // 緩沖區(qū)
        byte[] buffer = new byte[1029];
        int len = buffer.length;
        byte[] fullData = {0, 1, 0, 2, 4};
        // 實(shí)際長度
        int realLen = 0;
        // 文件末尾標(biāo)志
        int cf = 0;
        // 文件輸入流
        FileInputStream fileInputStream = null;
        // client輸出流
        OutputStream clientOutputStream = null;
        // 文件路徑
        File file = new File("res/image1.jpg");

        try {
            // 建立socket
            System.out.println(TAG+"set up connection to the server ...");
            clientSocket = new Socket(ip, port);
            // 獲取文件輸入流(讀取要發(fā)送的圖片)
            fileInputStream = new FileInputStream(file);
            // 獲取socket輸出流(發(fā)送數(shù)據(jù)包)
            clientOutputStream = clientSocket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (clientOutputStream != null) {
            try {
                System.out.println(TAG+"begin to send the image ...");
                while (cf != -1) {
                    // 讀取數(shù)據(jù)放到數(shù)據(jù)區(qū)域
                    cf = fileInputStream.read(buffer, 5, len - 5);
                    if (cf == -1) {
                        // 將長度轉(zhuǎn)為byte[]
                        byte[] by = ChangeInt2Byte(realLen);
                        // 將長度數(shù)據(jù)整合到數(shù)據(jù)包里面
                        System.arraycopy(by, 0, buffer, 0, by.length);
                        // 標(biāo)志位置2
                        buffer[0] = 2;
                        // 發(fā)送數(shù)據(jù)
                        clientOutputStream.write(buffer, 0, realLen + 5);
                    }else {
                        // 緩存實(shí)際的數(shù)據(jù)長度
                        realLen = cf;
                        System.arraycopy(fullData, 0, buffer, 0, fullData.length);
                        // 標(biāo)志位置1
                        buffer[0] = 1;
                        // 發(fā)送數(shù)據(jù)
                        clientOutputStream.write(buffer, 0, len);
                    }
                }
                System.out.println(TAG+"image has been sent ...");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static int ChangeByte2Int(byte[] bytes) {
        int result = 0;
        int[] a = {1000, 100, 10, 1};
        for (int i = 0, j = 0; i < bytes.length; i++, j++) {
            int k = (int) bytes[i];
            result += k * a[j];
        }
        return result;
    }

    public static byte[] ChangeInt2Byte(int len) {
        byte[] bytes = new byte[5];
        byte[] bytesReturn = new byte[5];
        int result = 0;
        int i;
        int j = 1;
        int z = 4;
        int k = 1;
        for (i = 4; i >= 0; i--) {
            result = len % 10;
            bytes[i] = (byte) result;
            len /= 10;
        }

        for (; j < bytes.length; j++) {
            bytesReturn[k] = bytes[j];
            k++;
        }
        return bytesReturn;
    }

    public static void PrintBytes(byte[] bytes) {
        int len = bytes.length;
        for (byte b : bytes) {
            System.out.print(b + " ");
        }
    }

}
// main.java 
import java.net.Socket;
import java.util.Timer;

public class Main {

    public static void main(String[] args) {
        beginTest();
    }

    private static void beginTest() {
        Thread server = new Thread(new Runnable() {
            @Override
            public void run() {
                SocketTest.ServerSocket();
            }
        });
        Thread client = new Thread(new Runnable() {
            @Override
            public void run() {
                SocketTest.ClientSocket();
            }
        });
        server.start();

        // 確保服務(wù)端先啟動
        try {
            server.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        client.start();
    }
}

運(yùn)行上述代碼后,結(jié)果如下:

運(yùn)行結(jié)果

注意,需要自己準(zhǔn)備圖片!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 網(wǎng)絡(luò)概念第一天 兩臺電腦怎么通過網(wǎng)絡(luò)傳輸數(shù)據(jù)?怎樣才能知道傳輸?shù)氖菙?shù)據(jù)?誰摸過網(wǎng)線? 看電影,怎么看的?通過電流,...
    小吖朱閱讀 1,650評論 0 1
  • 簡介 用簡單的話來定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    保川閱讀 6,086評論 1 13
  • 有時我們需要懂的應(yīng)該是自己所愛的人。# Z的這幅圖讓我看了很久,我一直在試圖揣測他畫圖時的所思所想。整幅圖畫的非常...
    周米啊閱讀 563評論 0 1
  • asdfasdfsdaf sdf sdf sdf sadf sadfsad dfasdf dsf sdf sdf
    venuss閱讀 558評論 0 50
  • 1 正則表達(dá)式 2 元字符介紹 3 BRE 和 ERE 1 BRE(基礎(chǔ)正則表達(dá)式) 2 ERE (擴(kuò)展正則表達(dá)式...
    喜歡蘿莉的逗逼青年閱讀 720評論 0 50

友情鏈接更多精彩內(nèi)容