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

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

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

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

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

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

需要解决的难点:

  • 如何判断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

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。步骤如下:

  • 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求
  • 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
  • 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

JDK Socket

java.net包下有两个类:SocketServerSocketServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。接着了解下SocketServerSocket的构造方法。

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地址创建服务器

接着我们一起来看看案例。

发送和接收消息

首先来实现一个简单的案例,服务器端一直监听某个端口,等待客户端连接请求。客户端根据IP地址和端口号连接服务器端,接着客服端通过控制台向服务端发送消息,服务端接收到消息并且展示出来。下面来看看具体实现的步骤:

ClientSocket客服端:

        try {
            Socket socket = new Socket("173.1.1.121", 30004);

            //获取控制台输入的内容
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
            System.out.print("请输入发送的字符串:");
            String str = bufferedReader.readLine();

            //给服务端发送消息
            PrintWriter printWriter = new PrintWriter(socket.getOutputStream());
            printWriter.write(str + "\r\n");
            printWriter.flush();

            //关闭资源
            bufferedReader.close();
            printWriter.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

Socket两个参数分别是IP地址端口号,可以通过以下代码获取IP地址

     InetAddress ia = null;
     try {
         ia = ia.getLocalHost();
         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();
     }

注意:关闭多余的网络适配,只保留当前的网络连接。关闭防火墙,安全软件。不然可能导致连接不上。

ServerSocket服务端:

        try {
            mServerSocket = new java.net.ServerSocket(30004);

            Socket socket = mServerSocket.accept();

            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

            String content = null;
            while ((content=bufferedReader.readLine() )!= null) {
                System.out.println("接收到客服端发来的消息:" +content);
            }

            //关闭连接
            bufferedReader.close();
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

服务端和客服端的端口号必须保持一致。下面我们运行两个 java 程序看看效果:

可能有些童鞋还不知道,怎么在AndroidStudio下面运行java程序,请看下面截图:

传输文件

Socket只能通过流去读取消息,传输文件需要解决文章开始提出的问题, 如何判断socket接收的数据是字符串还是流?

定义协议

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

  • 发送一个握手信号。 根据握手信号来确定发送的是字符串还是流
  • 定义了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)

客户端发送文件的代码如下:

    /**
     * 文件路径
     *
     * @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();
        }
    }

文章结尾处我会附上源码。

        trans.content = Base64Utils.encode(bytes);
        mPrintWriter.write(mGson.toJson(trans) + "\r\n");
        mPrintWriter.flush();

把字节流转换成字符串传输Base64Utils.encode(bytes),接收方把字符串解析成字节流并写入文件。

注意:在Android程序中运行,记得添加网络文件读写的权限。

服务端(ServerThread)

服务端接收文件的代码如下:

       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();
       }

服务端接收到文件,并存储到了d盘。注意文件传输结束后关闭流。

源码地址

下载源码后,请先替换Constants类中HOST地址,然后运行MyServerjava程序,最后运行MainActivityAndroid程序。

参考文章:

聊聊Socket、TCP/IP、HTTP、FTP及网络编程

MultiType-FilePicker

Android 6.0 运行时权限封装之路

时间: 2024-08-05 11:10:54

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

Socket编程(在控制台模拟聊天功能)

目录 TCP简单示例 TCP模拟聊天 UDP简单示例 UDP模拟聊天 @ 服务器端 (1) 创建ServerSocket对象,绑定监听端口: (2) 通过accept()方法监听客户端请求: (3) 连接建立后,通过输入流读取客户端发送的请求信息: (4) 通过输出流向客户端发送相应信息: (5) 关闭响应资源. 客户端 (1) 创建Socket对象,指明需要连接的服务器地址和端口: (2) 连接建立后,通过输出流向服务器端发送请求信息: (3) 通过输入流获取服务器端返回的响应信息: (4)

Linux功能-远程文件传输

linux系统中,难免会遇到一些要将某文件通过网络传送给其他主机的情况,而恰好两台主机 都是linux系统的时候,我们就可以直接使用scp命令来传输文件到另一台主机了. scp命令用于在网络中安全的传输文件,格式为: scp [参数] 本地文件 远程账户@远程IP地址:远程目录

使用libevent进行多线程socket编程demo

最近要对一个用libevent写的C/C++项目进行修改,要改成多线程的,故做了一些学习和研究. libevent是一个用C语言写的开源的一个库.它对socket编程里的epoll/select等功能进行了封装,并且使用了一些设计模式(比如反应堆模式),用事件机制来简化了socket编程.libevent的好处网上有很多,但是初学者往往都看不懂.我打个比方吧,1)假设有N个客户端同时往服务端通过socket写数据,用了libevent之后,你的server程序里就不用再使用epoll或是sele

IP地址的三种表示格式及在Socket编程中的应用

转自:http://blog.csdn.net/hguisu/article/details/7449955 使用TCP/IP协议进行网络应用开发的朋友首先要面对的就是对IP地址信息的处理.IP地址其实有三种不同的表示格式:  1)Ascii(网络点分字符串)-        2) 网络地址(32位无符号整形,网络字节序,大头)        3)主机地址 (主机字节序)   IP地址是IP网络中数据传输的依据,它标识了IP网络中的一个连接,一台主机可以有多个IP地址,IP分组中的IP地址在网络

非阻塞socket实现android手机与PC的文件传输

项目需要是通过WIFI建立手机和PC的通信,然后自定义一个简单的协议对要传输的文件进行校验,传输的文件是2张3M的图片,要求考虑网络中断情况处理. 我这里采用的是非阻塞socket来实现的,之前查过很多资料,觉得这种比较方便,其实用传统的那种socket也是可以实现的,至于阻塞问题,可以开两个线程,这样保证读取不是同一个线程,也就可以解决. 程序大致是这样的流程,手机端发送一个"filename"字符串给PC,PC校验字符串后返回文件名,然后手机端再把接收到的文件名发送给PC端,进行校

Socket编程(简易聊天室客户端/服务器编写、CocoaAsyncSocket)

Socket编程(简易聊天室客户端/服务器编写.CocoaAsyncSocket) 一.Socket 1.1 Socket简介 Socket就是为网络服务提供的一种机制.网络通信其实就是Socket间的通信,通信的两端都是Socket,数据在两个Socket间通过IO传输. 在Web服务大行其道的今天,调用Web服务的代价是高昂的,尤其是仅仅是抓取少量数据的时候尤其如此.而使用Socket,可以只传送数据本身而不用进行XML封装,大大降低数据传输的开销.Socket允许使用长连接,允许应用程序运

Python Socket 编程——聊天室演示样例程序

上一篇 我们学习了简单的 Python TCP Socket 编程,通过分别写服务端和client的代码了解主要的 Python Socket 编程模型.本文再通过一个样例来加强一下对 Socket 编程的理解. 聊天室程序需求 我们要实现的是简单的聊天室的样例,就是同意多个人同一时候一起聊天.每一个人发送的消息全部人都能接收到,类似于 QQ 群的功能,而不是点对点的 QQ 好友之间的聊天.例如以下图: 图来自:http://www.ibm.com/developerworks/linux/tu

vue + socket.io实现一个简易聊天室

vue + vuex + elementUi + socket.io实现一个简易的在线聊天室,提高自己在对vue系列在项目中应用的深度.因为学会一个库或者框架容易,但要结合项目使用一个库或框架就不是那么容易了.功能虽然不多,但还是有收获.设计和实现思路较为拙劣,恳请各位大大指正. 可以达到的需求 能查看在线用户列表 能发送和接受消息 使用到的框架和库 socket.io做为实时通讯基础 vuex/vue:客户端Ui层使用 Element-ui:客户端Ui组件 类文件关系图 服务端: 客户端: 服

Java Socket编程实现聊天小案例

 一.用户需求: 1.实现使用用户名登陆(不能重复) 2.登陆后能获取所有在线用户 3.可以给所用用户群发信息 4.也可以给某个用户单独发送信息 5.用户退出时,一起登陆用户能立刻感知 二.初步分析: 1.需要在服务器端记录当前登陆的用户,便于用户登陆时用户名查重及消息群发 2.用户登陆成功后需要获取所有在线用户信息,并记录,能够单独给该用户发送信息 3.用户退出时,需要通知服务器从服务器数据中删除该用户并通知到所有用户 三.效果图: 图1.服务器启动Socket监听 图2 启动客户端登录