寫在前面
還是畢業(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é)議來完成自己的需求。(來自百度百科)

通俗而言: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)閉該文件。

具體實(shí)現(xiàn)
有了上面的原理,我們可以著手來實(shí)現(xiàn)以下了。以下出現(xiàn)的代碼,只是為了演示,真實(shí)開發(fā)環(huán)境下不要這樣寫。
Java提供了socket的相關(guān)API,我們不需要知道怎么寫socket的底層,通過調(diào)用API就可以建立連接了。在建立連接之前,我們需要自己定義一個用于發(fā)送圖片的簡單的通信協(xié)議。我自己隨便定義了一個,如下圖中間部分所示。

通信協(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é)果如下:

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