建立Socket连接
基本知识
TCP编程:
1. 客户端/服务端(Client/Server)
2. C/S架构
我们开发一种产品时,需要编写客户端程序和服务端程序,服务端程序放在服务器上,客户端程序给用户使用。
对于网络编程:一台电脑,代码也认为是多台电脑
Socket(网络套接字):可以认为是一条网络连接
ServerSocket:在指定端口建立监听,可以接收socket
Socket对象中可以得到输入流和输出流,直接使用即可,
1. 输入流可以从网络对端接收数据,
2. 输出流可以把数据发送到网络对端,
在使用过滤流的时候需要注意,C/S双方创建过滤流的顺序应该不同,否则会使程序发生阻塞。
DEMO
1. DEMO1:服务器和客户端互相发送字符串
2. DEMO2:服务器给客户端发送一个文件
3. DEMO3:发送文件时支持断点续传
flush()方法
1. 刷新缓冲区,强制缓冲区的数据流出
Flushable接口中定义了flush,OutputStream继承了这个接口
FileOutputStream究其根源,flush什么也没写
BufferedOutputStream是一个过滤流,之所以可以缓冲,是因为内部定义了一个byte[],当byte[]达到一定大小的时候,统一写出去。
这个流中的flush有作用,强行把byte[]中的数据写出去。
ObjectOutputStream中也有缓冲区
close()会执行flush()
代码示例
客户端
package demo;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;
public class MyClient {
public static void main(String[] args) throws UnknownHostException, IOException {
/**
* 现在编写客户端,我们不需要关系自己客户端的地址
* 但是一定要知道服务器的地址,这样才能连接服务器
* 服务器开放的端口也是必须要知道的
*/
Socket ss = new Socket("192.168.1.100", 6767);
System.out.println("服务器连接成功");
}
}
服务端
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer {
public static void main(String[] arg) throws IOException {
//建立网络服务端对象,监听6767端口
ServerSocket serv = new ServerSocket(6767);
System.out.println("服务器启动成功,等待用户的接入");
//等待用户接入,直到用户接入为止
Socket sc = serv.accept();
System.out.println("有客户端接入,客户IP:"+sc.getInetAddress());
}
}
运行
然后先运行服务端(服务器端)的程序,再运行客户端程序,效果如下:
1. 服务端运行效果
2. 客户端运行效果
这也验证了一句话: 对于网络编程:一台电脑,代码也认为是多台电脑。
Socket基本流数据传输
示意图
基本流程序示例
服务端工程程序
public class MyServer {
public static void main(String[] arg) throws IOException {
//建立网络服务端对象,监听6767端口
ServerSocket serv = new ServerSocket(6767);
System.out.println("服务器启动成功,等待用户的接入");
//等待用户接入,直到用户接入为止
Socket sc = serv.accept();
System.out.println("有客户端接入,客户IP:"+sc.getInetAddress());
//从socket中得到网络输入流,接收来自网络的数据
InputStream in = sc.getInputStream();
//从socket中得到网络输出流,把数据发送到网络上
OutputStream out = sc.getOutputStream();
//接收缓冲区
byte[] b = new byte[1024];
//接收数据
int len = in.read(b);
String str = new String(b, 0, len);
System.out.println("来自客户端的消息是: "+ str);
//往客户端发送消息
out.write("你好,我是服务端,欢迎光临".getBytes());
sc.close();
}
}
客户端工程程序
public class MyClient {
public static void main(String[] args) throws UnknownHostException, IOException {
/**
* 现在编写客户端,我们不需要关系自己客户端的地址
* 但是一定要知道服务器的地址,这样才能连接服务器
* 服务器开放的端口也是必须要知道的
*/
Socket ss = new Socket("192.168.1.100", 6767);
System.out.println("服务器连接成功");
//从socket中得到网络输入流,接收来自网络的数据
InputStream in = ss.getInputStream();
//从socket中得到网络输出流,把数据发送到网络上
OutputStream out = ss.getOutputStream();
out.write("你好,我是客户端".getBytes());
//接收来自服务端的信息
//接收缓冲区
byte[] b = new byte[1024];
//接收数据
int len = in.read(b);
String str = new String(b, 0, len);
System.out.println("来自服务端的消息是: "+ str);
ss.close();
}
}
程序运行结果
1. 服务端程序的运行结果:
客户端程序的运行结果:
Socket过滤流数据传输
过滤流属于高级流,过滤流可以写对象,万物皆对象
高级流程序示例
服务端程序:
public class MyServer {
public static void main(String[] arg) throws IOException, ClassNotFoundException {
//建立网络服务端对象,监听6767端口
ServerSocket serv = new ServerSocket(6767);
System.out.println("服务器启动成功,等待用户的接入");
//等待用户接入,直到用户接入为止
Socket sc = serv.accept();
System.out.println("有客户端接入,客户IP:"+sc.getInetAddress());
//从socket中得到网络输入流,接收来自网络的数据
InputStream in = sc.getInputStream();
//从socket中得到网络输出流,把数据发送到网络上
OutputStream out = sc.getOutputStream();
//创建过滤流,注意服务端和客户端的顺序不同
ObjectInputStream ois = new ObjectInputStream(in);
ObjectOutputStream oos = new ObjectOutputStream(out);
//接收来自客户端的消息
String s1 = (String)ois.readObject();
System.out.println(s1);
//往客户端发送消息
oos.writeObject("你好,我是服务端,很霸气的!");
sc.close();
}
}
客户端程序
public class MyClient {
public static void main(String[] args) throws UnknownHostException, IOException, ClassNotFoundException {
/**
* 现在编写客户端,我们不需要关系自己客户端的地址
* 但是一定要知道服务器的地址,这样才能连接服务器
* 服务器开放的端口也是必须要知道的
*/
Socket ss = new Socket("192.168.1.100", 6767);
System.out.println("服务器连接成功");
//从socket中得到网络输入流,接收来自网络的数据
InputStream in = ss.getInputStream();
//从socket中得到网络输出流,把数据发送到网络上
OutputStream out = ss.getOutputStream();
//创建过滤流,注意服务端和客户端的顺序不同
ObjectOutputStream oos = new ObjectOutputStream(out);
ObjectInputStream ois = new ObjectInputStream(in);
//往服务端发送消息
oos.writeObject("你好,我是客户端,很帅很帅");
//接收来自服务端的消息
String s1 = (String)ois.readObject();
System.out.println(s1);
ss.close();
}
}
程序运行结果:
1. 服务端程序运行结果
2. 客户端程序运行结果
高级流注意事项
创建过滤流(高级流)时,服务端和发送端的顺序应该不同,如果相同,数据将不能互相发送。
Socket传文件
文件传输流程
示意图
本示意图只是演示了文件从服务端发送到客户端过程,从客户端到服务端的原理一样,电脑A本地文件<—>JVM<—>网络通道<—>电脑B本地文件<—>JVM<—>网络通道。
发送端
服务器给客户端发送一个文件。
网络服务器工程程序示例:
public class FileServer {
public static void main(String[] args) throws IOException {
//监听12345端口
ServerSocket ser = new ServerSocket(12345);
System.out.println("等待用户接入服务器!!!");
//等待用户接入,知道用户接入为止
Socket sc = ser.accept();
//获取sc的IO流
InputStream in = sc.getInputStream();
OutputStream out = sc.getOutputStream();
//为了简化操作,创建过滤流
ObjectInputStream ois = new ObjectInputStream(in);
ObjectOutputStream oos = new ObjectOutputStream(out);
//获取服务器端的本地文件信息
File f1 = new File("F:\\abc\\Xshell_Plus_v6.0.0095.7z");
//获取文件的IO流
//创建输入流,把文件读取到java的jvm中
InputStream FileIn = new FileInputStream(f1);
//发送文件名
oos.writeObject(f1.getName());
/*发送文件*/
//创建128k缓冲区,用于缓冲本地文件
byte[] b = new byte[1024*128];
for(int len = 0;(len = FileIn.read(b)) != -1;) {
//将数据写入到网络中
out.write(b, 0, len);
}
System.out.println("文件发送完成!!!");
//关闭网络流
sc.close();
//关闭文件流
FileIn.close();
}
}
接收端
客户端接收服务端发送的一个文件。
网络客户端工程程序示例:
public class FileClient {
public static void main(String[] args) throws UnknownHostException, IOException, ClassNotFoundException {
//通过socket(网络通道),连接服务器
Socket sc = new Socket("192.168.1.100", 12345);
System.out.println("成功连接服务器.......");
//从网络套字节socket获取,网络IO流
OutputStream out = sc.getOutputStream();
InputStream in = sc.getInputStream();
//创建过滤流
//注意,服务端与客户端的顺序相反
ObjectOutputStream oos = new ObjectOutputStream(out);
ObjectInputStream ois = new ObjectInputStream(in);
//接收文件名
String FileName = (String)ois.readObject();
//获取本地存放的目录信息
File f1 = new File("F:\\download\\"+FileName);
//创建文件输出流
OutputStream FileOut = new FileOutputStream(f1);
System.out.println("开始接收文件"+ FileName);
//从网络接收文件的缓冲区
byte[] b= new byte[1024*128];
//将文件写入到本地中
for(int len = 0; (len = in.read(b)) != -1 ;) {
//从JVM中读取数据
FileOut.write(b, 0, len);
}
//关闭网络流
sc.close();
//关闭文件流
FileOut.close();
System.out.println("接收文件完成 ");
}
}
断点续传
需要服务端和客户端进行协商,首先客户端要判断文件的大小,将文件的大小传给服务器,然后服务器进行响应,xc代表续传,xin重头开始传,bu不传。
其实重头开始传和续传对于客户端是一样的,只不过重传的跳过文件比特大小为0。
本程序如果想要演示断点续传,可以利用360安全软件(功能大全->网络优化->360流量防火墙)限制java的虚拟机速度(前提是要运行java程序,才会启用虚拟机javaw),当传一部分数据时,关闭java程序的运行,再次运行程序,查看是否可以断点续传。
客户端程序
public class FileClient {
public static void main(String[] args) throws UnknownHostException, IOException, ClassNotFoundException {
//通过socket(网络通道),连接服务器
Socket sc = new Socket("192.168.1.100", 12345);
System.out.println("成功连接服务器.......");
//从网络套字节socket获取,网络IO流
OutputStream out = sc.getOutputStream();
InputStream in = sc.getInputStream();
//创建过滤流
//注意,服务端与客户端的顺序相反
ObjectOutputStream oos = new ObjectOutputStream(out);
ObjectInputStream ois = new ObjectInputStream(in);
//接收文件名
String FileName = (String)ois.readObject();
//获取本地存放的目录信息
File f1 = new File("F:\\download\\"+FileName);
//将客户端本地文件的大小,传送给服务端
//服务端进行响应,"xc"续传和重头开始传,"bc"不传
oos.writeLong(f1.length());
oos.flush();//刷新缓冲区,强制缓冲区数据流出
System.out.println("本地文件大小:"+ f1.length());
//获取服务器的响应
String str = (String)ois.readObject();
//根据服务器的响应,开始进行接收数据
if("xc".equals(str)) {//断点续传和重传
//创建文件输出流,从jvm输出到本地
//true表示输出的数据追加到所接收的文件
OutputStream FileOut = new FileOutputStream(f1, true);
System.out.println("开始接收文件"+ FileName);
//从网络接收文件的缓冲区
byte[] b= new byte[1024*128];
//将文件写入到本地中
for(int len = 0; (len = in.read(b)) != -1 ;) {
//从JVM中读取数据
FileOut.write(b, 0, len);
}
//关闭网络流
sc.close();
//关闭文件流
FileOut.close();
System.out.println("接收文件完成 ");
}else if("bu".equals(str)) {
System.out.println("本地存在将下载文件");
}
}
}
服务端程序
public class FileServer {
public static void main(String[] args) throws IOException {
//监听12345端口
ServerSocket ser = new ServerSocket(12345);
System.out.println("等待用户接入服务器!!!");
//等待用户接入,知道用户接入为止
Socket sc = ser.accept();
//获取sc的IO流
InputStream in = sc.getInputStream();
OutputStream out = sc.getOutputStream();
//为了简化操作,创建过滤流
ObjectInputStream ois = new ObjectInputStream(in);
ObjectOutputStream oos = new ObjectOutputStream(out);
//获取服务器端的本地文件信息
File f1 = new File("F:\\abc\\Xshell_Plus_v6.0.0095.7z");
//获取文件的IO流
//创建输入流,把文件读取到java的jvm中
InputStream FileIn = new FileInputStream(f1);
//发送文件名
oos.writeObject(f1.getName());
//接收客户端的文件大小
long overSize = ois.readLong();
System.out.println("客户端的文件大小: " + overSize);
if( f1.length() == overSize) {
oos.writeObject("bu");
System.out.println("客户端文件存在无需再传");
}else if( overSize >= 0) { //断点续传和重传
oos.writeObject("xc");
System.out.println("服务端开始发送文件给客户端......");
//跳过文件的大小为overSize 比特开始读取文件
FileIn.skip(overSize);
System.out.println("文件的起始位置: " + overSize);
/*发送文件*/
//创建128k缓冲区,用于缓冲本地文件
byte[] b = new byte[1024*128];
for(int len = 0;(len = FileIn.read(b)) != -1;) {
//将数据写入到网络中
out.write(b, 0, len);
}
System.out.println("文件发送完成!!!");
//关闭网络流
sc.close();
//关闭文件流
FileIn.close();
}
}
}
输出流中的flush方法
//通过socket(网络通道),连接服务器
Socket sc = new Socket("192.168.1.100", 12345);
//从网络套字节socket获取,网络IO流
OutputStream out = sc.getOutputStream();
//创建过滤流
//注意,服务端与客户端的顺序相反
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeLong(f1.length());
oos.flush();
flush():刷新缓冲区,强制缓冲区数据流出
当我们把数据写入到jvm的缓冲区中后,如果没有达到缓冲区大小数据不会从缓冲区中流出,只有达到缓冲区大小数据才会从缓冲区中流出,而flush()方法就是不管你的缓冲区是否满了,我都将强制缓冲区的数据流出。
Flushable接口中定义了flush(),OutputStream继承了这个接口。
1. FileOutputStream中的flush什么也没写。
BufferedOutputStream是一个过滤流,之所以缓冲,是因为内部定义了一个byte数组,当byte[]达到一定大小的时候,统一写出去。
1. ObjectOutputStream中也有一个缓冲区。
执行close()方法也会执行flush()方法。
基本流没有缓冲区。
1. 这个流中的flush有作用,强行把byte[]中的数据写出去。