Socket编程一实现简易的聊天功能以及文件传输

Socket编程一实现简易的聊天功能以及文件传输

标签: androidsocket通讯协议聊天socket传输文件
174人阅读 评论(6) 收藏 举报

干程序是一件枯燥重复的事,每当感到内心浮躁的时候,我就会找小说来看。我从小就喜爱看武侠小说,一直有着武侠梦。从金庸,古龙,梁羽生系列到凤歌(昆仑),孙晓(英雄志)以及萧鼎的(诛仙)让我领略着不一样的江湖。

如果你有好看的武侠系列小说,给我留言哦。题外话就扯这么多了,接着还是上技术。

看看今天实现的功能效果图:

soc.gif

可以这里使用多台手机进行通讯,【凤歌】我采用的服务器发送消息。

是不是只有发送消息,有些显得太单调了。好,在发送消息的基础上增加文件传输。后期会增加视频,音频的传输,增加表情包。那一起来看看图文消息的效果图,带领大家一起来实现通讯的简易聊天功能。

soc.gif

需要解决的难点:

  • 如何判断socket接收的数据是字符串还是流?

如果你已是一名老司机,还请留言给出宝贵意见。带着这个疑问我们接着往下看。

Socket概述

Socket我们称之为”套接字”,用于消息通知系统(如:激光推送),时事通讯系统(如:环信)等等。用于描述IP地址和端口,是一个通信链的句柄。网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket,一个Socket由一个IP地址和一个端口号唯一确定(如:ServerSocket)。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。SocketTCP/IP协议的一个十分流行的编程界面,但是,Socket所支持的协议种类也不光TCP/IP一种,因此两者之间是没有必然联系的。在Java环境下,Socket编程主要是指基于TCP/IP协议的网络编程。

java.NET包下有两个类:SocketServerSocket,基于TCP协议。

本文针对SocketServerSocket作主要讲解。

socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket

客户端Socket

Socket的构造方法:

Socket(InetAddress address,int port); //创建一个流套接字并将其连接到指定 IP 地址的指定端口号
Socket(String host,int port); //创建一个流套接字并将其连接到指定主机上的指定端口号
Socket(InetAddress address,int port, InetAddress localAddr,int localPort); //创建一个套接字并将其连接到指定远程地址上的指定远程端口
Socket(String host,int port, InetAddress localAddr,int localPort); //创建一个套接字并将其连接到指定远程主机上的指定远程端口
Socket(SocketImpl impl); //使用用户指定的 SocketImpl 创建一个未连接 Socket

参数含义:

  • address 双向连接中另一方的IP地址

  • port 端口号

  • localPort 本地主机端口号

  • localAddr 本地机器地址

  • impl 是socket的父类,既可以用来创建serverSocket又可以用来创建Socket

注意:我们在选取端口号的时候需要特别注意,每一个端口提供一种特定的服务,只有给出正确的端口,才能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telnet服务的端口号为21,ftp服务的端口号为23。本文选取的端口号为30003

Socket的几个重要方法:

public InputStream getInputStream(); //方法获得网络连接输入,同时返回一个IutputStream对象实例
public OutputStream getOutputStream(); //方法连接的另一端将得到输入,同时返回一个OutputStream对象实例
public Socket accept(); //用于产生"阻塞",直到接受到一个连接,并且返回一个客户端的Socket对象实例。

注意对流异常的处理。

服务端ServerSocket

ServerSocket的构造方法:

ServerSocket(int port); //创建绑定到特定端口的服务器套接字
ServerSocket(int port,int backlog); //利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号
ServerSocket(int port,int backlog, InetAddress bindAddr); //使用指定的端口、侦听 backlog 和要绑定到的本地 IP地址创建服务器

消息与文件的接收和发送

基础常量

Constants常量类:

    //连接ip地址 可以通过以下代码查看当前 IP 地址,记住在网络适配里关掉其他的连接,只保留当前连接
    //注意关掉防火墙,关闭杀毒软件
    /**
     * InetAddress ia = null;
     * try {
     * ia = ia.getLocalHost();
     * <p>
     * String localname = ia.getHostName();
     * String localip = ia.getHostAddress();
     * System.out.println("本机名称是:" + localname);
     * System.out.println("本机的ip是 :" + localip);
     * } catch (Exception e) {
     * // TODO Auto-generated catch block
     * e.printStackTrace();
     * }
     */
    public static final String HOST = "192.168.199.164";
    //端口号  避免端口冲突 我这里取30003
    public static final int PORT = 30003;

    //收到消息
    public static final int RECEIVE_MSG = 0;

    //发送消息
    public static final int SEND_MSG = 1;

    //发送文件
    public static final int SEND_FILE = 2;

    //传输文件
    public static final int TRANSFER_FILE = 3;

    //传输字符串
    public static final int TRANSFER_STR = 4;

    //聊天列表 发送消息
    public static final int CHAT_SEND = 1;

    //聊天列表 接收消息
    public static final int CHAT_FROM = 2;

    //更新进度
    public static final int PROGRESS = 5;

注意:关闭多余的网络适配,关闭防火墙。

定义协议

为了保证接收到的数据类型统一(数据是字符串还是流),需要定义协议。定义协议的方式有很多种:

  • 发送一个握手信号。 根据握手信号来确定发送的是字符串还是流

  • 定义了Header(头)和Body(实体),头是固定大小的,用来告诉接收者数据的格式、用途、长度等信息,接收者根据Header来接受Body。

  • 自定义协议

我这里采用的自定义协议,原理跟前面两种类似。我传输的是JSON数据,根据字段标识传输的是字符串还是流,接收者根据标识去解析数据即可。

协议的实体类(Transmission):

    //文件名称
    public String fileName;

    //文件长度
    public long fileLength;

    //传输类型
    public int transmissionType;

    //传输内容
    public String content;

    //传输的长度
    public long transLength;

    //发送还是接受类型    1发送  2接收
    public int itemType = 1;

    //0 文本  1  图片
    public int showType;

根据字段transmissionType去标识传输(序列化)或接收(反序列化)的类型。传输的过程中始终都是以JSON的格式存在的。传输文件时需要把流转换成字符串(方式很多种我用的是Base64加密与解密)。

客户端(ClientThread)
public class ClientThread extends Thread {

    PrintWriter mPrintWriter;
    BufferedReader mBufferedReader;
    Socket mSocket;

    Handler mSendHandler;
    Handler mWriteHandler;

    Gson mGson;

    public ClientThread(Handler handler) {
        mSendHandler = handler;
        mGson = new Gson();
    }

    @Override
    public void run() {
        super.run();

        try {
            //创建socket
            mSocket = new Socket(Constants.HOST, Constants.PORT);
            //获取到读写对象
            mPrintWriter = new PrintWriter(mSocket.getOutputStream());
            mBufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));

            //新开线程读取消息 并发送消息
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    String content = null;
                    try {
                        while ((content = mBufferedReader.readLine()) != null) {
                            Transmission trans = mGson.fromJson(content, Transmission.class);
                            if (trans.transmissionType == Constants.TRANSFER_STR) {
                                Message msg = new Message();
                                msg.what = Constants.RECEIVE_MSG;
                                msg.obj = content;
                                mSendHandler.sendMessage(msg);
                            }
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }.start();

            //当前线程创建 handler
            Looper.prepare();
            mWriteHandler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    if (msg.what == Constants.SEND_MSG) {
                        mPrintWriter.write(msg.obj.toString() + "\r\n");
                        mPrintWriter.flush();
                    } else if (msg.what == Constants.SEND_FILE) {//传输文件
                        //定义标记判定是字符串还是文件
                        sendFile(msg.obj.toString());
                    }
                }
            };
            Looper.loop();

        } catch (IOException e) {
            e.printStackTrace();
            //出现异常关闭资源
            try {
                if (mPrintWriter != null) {
                    mPrintWriter.close();
                }
                if (mBufferedReader != null) {
                    mBufferedReader.close();
                }
                if (mSocket != null) {
                    mSocket.close();
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }

    }

    /**
     * 文件路径
     *
     * @param filePath
     */
    private void sendFile(String filePath) {
        FileInputStream fis = null;
        File file = new File(filePath);

        try {
            mSendHandler.sendEmptyMessage(Constants.PROGRESS);

            fis = new FileInputStream(file);

            Transmission trans = new Transmission();
            trans.transmissionType = Constants.TRANSFER_FILE;
            trans.fileName = file.getName();
            trans.fileLength = file.length();
            trans.transLength = 0;

            byte[] bytes = new byte[1024];
            int length = 0;
            while ((length = fis.read(bytes, 0, bytes.length)) != -1) {
                trans.transLength += length;
                trans.content = Base64Utils.encode(bytes);
                mPrintWriter.write(mGson.toJson(trans) + "\r\n");
                mPrintWriter.flush();

                Message message = new Message();
                message.what = Constants.PROGRESS;
                message.obj = 100 * trans.transLength / trans.fileLength;
                mSendHandler.sendMessage(message);
            }
            fis.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            mPrintWriter.close();
        }
    }

MainActivity中开启客户端线程:

    mClientThread = new ClientThread(mHandler);
    mClientThread.start();
服务端(MyServer)
public class MyServer {

    //多客户端
    public static ArrayList<Socket> sSockets = new ArrayList<Socket>();

    public static void main(String[] args) {
        //DatagramSocket 基于UDP协议的
        ServerSocket serverSocket = null;

        try {
            //创建服务器的socket对象
            serverSocket = new ServerSocket(Constants.PORT);

            while (true) {
                Socket socket = serverSocket.accept();
                sSockets.add(socket);
                //开启线程
                new Thread(new ServerThread(socket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
   
   
  • 25

ServerThread 类:

public class ServerThread implements Runnable {

    Socket mSocket;
    BufferedReader mBufferedReader;
    Gson mGson;

    boolean mCreateFile = true;

    public ServerThread(Socket socket) throws IOException {

        mGson = new Gson();
        mSocket = socket;
        mBufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream(), "utf-8"));
        //新开线程给客服端发送消息
        new Thread() {
            @Override
            public void run() {
                super.run();
                sendMessage();
            }
        }.start();
    }

    @Override
    public void run() {
        readMessage();
    }

    //读取的数据发送给客户端
    private void readMessage() {
        String content = null;
        FileOutputStream fos = null;
        try {
            while ((content = mBufferedReader.readLine()) != null) {
                Transmission trans = mGson.fromJson(content, Transmission.class);
                if (trans.transmissionType == Constants.TRANSFER_STR) {
                    System.out.println("" + content);
                    for (Iterator<Socket> it = MyServer.sSockets.iterator();
                         it.hasNext(); ) {
                        if (it == null) {
                            break;
                        }
                        Socket s = it.next();
                        try {

                            PrintWriter printWriter = new PrintWriter(s.getOutputStream());
                            printWriter.write(content + "\r\n");
                            printWriter.flush();

                        } catch (SocketException e) {
                            e.printStackTrace();
                            it.remove();
                        }
                    }
                } else {
                    long fileLength = trans.fileLength;
                    long transLength = trans.transLength;
                    if (mCreateFile) {
                        mCreateFile = false;
                        fos = new FileOutputStream(new File("d:/" + trans.fileName));
                    }
                    byte[] b = Base64Utils.decode(trans.content.getBytes());
                    fos.write(b, 0, b.length);
                    System.out.println("接收文件进度" + 100 * transLength / fileLength + "%...");
                    if (transLength == fileLength) {
                        mCreateFile = true;

                        fos.flush();
                        fos.close();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            try {
                if (fos != null) {
                    fos.close();
                }
                if (mBufferedReader != null) {
                    mBufferedReader.close();
                }
                if (mSocket != null) {
                    mSocket.close();
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
            MyServer.sSockets.remove(mSocket);
        }
    }

    //发送消息给连接的客服端
    private void sendMessage() {
        BufferedReader bufferedReader = null;
        try {
            while (true) {
                bufferedReader = new BufferedReader(new InputStreamReader(System.in));
                System.out.print("请输入发送的字符串:");
                String str = bufferedReader.readLine();
                for (Iterator<Socket> it = MyServer.sSockets.iterator();
                     it.hasNext(); ) {
                    if (it == null) {
                        break;
                    }
                    Socket s = it.next();
                    try {
                        Transmission trans = new Transmission();
                        trans.itemType = Constants.CHAT_FROM;
                        trans.transmissionType = Constants.TRANSFER_STR;
                        trans.content = str;

                        PrintWriter printWriter = new PrintWriter(s.getOutputStream());
                        printWriter.write(mGson.toJson(trans) + "\r\n");
                        printWriter.flush();

                    } catch (SocketException e) {
                        e.printStackTrace();
                        s.close();
                        it.remove();
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
                MyServer.sSockets.remove(mSocket);
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }
}

首先运行MyServermain函数,把服务启动起来,然后运行MainActivity把客户端启动起来。这样就实现了两端通讯。

源码地址


 
 
我的同类文章
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值