[毕设记录] socket自定义数据包发送图片

写在前面

还是毕业设计的事儿,还是发送图片的事儿。前面已经用了python实现图片的发送,但是很蛋疼的是,上位机用的是vb.net来进行开发,python的在图片发送和接收又变得不好使了。实验了若干种方法,下面记录一下。

目标:在vb.net的环境下和Android之间使用socket进行图片的传输。

实现:自定义一个简单的传输协议,将图片用字节流的方式发送和接收。

环境:在vb.net和Android上已经试验成功,但是本文用java进行演示(原因是:1.懒得连接Android了;2.实在很讨厌vb的语言....)。逻辑是一样的,只是具体的语法会有些少出入。

原理介绍

这次,在具体的实现之前,先扒一下原理。网络传输,使用的时socket,这是一个建立在tcp协议上的通信机制。所以,有必要了解一下什么是tcp协议,什么是socket。

TCP / IP 协议

Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。TCP/IP 定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,如下图所示,每一层都呼叫它的下一层所提供的协议来完成自己的需求。(来自百度百科)

tcp ip协议层级示意图

通俗而言:IP协议是给因特网的每一台联网设备规定一个地址,以便能够在因特上上定位到该设备。TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。

socket

我们知道,程序在设备当中运行时是以进程的形式被操作系统管理的。在本地,两个进程之间要通信,首先必须知道的是它们的PID。在网络上,依靠PID来识别不同的进程,显然是不科学的,因为它们会重复。从上面,我们了解到每个连接到因特网的设备的ip都是唯一的。所以,我们可以借助ip再加上一个端口号,来进行进程的唯一识别。当网络进程可以唯一识别之后,就可以使用socket进行通信了。

socket,翻译成中文是“套接字”。它是应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信,如下图所示。socket源起于UNIX,在一切皆是文件的哲学思想下,socket是“打开-读写-关闭”模式的实现,服务端和客户端共同维护一个“文件”,在连接建立之后,双方都可以对文件进行读写操作;通讯结束时,关闭该文件。

socket 示意图

具体实现

有了上面的原理,我们可以着手来实现以下了。以下出现的代码,只是为了演示,真实开发环境下不要这样写。

Java提供了socket的相关API,我们不需要知道怎么写socket的底层,通过调用API就可以建立连接了。在建立连接之前,我们需要自己定义一个用于发送图片的简单的通信协议。我自己随便定义了一个,如下图中间部分所示。

自定义socket通信

通信协议,顾名思义,是用来规定通信过程的一种约定。只有服务端和客户端都遵循同一个协议,才可以完成有效的通信。就想上图表示的一样,我规定了如下的内容:

  • 每次发送的数据包长度为1029个字节(数据包是一个有符号的字节数组,下标从0开始到1028结束)
  • 第一个字节是标志位,该位为1时,表示后面还有数据;该位为2时,表示这是最后一包数据
  • 数据包的[1 : 4]表示该数据包的有效数据长度,因为数据包最大可以存放1024字节的数据,所以[1 : 4]每一位分别表示“个十百千”位上的数字。除去最后一包数据,其他包都应该是放满的,最后一包的数据则很大可能都不满1024(配合流的read方法可以避免接收冗余数据)。

有了上面的约定,我们就可以写代码了:

// SocketTest.java 下面代码的运行环境是Mac intellij14
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

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

    // 服务端, 用来接收图片
    public static void ServerSocket() {

        // 调试标志
        String TAG = "ServerSocket: ";
        // 服务端socket
        ServerSocket serverSocket = null;
        // 端口号
        int port = 9998;
        // 缓冲区
        byte[] buffer = new byte[1029];
        int len = buffer.length;
        // 实际长度
        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();
            // 读取数据
            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);
                        // 读取数据
                        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() {

        // 调试标志
        String TAG = "ClientSocket: ";
        // 客户端socket
        Socket clientSocket = null;
        // 服务器地址
        String ip = "127.0.0.1";
        // 端口号
        int port = 9998;
        // 缓冲区
        byte[] buffer = new byte[1029];
        int len = buffer.length;
        byte[] fullData = {0, 1, 0, 2, 4};
        // 实际长度
        int realLen = 0;
        // 文件末尾标志
        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);
            // 获取文件输入流(读取要发送的图片)
            fileInputStream = new FileInputStream(file);
            // 获取socket输出流(发送数据包)
            clientOutputStream = clientSocket.getOutputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }

        if (clientOutputStream != null) {
            try {
                System.out.println(TAG+"begin to send the image ...");
                while (cf != -1) {
                    // 读取数据放到数据区域
                    cf = fileInputStream.read(buffer, 5, len - 5);
                    if (cf == -1) {
                        // 将长度转为byte[]
                        byte[] by = ChangeInt2Byte(realLen);
                        // 将长度数据整合到数据包里面
                        System.arraycopy(by, 0, buffer, 0, by.length);
                        // 标志位置2
                        buffer[0] = 2;
                        // 发送数据
                        clientOutputStream.write(buffer, 0, realLen + 5);
                    }else {
                        // 缓存实际的数据长度
                        realLen = cf;
                        System.arraycopy(fullData, 0, buffer, 0, fullData.length);
                        // 标志位置1
                        buffer[0] = 1;
                        // 发送数据
                        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();

        // 确保服务端先启动
        try {
            server.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        client.start();
    }
}

运行上述代码后,结果如下:

运行结果

注意,需要自己准备图片!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容

  • 网络概念第一天 两台电脑怎么通过网络传输数据?怎样才能知道传输的是数据?谁摸过网线? 看电影,怎么看的?通过电流,...
    小吖朱阅读 1,517评论 0 1
  • 简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者...
    保川阅读 5,933评论 1 13
  • 有时我们需要懂的应该是自己所爱的人。# Z的这幅图让我看了很久,我一直在试图揣测他画图时的所思所想。整幅图画的非常...
    周米啊阅读 360评论 0 1
  • asdfasdfsdaf sdf sdf sdf sadf sadfsad dfasdf dsf sdf sdf
    venuss阅读 530评论 0 50
  • 1 正则表达式 2 元字符介绍 3 BRE 和 ERE 1 BRE(基础正则表达式) 2 ERE (扩展正则表达式...
    喜欢萝莉的逗逼青年阅读 675评论 0 50