JAVA使用Socket实现多人通讯以及文件传输

来源:互联网 发布:淘宝卖家不参加双十一 编辑:程序博客网 时间:2024/05/21 17:04

这篇文章将使用java的socket通讯完成一个多人聊天以及文件传输的例子。这里例子中聊天和文件传输可以同时进行。

当然两者都是基于TCP协议的,因为无论是聊天还是文件传输要保证数据的完整性,如果是使用UDP,数据传输将会不可靠。

至于两者的区别也就不多说了,不懂得稍微百度一下咯。


基于socket通讯的知识在大学的时候学过了,那时是使用c/c++语言完成的,实现起来要比java稍微复杂一点。那时候聊天和,传输文件可没有这么简单,我记得写聊天系统的时候,我们还要自定义协议!现在java帮我们封装了很多方法,使用起来就方便多了。话不多说,下面准备开始丢代码:


socket通讯,我们写例子一般是客户端和服务器进行通讯,那在java中,就有两个类需要注意

1.ServerSocket : 顾名思义,这就是用来建立服务器端套接字的,除了构造方法,它主要的方法有

bind():用来绑定一个ip地址,如果不调用这个方法,则默认是本机ip

accept():监听客户端的链接,这方法一直阻塞,直到有客户端链接,然后得到该客户端的套接字

close():关闭套接字

2.socket :套接字,这个客户端发起链接需要的。获取到套接字就能进去通讯了,它除了构造方法之外主要的方法:

connect():通常用于客户端发起TCP连接。

getInputStream():获取输入流,这个可重要了,就是获取对方给你传输的数据流

getOutputStream():获取输出流,一样重压力,用于给对方传输数据

close():关闭套接字


这都是常用的几个方法,其他方法大家自己去了解吧。


多人聊天

首先我们需要先写个服务器,代码如下:

package server;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.net.ServerSocket;import java.net.Socket;public class SocketServer {public static void main(String[] args) {initServer();}private static void initServer() {ServerSocket ss = null;try {ss = new ServerSocket(5001);System.out.println("服务器正在运行...");for (;;) {Socket client = ss.accept();System.out.println("新客户端链接:" + client.getPort());new SocketThread(client).onStart();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {try {ss.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}static class SocketThread extends Thread {private Socket socket;private boolean flag = true;public SocketThread(Socket socket) {this.socket = socket;}public void onStart() {this.start();}@Overridepublic void run() {System.out.println("链接成功!");BufferedReader reader = null;BufferedWriter writer = null;try {InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));while (flag) {String content = reader.readLine();if (content == null || content.equals("bye")) {flag = false;System.out.println(socket.getPort() + "已经断开链接");} else {System.out.println("收到消息:" + content);/*writer.write("我收到了您的消息! 在" + System.currentTimeMillis());writer.flush();*/}}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();System.out.println(socket.getPort() + "已经断开链接");flag = false;} finally {try {reader.close();writer.close();socket.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}



这里的服务器没有实现多人转发的逻辑,如果要实现类似于QQ的群聊,每个人发的消息都能看到,就在收到消息的逻辑里面实现转发就行了。

这里我就不写了,大致逻辑就是,用一个map保存所有已经链接客户端,一旦有用户发消息到服务器,服务器就转发到所有已经链接的客户端。

为什么我用map,好处就是,一旦某个用户断开连接,我们可以根据key值把它删除掉。key可以是客户端的ip之类的。具体怎么转发,可以参考客户端的发送消息逻辑。

下面是客户端:

package client;import java.io.BufferedReader;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.Socket;import java.net.UnknownHostException;import java.util.Scanner;public class SocketClient {public static void main(String[] args) {initClient();}private static void initClient() {try {boolean flag = true;Socket socket = new Socket("192.168.1.105", 5001);InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));PrintWriter writer = new PrintWriter(new OutputStreamWriter(os,"UTF-8"), true);Scanner scanner = new Scanner(System.in);while (flag) {String content = scanner.next();System.out.println("client 等待用户输入:");writer.println(content + ":" + System.currentTimeMillis());}scanner.close();reader.close();writer.close();socket.close();} catch (UnknownHostException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}


客户端功能就是获取用户输入的内容,然后发送到服务器,然后你们发现我定义了一个

BufferedReader 没有用到,其实这个是用来获取服务器给客户端发送的内容。因为我在服务器没有写转发逻辑,所以也就没用到了。具体怎么获取服务器发送的消息可以参考服务器端的接收逻辑。


这样就实现了简单的多人聊天系统。服务器主要的功能是监听客户端链接,一旦有客户端链接就为其开启一个线程用来实现通讯。而客户端只需要发送消息就行了。



文件传输

我在开头说了,我这个例子是多人聊天和文件传输可以同时进行的。那怎么实现的呢?

当服务器接受到文件传输请求时,则开启新的文件传输端口,并进行监听。端口号可由客户端决定。

而客户端如果想接收文件,则发起一个文件传输请求,并把需要传输的文件名还有传输端口什么的发送给服务器,然后链接新建一个线程链接传输端口进行文件接收就行。

服务器发送文件线程:

/*** * 文件传输监听线程 */static class FileThread extends Thread {// 文件传输端口5002private ServerSocket fileSS;private int port;public FileThread(int port) {this.port = port;}@Overridepublic void run() {DataInputStream dis = null;Socket socket = null;try {fileSS = new ServerSocket(port);socket = fileSS.accept();System.out.println("客户端已经链接文件服务");// 需要传输的文件 这里是我指定的,可以让客户端指定File file = new File("F:\\line.zip");DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dis = new DataInputStream(new FileInputStream(file));// 传送文件名字dos.writeUTF(file.getName());dos.flush();// 传送长度dos.writeLong(file.length());dos.flush();System.out.println("开始传送文件...(大小:"+file.getTotalSpace()+")");// 传送文件int length = -1;// 读取到的文件长度byte[] buff = new byte[1024];// 循环读取文件,直到结束while ((length = dis.read(buff)) > 0) {dos.write(buff, 0, length);dos.flush();}System.out.println("传送文件完成");} catch (IOException e) {e.printStackTrace();} finally {try {// 关闭流if (dis != null) {dis.close();}//关闭客户端口if (socket != null) {socket.close();}// 关闭服务端口if (fileSS != null) {fileSS.close();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}


客户端接收文件的线程:

/*** * 文件传输监听线程 */static class FileThread extends Thread {private int port;public FileThread(int port) {this.port = port;}@Overridepublic void run() {Socket socket = null;DataOutputStream dos = null;try {socket = new Socket("192.168.1.105", port);System.out.println("客户端已经链接文件服务");// 需要传输的文件 这里是我指定的,可以让客户端指定DataInputStream dis = new DataInputStream(socket.getInputStream());//获取服务器传过来的文件名字File file = new File("G:\\"+dis.readUTF());//获取服务器传过来的文件大小double totleLength = dis.readLong();dos = new DataOutputStream(new FileOutputStream(file));//开始接收文件System.out.println("开始接收:"+totleLength);int length=-1;byte[] buff= new byte[1024];double curLength = 0;while((length=dis.read(buff))>0){dos.write(buff, 0, length);curLength+=length;//System.out.println("传输进度:"+(curLength/totleLength*100)+"%");System.out.println("传输进度:"+(curLength/totleLength*100)+"%");}dos.flush();System.out.println("接收文件完成");} catch (IOException e) {e.printStackTrace();} finally {try {// 关闭流if (dos != null) {dos.close();}//关闭客户端口if (socket != null) {socket.close();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}

具体什么时候开启文件传输线程就看你们的需求了,这里我给我完整的代码给大家参考草看:

服务器:

package server;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.net.ServerSocket;import java.net.Socket;public class SocketServer {public static void main(String[] args) {initServer();}private static void initServer() {ServerSocket ss = null;try {ss = new ServerSocket(5001);System.out.println("服务器正在运行...");for (;;) {Socket client = ss.accept();System.out.println("新客户端链接:" + client.getPort());new SocketThread(client).onStart();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {try {ss.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}static class SocketThread extends Thread {private Socket socket;private boolean flag = true;public SocketThread(Socket socket) {this.socket = socket;}public void onStart() {this.start();}@Overridepublic void run() {System.out.println("链接成功!");BufferedReader reader = null;BufferedWriter writer = null;try {InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));writer = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));while (flag) {String content = reader.readLine();if (content == null || content.equals("bye")) {flag = false;System.out.println(socket.getPort() + "已经断开链接");} else {System.out.println("收到消息:" + content);/*writer.write("我收到了您的消息! 在" + System.currentTimeMillis());writer.flush();*//*** * 文件传输处理 */if (content.contains("filerequest")) {int port = Integer.valueOf(content.split(":")[1]);new FileThread(port).start();System.out.println("文件传输端口已经启动");}/****/}}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();System.out.println(socket.getPort() + "已经断开链接");flag = false;} finally {try {reader.close();writer.close();socket.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}/*** * 文件传输监听线程 */static class FileThread extends Thread {// 文件传输端口5002private ServerSocket fileSS;private int port;public FileThread(int port) {this.port = port;}@Overridepublic void run() {DataInputStream dis = null;Socket socket = null;try {fileSS = new ServerSocket(port);socket = fileSS.accept();System.out.println("客户端已经链接文件服务");// 需要传输的文件 这里是我指定的,可以让客户端指定File file = new File("F:\\line.zip");DataOutputStream dos = new DataOutputStream(socket.getOutputStream());dis = new DataInputStream(new FileInputStream(file));// 传送文件名字dos.writeUTF(file.getName());dos.flush();// 传送长度dos.writeLong(file.length());dos.flush();System.out.println("开始传送文件...(大小:"+file.getTotalSpace()+")");// 传送文件int length = -1;// 读取到的文件长度byte[] buff = new byte[1024];// 循环读取文件,直到结束while ((length = dis.read(buff)) > 0) {dos.write(buff, 0, length);dos.flush();}System.out.println("传送文件完成");} catch (IOException e) {e.printStackTrace();} finally {try {// 关闭流if (dis != null) {dis.close();}//关闭客户端口if (socket != null) {socket.close();}// 关闭服务端口if (fileSS != null) {fileSS.close();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}}

客户端:

package client;import java.io.BufferedReader;import java.io.DataInputStream;import java.io.DataOutputStream;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.OutputStream;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.Socket;import java.net.UnknownHostException;import java.util.Scanner;public class SocketClient {public static void main(String[] args) {initClient();}private static void initClient() {try {boolean flag = true;Socket socket = new Socket("192.168.1.105", 5001);InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));PrintWriter writer = new PrintWriter(new OutputStreamWriter(os,"UTF-8"), true);Scanner scanner = new Scanner(System.in);while (flag) {String content = scanner.next();System.out.println("client 等待用户输入:");writer.println(content + ":" + System.currentTimeMillis());/** * 文件接收线程 */if (content.contains("filerequest")) {int port = Integer.valueOf(content.split(":")[1]);new FileThread(port).start();System.out.println("接收文件线程已经启动");}}scanner.close();reader.close();writer.close();socket.close();} catch (UnknownHostException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}/*** * 文件传输监听线程 */static class FileThread extends Thread {private int port;public FileThread(int port) {this.port = port;}@Overridepublic void run() {Socket socket = null;DataOutputStream dos = null;try {socket = new Socket("192.168.1.105", port);System.out.println("客户端已经链接文件服务");// 需要传输的文件 这里是我指定的,可以让客户端指定DataInputStream dis = new DataInputStream(socket.getInputStream());//获取服务器传过来的文件名字File file = new File("G:\\"+dis.readUTF());//获取服务器传过来的文件大小double totleLength = dis.readLong();dos = new DataOutputStream(new FileOutputStream(file));//开始接收文件System.out.println("开始接收:"+totleLength);int length=-1;byte[] buff= new byte[1024];double curLength = 0;while((length=dis.read(buff))>0){dos.write(buff, 0, length);curLength+=length;//System.out.println("传输进度:"+(curLength/totleLength*100)+"%");System.out.println("传输进度:"+(curLength/totleLength*100)+"%");}dos.flush();System.out.println("接收文件完成");} catch (IOException e) {e.printStackTrace();} finally {try {// 关闭流if (dos != null) {dos.close();}//关闭客户端口if (socket != null) {socket.close();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}}}

在这里我是这样子处理的:

1.文件名我是给服务器决定的,毕竟只是做测试。大家可以由客户端决定传输什么文件,然后服务器如果存在这个文件则通知进行传输,否则告诉客户端不存在这个文件。因为这样比较符合实际情况。我们一般是下载我们自己想要的东西的嘛。

2.建立文件传输的时机是当客户端输入filerequest:xxxx,其中xxxx就是文件传输的端口。显然这样是不严谨的,万一聊天内容含有filerequest,那就GG了。这里留给大家做优化了。


这个例子只能给大家参考,若想实际使用还需要做大量修改优化。刚才想到一些需要注意的地方要跟大家说来的,现在忘了...那就不说了。大家有问题或者意见请在评论中指出哦。


下面附几张运行截图:


---------------------------------------------------------------------------


-----------------------------------------------------------------------------



---本文结束,希望能帮到大家---