Socket编程一实现简易的聊天功能以及文件传输
来源:互联网 发布:超级数据恢复软件破解 编辑:程序博客网 时间:2024/05/22 14:43
Socket编程一实现简易的聊天功能以及文件传输
干程序是一件枯燥重复的事,每当感到内心浮躁的时候,我就会找小说来看。我从小就喜爱看武侠小说,一直有着武侠梦。从金庸,古龙,梁羽生系列到凤歌(昆仑),孙晓(英雄志)以及萧鼎的(诛仙)让我领略着不一样的江湖。
如果你有好看的武侠系列小说,给我留言哦。题外话就扯这么多了,接着还是上技术。
看看今天实现的功能效果图:
可以这里使用多台手机进行通讯,【凤歌】我采用的服务器发送消息。
是不是只有发送消息,有些显得太单调了。好,在发送消息的基础上增加文件传输。后期会增加视频,音频的传输,增加表情包。那一起来看看图文消息的效果图,带领大家一起来实现通讯的简易聊天功能。
需要解决的难点:
- 如何判断
socket
接收的数据是字符串还是流?
如果你已是一名老司机,还请留言给出宝贵意见。带着这个疑问我们接着往下看。
Socket概述
Socket
我们称之为”套接字”,用于消息通知系统(如:激光推送),时事通讯系统(如:环信)等等。用于描述IP地址
和端口,是一个通信链的句柄。网络上的两个程序通过一个双向的通讯连接实现数据的交换,这个双向链路的一端称为一个Socket
,一个Socket
由一个IP地址
和一个端口号唯一确定(如:ServerSocket)。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。Socket
是TCP/IP
协议的一个十分流行的编程界面,但是,Socket
所支持的协议种类也不光TCP/IP
一种,因此两者之间是没有必然联系的。在Java环境下,Socket
编程主要是指基于TCP/IP
协议的网络编程。
java.NET
包下有两个类:Socket
和ServerSocket
,基于TCP
协议。
本文针对Socket
和ServerSocket
作主要讲解。
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(); } } }}
首先运行MyServer
的main
函数,把服务启动起来,然后运行MainActivity
把客户端启动起来。这样就实现了两端通讯。
源码地址
- 上一篇初谈一Dagger2
我的同类文章
- •初谈一Dagger22017-02-12
- •初谈一Java Annotation2017-02-04
- •自定义View之案列篇(四):颜色选择器2016-11-22
- •Android 6.0 运行时权限封装之路2016-11-01
- •自定义View之案列篇(一):魔方2016-10-18
- •EventBus使用大全2017-02-07
- •Android APK 更新之路2016-12-03
- •自定义View之案列篇(三):仿QQ小红点2016-11-08
- •自定义View之案列篇(二):扇形菜单2016-10-24
- •Android Studio JNI 开发简单案例2016-09-29
- Socket编程一实现简易的聊天功能以及文件传输
- Socket编程一实现简易的聊天功能以及文件传输
- socket文件传输功能的实现
- socket文件传输功能的实现
- Java:基于socket的聊天实现+文件传输
- 网络编程 基于Socket的多文件传输程序实现(一)
- socket实现文件传输功能
- socket 编程 TCP 实现简单聊天功能
- 17、socket文件传输功能的实现
- (一)Android socket+多线程 实现聊天功能
- 聊天中“@”人功能的简易实现
- UDP实现的简易双向聊天功能
- JAVA通过Swing和socket编程实现简易点对点聊天
- 简易的聊天界面以及聊天机器人的实现
- 基于smack的即时聊天系统之文件传输功能实现
- Socket实现聊天功能
- Socket 实现聊天功能
- iOS微信QQ聊天界面的UI框架以及Socket简单实现群聊功能
- Windows Openmp 设置线程亲缘性
- JAX-WS HandlerChain使用详解
- 2668: [cqoi2012]交换棋子
- 欧里几德及扩展欧里几德算法
- Ubuntu14.04安装cuda7.5出现login loop error解决办法
- Socket编程一实现简易的聊天功能以及文件传输
- (转)招商银行这一招,引起了各大银行的注意
- [CODEVS1907]方格取数3(最小割)
- hive启动之异常
- struts2相关的六个配置文件
- MJPG-Streamer源码分析(一)
- 通过remmina在linux和windows之间传输文件
- Kinect ROS Gmapping 2D地图
- TCP传输模式中,调用Socket的shutdownInput()和shutdownOutput()都做了些什么?