Java-网络编程基础
目录
一、网络编程
1.网络编程概述
2.电脑之间数据传输需要的条件(网络编程三要素)
(1)IP
(2)端口
(3)协议
①UDP协议
②TCP协议
3.常用命令
(1)IP相关
4.InetAddress类
5.UDP通信程序
(1)发送数据
(2)接收数据
(3)练习
(4)UDP的三种通信方式
(5)组播代码实现
①.组播发送
②组播接收(主要写区别了,大体一致)
(6)UDP广播代码实现
6.TCP通信程序
(1)发送数据
(2)接收数据
(3)三次握手
(4)四次挥手
(5)练习
(6)练习02
(7)服务端优化
①可多次接收客户端
②UUID类
③多线程版
⑤多线程版优化-线程池
(8)问题【待解决】
一、网络编程
1.网络编程概述
-
在网络通信协议下,不同计算机上运行的程序,可以进行数据传输
2.电脑之间数据传输需要的条件(网络编程三要素)
-
确认接收端在网络中的位置-------------------------------------------------IP地址(设备在网络中的地址,是唯一的标识)
-
确定接收端中某接收输数据软件的入口----------------------------------端口号(应用程序在设备中唯一的标识)
-
确定网络中传输数据的规则--------------------------------------------------协议(数据在网络中传输的规则,常见的协议有UDP协议和TCP协议)
(1)IP
全称”互联网协议地址,也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6,
-
ipv4:32bit----------------------------->11000000 10101000 00000001 01000010-------------------------->为了好读使用点分十进制表示法------------------->192.168.1.66
-
ipv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,而IPv4的模式下IP的总数是有限的。采用128位地址长度,分成8组------------>位数太多不表示了---------> 为了好读使用冒分十六进制表示法----------------------->2001:0DB8:0000:0023:0008:0800:200C:417A----------------->省略前面的0------------>2001:DB8:0:23:8:800:200C:417A
ipv6特殊情况:如果计算出的16进制表示形式中间有多个连续的0------------------>FF01:0:0:0:0:0:0:1101----------------------->0位压缩表示法---------->FF01::1101
(2)端口
应用程序在设备中唯一的标识
端口号:用两个字节表示的整数,它的取值范围是0~65535,其中0~1023之间的端口号用于一些知名的网络服务或者应用,我们自己使用1024以上的端口号就可以了
注意:一个端口号只能被一个应用程序使用.
(3)协议
计算机网络中,连接和通信的规则被称为网络通信协议
①UDP协议
-
用户数据报协议(User Datagram Protocol)
-
面向无连接通信协议
特点
-
速度快
-
有大小限制,一次最多发送64K
-
不安全,易丢失数据
②TCP协议
-
传输控制协议(Transmission Control Protocol)
-
TCP协议是面向连接的通信协议。
特点
-
速度慢
-
没有大小限制
-
数据安全
3.常用命令
(1)IP相关
-
ipconfig:查看本机IP地址
-
ping IP地址:检查网络是否通畅
特殊IP地址127.0.0.1
是回送地址也称本地回环地址,可以代表本机的IP地址,一般用来测试使用
4.InetAddress类
为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用
主要方法
方法名 | 说明 |
---|---|
static InetAddress getByName(String host) | 根据提供的主机名和IP地址创建InetAddress |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回IP地址字符串 |
public class InetAddressDemo {public static void main(String[] args) throws UnknownHostException {
//获取InetAddress对象,可以传递主机名或IP地址InetAddress address = InetAddress.getByName("DESKTOP-03DC35L");
//获得主机名final String hostName = address.getHostName();//获得IP地址final String hostAddress = address.getHostAddress();System.out.println(hostName);System.out.println(hostAddress);}
}
5.UDP通信程序
(1)发送数据
-
找发送端------------------------创建发送端的DatagramSocket对象
-
封装信息------------------------创建数据,并把数据打包(DatagramPacket)
-
发送端发送信息---------------调用DatagramSocket对象的方法发送数据
-
关闭发送端----------------------释放资源
代码实现:
public class SendingClient {public static void main(String[] args) throws IOException {//创建发送端DatagramSocket datagramSocket = new DatagramSocket();
byte[] bytes = "发送消息".getBytes(StandardCharsets.UTF_8);InetAddress inetAddress = InetAddress.getByName("127.0.0.1");int port = 10000;//打包消息DatagramPacket datagramPacket = datagramPacket(bytes, bytes.length, inetAddress, port);
//发送消息datagramSocket.send(datagramPacket);
//释放资源datagramSocket.close();
}
/*** 消息打包* @param bytes 数据* @param length 数据大小,全部则为bytes.length* @param inetAddress 接收端* @param port 接收端的端口号*/private static DatagramPacket datagramPacket(byte[] bytes, int length, InetAddress inetAddress,int port) {//打包消息return new DatagramPacket(bytes,length,inetAddress,port);;}
}
(2)接收数据
-
找接收端----------------------------------------------------------------创建接收端的DatagramSocket对象
-
创建一个容器,用来封装接收的数据--------------------------创建容器,用于接收数据
-
接收数据,并将数据放入到上一步创建的容器中----------调用DatagramSocket的方法接收数据并将数据放入箱子中
-
从容器中获取数据---------------------------------------------------解析数据
-
关闭接收端------------------------------------------------------------释放资源
代码实现:
public class ReceivingClient {public static void main(String[] args) throws IOException {//创建接收端,传入端口号,//无参数则为从随机端口接收数据DatagramSocket receive = new DatagramSocket(10000);//创建新的容器,用于接收数据//DatagramPacket(byte[] buf, int length)byte[] bytes = new byte[1024];DatagramPacket datagramPacket = getDatagramPacket(bytes);;
//接收数据receive.receive(datagramPacket);
//获得数据并解析byte[] data = datagramPacket.getData();int length = datagramPacket.getLength();System.out.println(new String(data,0,length));
//释放资源receive.close();}
private static DatagramPacket getDatagramPacket(byte[] bytes){return new DatagramPacket(bytes,bytes.length);}
}
(3)练习
UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
UDP接收数据:因为接收不知道发送端什么时候停止发送,故采用死循环接收
发送端:
public class SendClient {public static void main(String[] args) throws IOException {//固定的数据放上面//2.4 接收端ip地址InetAddress inetAddress = InetAddress.getByName("127.0.0.1");//2.5 接收端端口号int port = 10001;
//面向对象化//1.创建发送对象DatagramSocket send = createSendClient();
//2.数据准备//2.1 创建Scanner对象Scanner scanner = createScanner();
while (true){//2.2 输入要发送的消息String message = message(scanner);//2.3 数据处理str转byte[]byte[] bytes = strToBytes(message);//4.封装数据//4.1 创建封装对象DatagramPacket dataPacket = createDataPacket(bytes, bytes.length, inetAddress, port);//5.发送数据send.send(dataPacket);
if ("886".equals(message)){break;}}
//6.释放资源send.close();System.out.println("发送端已关闭");
}
private static DatagramPacket createDataPacket(byte[] bytes,int length,InetAddress inetAddress,int port) {return new DatagramPacket(bytes,bytes.length,inetAddress,port);}
private static Scanner createScanner() {return new Scanner(System.in);}
private static DatagramSocket createSendClient() throws SocketException {//创建发送端DatagramSocket send = new DatagramSocket();return send;}
private static String message(Scanner scanner){System.out.println("请输入要发送的信息");String s = scanner.nextLine();return s;}
private static byte[] strToBytes(String message){return message.getBytes();}
}
接收端:
public class ReceivingClient {public static void main(String[] args) throws IOException {//准备接收端对象,设置端口号DatagramSocket receiveClient = createReceivedClient(10001);
//准备新容器,用来接收数据//1.接收数据大小DatagramPacket dataBox = getDataBox();
while (true){//接收数据receiveClient.receive(dataBox);//从容器中拿数据,输出到控制台byte[] data = dataBox.getData();//实际接受到的数据大小int length = dataBox.getLength();
if ("886".equals(new String(data,0,length))){System.out.println("发送端不再发送消息,接收端准备关闭");break;}else {System.out.println("实际接收到的数据大小:"+length);System.out.println(new String(data,0,length));}}
//关闭接收端receiveClient.close();System.out.println("接收端已关闭");}
private static DatagramPacket getDataBox() {byte[] bytes = new byte[1024];DatagramPacket dataBox = new DatagramPacket(bytes,bytes.length);return dataBox;}
private static DatagramSocket createReceivedClient(int port) throws SocketException {DatagramSocket receiveClient = new DatagramSocket(port);return receiveClient;}
}
(4)UDP的三种通信方式
-
单播----------------------------------1对1,单发送单接收
-
组播(ipv4)----------------------1对多,单发送对一组接收
多播(ipv6)
-
广播----------------------------------1对所有,单发送对所有接收
(5)组播代码实现
组播地址:224.0.0.0~239.255.25.255
其中224.0.0.0~224.0.0.255为预留的组播地址
①.组播发送
和上边发送数据步骤基本一致,但发送数据略有不同
在单播中,是发给指定IP的电脑,但是在组播当中,是发给组播地址
public class SendClient {public static void main(String[] args) throws IOException {//创建对象DatagramSocket datagramSocket = new DatagramSocket();//数据准备String s = "组播测试";byte[] bytes = s.getBytes();InetAddress inetAddress = InetAddress.getByName("224.0.1.0");//封装数据DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,inetAddress,10002);
datagramSocket.send(datagramPacket);
datagramSocket.close();}
}
②组播接收(主要写区别了,大体一致)
-
组播接收端创建的是new MulticastSocket(10086);
-
在创建容器和接收数据之间,添加一步:把当前电脑添加到这个组中
public class ReserveClient {public static void main(String[] args) throws IOException {MulticastSocket multicastSocket = new MulticastSocket(10002);
byte[] bytes = new byte[1024];DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
//添加电脑到组中multicastSocket.joinGroup(InetAddress.getByName("224.0.1.0"));
multicastSocket.receive(packet);
byte[] data = packet.getData();int length = packet.getLength();System.out.println(new String(data,0,length));
multicastSocket.close();}
}
(6)UDP广播代码实现
发送
-
在单播中,是发给指定IP的电脑,但是在广播当中,是发给广播地址
-
广播地址:255.255.255.255
public class SendClient {public static void main(String[] args) throws IOException {DatagramSocket datagramSocket = new DatagramSocket();String message = "广播测试";final byte[] bytes = message.getBytes();InetAddress inetAddress = InetAddress.getByName("255.255.255.255");DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length,inetAddress,10002);datagramSocket.send(datagramPacket);
datagramSocket.close();}
}
接收
-
跟接收一模一样,啥都不用改
6.TCP通信程序
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象。通信之前要保证连接已经建立
通过Socket产生IO流来进行网络通信
(1)发送数据
-
创建客户端的socket对象Socket(String host,int port),与指定服务端连接
-
获得输出流,写数据
-
释放资源
public class SendClient {public static void main(String[] args) throws IOException {//创建一个Socket对象,两个参数//服务器ip地址//端口号Socket socket = new Socket("127.0.0.1",10003);
//获取输出流,写数据OutputStream outputStream = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(outputStream);
//写数据String s = "tcp发送消息";osw.write(s);//释放资源socket.close();}
}
(2)接收数据
-
创建服务端的socket对象ServerSocket(int port)
-
监听客户端连接,返回一个socket对象,accept()等待连接,死等(阻塞)
-
获得输入流,读取数据,并把数据显示或解析
-
释放资源
public class ServiceClient {public static void main(String[] args) throws IOException {//创建服务端的socket对象ServerSocketServerSocket serverSocket = new ServerSocket(10003);//等待客户端连接,连接后会返回一个socket对象Socket accept = serverSocket.accept();
//获取输入流,读取数据InputStream inputStream = accept.getInputStream();InputStreamReader isr = new InputStreamReader(inputStream);
int n;while ((n = isr.read()) != -1){System.out.print((char) n);}//释放资源isr.close();serverSocket.close();
}
}
注意点:
-
accept()方法是阻塞的,作用就是等待客户端连接
-
客户端创建对象运行后会连接服务器,如果服务器没有启动则会报ConnectException异常
-
与服务器连接是通过三次握手协议保证的
-
对客户端来讲,是往外写的,所以是输出流
-
对服务端来讲,是往里读的,所以是输入流
-
IO流中read()方法也是阻塞的
-
客户端在关流的时候,还有一个往服务器写结束标记符的动作
-
最后与服务端断开连接,是通过四次挥手协议保证连接终止的
(3)三次握手
-
第一次:客户端向服务器发出连接请求,等待服务器确认
-
第二次:服务器向客户端返回一个响应,告诉客户端收到了请求
-
第三次:客户端再次发出请求确认信息,连接建立
(4)四次挥手
-
第一次:客户端向服务端发出取消连接的请求
-
第二次:服务器向客户端返回一个响应,表示收到客户端请求
-
第三次:服务器会将最后的数据进行处理,处理完成后服务器会向客户端发出确认取消的消息
-
第四次:客户端再次发送确认取消连接的消息,服务器取消连接
(5)练习
客户端:发送数据,接收服务器反馈
服务器:接收数据,给出反馈
public class SendClient {public static void main(String[] args) throws IOException {Socket socket = new Socket("127.0.0.1",10005);
OutputStream outputStream = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(outputStream);
osw.write("客户端请求:hello world");
//需要写一个结束标记//注意:不能把关流操作放到这,关流完成后会导致整个socket关闭//shutdownOutput() 仅仅关闭输出流,并写一个结束标记osw.flush();socket.shutdownOutput();
InputStreamReader isr = new InputStreamReader(socket.getInputStream());int n;while ((n = isr.read()) != -1){System.out.print((char) n);}
isr.close();osw.close();socket.close();
}
}
public class ServerClient {public static void main(String[] args) throws IOException {ServerSocket serverSocket = new ServerSocket(10005);
Socket accept = serverSocket.accept();
InputStream inputStream = accept.getInputStream();InputStreamReader isr = new InputStreamReader(inputStream);int n;while ((n = isr.read()) != -1){System.out.print((char) n);}
OutputStream outputStream = accept.getOutputStream();outputStream.write("服务端响应:你是谁?".getBytes());
outputStream.close();isr.close();accept.close();serverSocket.close();}}
(6)练习02
客户端:将本地文件上传到服务器。接收服务器的反馈
服务器:接收客户端上传的文件并保存,保存完毕之后给出反馈
public class SendClient {public static void main(String[] args) throws IOException {
//创建socket对象//传入地址和端口号Socket socket = new Socket("127.0.0.1",10005);
//优化输入流的变量byte[] bytes = new byte[1024];int length;
//创建一个File对象,获取文件名和文件路径File file = new File("src\\tcp\\socket_tcp03_2\\music");//创建一个ArrayList数组,准备保存文件名ArrayList<String> fileNameList = new ArrayList<>();//查看该目录下所有文件File[] files = file.listFiles();if (files.length != 0){for (File listFile : files) {fileNameList.add(listFile.getName());}}
//创建一个字节输入流,准备从本地文件中读取一个作为数据写出//传入文件路径FileInputStream fis = new FileInputStream(new File(file, fileNameList.get(0)));//优化输入流BufferedInputStream bis = new BufferedInputStream(fis);//创建一个集合,用来保存读取到的数据ArrayList<byte[]> dataList = new ArrayList<>();
while ((length = bis.read(bytes)) != -1){dataList.add(Arrays.copyOfRange(bytes,0,length));}
//获取输出流,准备写出数据OutputStream os = socket.getOutputStream();BufferedOutputStream bos = new BufferedOutputStream(os);for (byte[] bytes1 : dataList) {bos.write(bytes1);bos.flush();}//不传输数据后,单独关闭socket的输出流socket.shutdownOutput();
//获取输入流,准备接收服务端的反馈InputStream is = socket.getInputStream();bis = new BufferedInputStream(is);while ((length = bis.read(bytes))!= -1){System.out.print(new String(bytes,0,length));}socket.close();
}
}
Server类,用来实现服务端的具体操作
public class Server {
/*** 遍历数组并写出* @param listBytes 保存数据的集合* @param osw 输出流* @throws IOException 会抛出IO异常*/public void traverseAndWrite(ArrayList<byte[]> listBytes, BufferedOutputStream osw) throws IOException {for (byte[] listByte : listBytes) {osw.write(listByte);}}
/*** 输出流优化,缓冲字节输出流* @param fos 需要优化的字节流* @return 缓冲字节输出流对象*/public BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) {return new BufferedOutputStream(fos);}
/*** 创建输出流* @param file 要写出的文件(路径+文件名)* @return 返回字节输出流对象* @throws FileNotFoundException 抛出FileNotFoundException异常*/public FileOutputStream getFileOutputStream(File file) throws FileNotFoundException {return new FileOutputStream(file);}
/*** 把内容写入到哪* @param folder 目录(目标文件夹)* @param fileName 文件名(目标文件名)* @param nameSuffix 文件后缀名(格式.jpg/.mp3等)* @return 返回File对象*/public File getFile(File folder, String fileName,String nameSuffix) {return new File(folder, fileName +nameSuffix);}
/*** 获取唯一文件名* @return 返回唯一文件名(String)*/public String getFileName() {return UUID.randomUUID().toString().replace("-","");}
/*** 检查目标文件夹是否存在* @param folder 目标文件夹*/public void inspectFolder(File folder) {if (!folder.exists()){if (folder.mkdir()){System.out.println("文件夹创建成功");}}}
/*** 设置写出路径* @param path 目标位置* @return 返回File对象*/public File getFolder(String path) {return new File(path);}
/*** 读取数据* @param bis 缓冲字节输入流对象* @param listBytes 存储数据的集合* @throws IOException 会抛出IOException*/public void readData(BufferedInputStream bis, ArrayList<byte[]> listBytes) throws IOException {byte[] bytes = new byte[1024];int length;while ((length = bis.read(bytes)) != -1){listBytes.add(Arrays.copyOfRange(bytes,0,length));}}
/*** 创建集合* @return ArrayList<byte[]>*/public ArrayList<byte[]> getArrayList() {return new ArrayList<>();}
/*** 优化输入流* @param inputStream 字节输入流* @return 返回缓冲字节输入流对象*/public BufferedInputStream getBufferedInputStream(InputStream inputStream) {return new BufferedInputStream(inputStream);}
/*** 获取服务端Socket的输入流* @param serverSocket 服务端的socket对象* @return 返回服务端Socket的字节输入流对象* @throws IOException 会抛出IOException*/public InputStream getInputStream(Socket serverSocket) throws IOException {return serverSocket.getInputStream();}
/*** 获取服务端Socket对象* @param ss ServerSocket* @return 返回服务端的socket对象* @throws IOException 会抛出IOException*/public Socket getServerSocket(ServerSocket ss) throws IOException {return ss.accept();}
}
服务端:
public class ServerClient {public static void main(String[] args) throws IOException {
Server server = new Server();
//创建ServerSocket对象,设置好端口号ServerSocket ss = new ServerSocket(10005);
//获取服务端的socket对象Socket serverSocket = server.getServerSocket(ss);
//获取输入流,准备读取数据InputStream inputStream = server.getInputStream(serverSocket);
//输入流优化BufferedInputStream bis = server.getBufferedInputStream(inputStream);
//创建一个集合(可选)保存接收到的数据ArrayList<byte[]> listBytes = server.getArrayList();
//读取数据并保存到listBytes集合中server.readData(bis, listBytes);
//设置写出路径String s = "src\\tcp\\socket_tcp03_2\\serverClient_file";File folder = server.getFolder(s);
//检查文件夹是否存在server.inspectFolder(folder);
//获取唯一文件名final String fileName = server.getFileName();
//设置文件后缀名String nameSuffix = ".mp3";
//在设置好的路径下创建名为【fileName】的空文件File file = server.getFile(folder, fileName,nameSuffix);
//创建输出流,准备写出数据FileOutputStream fos = server.getFileOutputStream(file);//优化输出流BufferedOutputStream osw = server.getBufferedOutputStream(fos);//遍历集合并写出数据server.traverseAndWrite(listBytes, osw);
//获取输出流,准备给反馈OutputStream os = serverSocket.getOutputStream();//优化BufferedOutputStream bos = new BufferedOutputStream(os);//写反馈bos.write("保存成功".getBytes());bos.flush();
//释放资源serverSocket.close();ss.close();
}
}
(7)服务端优化
①可多次接收客户端
使用循环虽然可以让服务器处理多个客户端请求。但是无法同时跟多个客户端进行通信。
while (true){//获取服务端的socket对象Socket serverSocket = getServerSocket(ss);
//获取输入流,准备读取数据InputStream inputStream = getInputStream(serverSocket);
//输入流优化BufferedInputStream bis = getBufferedInputStream(inputStream);
//创建一个集合(可选)保存接收到的数据ArrayList<byte[]> listBytes = getArrayList();
//读取数据并保存到listBytes集合中readData(bis, listBytes);
//设置写出路径String s = "src\\tcp\\socket_tcp03\\serverClient_file";File folder = getFolder(s);
//检查文件夹是否存在inspectFolder(folder);
//获取唯一文件名final String fileName = getFileName();
//设置文件后缀名String nameSuffix = ".mp3";
//在设置好的路径下创建名为【fileName】的空文件File file = getFile(folder, fileName,nameSuffix);
//创建输出流,准备写出数据FileOutputStream fos = getFileOutputStream(file);//优化输出流BufferedOutputStream osw = getBufferedOutputStream(fos);//遍历集合并写出数据traverseAndWrite(listBytes, osw);
//获取输出流,准备给反馈OutputStream os = serverSocket.getOutputStream();//优化BufferedOutputStream bos = new BufferedOutputStream(os);//写反馈bos.write("保存成功".getBytes());
//释放资源serverSocket.close();}
②UUID类
主要方法
方法名 | 说明 |
---|---|
static UUID randomUUID() | 静态工厂检索一个类型4《伪随机生成》的UUID。 |
String toString() | 返回表示此 UUID 的 String 对象 |
③多线程版
使用多线程虽然可以让服务器同时处理多个客户端请求。但是资源消耗太大。
public class ServerClient {public static void main(String[] args) throws IOException {//创建ServerSocket对象,设置好端口号ServerSocket ss = new ServerSocket(10005);while (true){Socket accept = ss.accept();ThreadSocket threadSocket = new ThreadSocket(accept);new Thread(threadSocket).start();}}
}
public class ThreadSocket implements Runnable{Socket socket;
public ThreadSocket(Socket socket) {this.socket = socket;}
@Overridepublic void run() {//获取输入流,准备读取数据InputStream inputStream = null;try {inputStream = getInputStream(socket);} catch (IOException e) {e.printStackTrace();}
//输入流优化BufferedInputStream bis = getBufferedInputStream(inputStream);
//创建一个集合(可选)保存接收到的数据ArrayList<byte[]> listBytes = getArrayList();
//读取数据并保存到listBytes集合中try {readData(bis, listBytes);} catch (IOException e) {e.printStackTrace();}
//设置写出路径String s = "src\\tcp\\socket_tcp04\\serverClient_file";File folder = getFolder(s);
//检查文件夹是否存在inspectFolder(folder);
//获取唯一文件名final String fileName = getFileName();
//设置文件后缀名String nameSuffix = ".mp3";
//在设置好的路径下创建名为【fileName】的空文件File file = getFile(folder, fileName,nameSuffix);
//创建输出流,准备写出数据FileOutputStream fos = null;try {fos = getFileOutputStream(file);} catch (FileNotFoundException e) {e.printStackTrace();}//优化输出流BufferedOutputStream osw = getBufferedOutputStream(fos);//遍历集合并写出数据try {traverseAndWrite(listBytes, osw);} catch (IOException e) {e.printStackTrace();}
//获取输出流,准备给反馈OutputStream os = null;try {os = socket.getOutputStream();} catch (IOException e) {e.printStackTrace();}//优化BufferedOutputStream bos = null;if (os != null){bos = new BufferedOutputStream(os);}
//写反馈try {if (bos!=null){bos.write("保存成功".getBytes());bos.flush();}
} catch (IOException e) {e.printStackTrace();}
//释放资源try {socket.close();} catch (IOException e) {e.printStackTrace();}
}
/*** 遍历数组并写出* @param listBytes 保存数据的集合* @param osw 输出流* @throws IOException 会抛出IO异常*/private void traverseAndWrite(ArrayList<byte[]> listBytes, BufferedOutputStream osw) throws IOException {for (byte[] listByte : listBytes) {osw.write(listByte);osw.flush();}}
/*** 输出流优化,缓冲字节输出流* @param fos 需要优化的字节流* @return 缓冲字节输出流对象*/private BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) {return new BufferedOutputStream(fos);}
/*** 创建输出流* @param file 要写出的文件(路径+文件名)* @return 返回字节输出流对象* @throws FileNotFoundException 抛出FileNotFoundException异常*/private FileOutputStream getFileOutputStream(File file) throws FileNotFoundException {return new FileOutputStream(file);}
/*** 把内容写入到哪* @param folder 目录(目标文件夹)* @param fileName 文件名(目标文件名)* @param nameSuffix 文件后缀名(格式.jpg/.mp3等)* @return 返回File对象*/private File getFile(File folder, String fileName,String nameSuffix) {return new File(folder, fileName +nameSuffix);}
/*** 获取唯一文件名* @return 返回唯一文件名(String)*/private String getFileName() {return UUID.randomUUID().toString().replace("-","");}
/*** 检查目标文件夹是否存在* @param folder 目标文件夹*/private void inspectFolder(File folder) {if (!folder.exists()){if (folder.mkdir()){System.out.println("文件夹创建成功");}}}
/*** 设置写出路径* @param path 目标位置* @return 返回File对象*/private File getFolder(String path) {return new File(path);}
/*** 读取数据* @param bis 缓冲字节输入流对象* @param listBytes 存储数据的集合* @throws IOException 会抛出IOException*/private void readData(BufferedInputStream bis, ArrayList<byte[]> listBytes) throws IOException {byte[] bytes = new byte[1024];int length;while ((length = bis.read(bytes)) != -1){listBytes.add(Arrays.copyOfRange(bytes,0,length));}}
/*** 创建集合* @return ArrayList<byte[]>*/private ArrayList<byte[]> getArrayList() {return new ArrayList<>();}
/*** 优化输入流* @param inputStream 字节输入流* @return 返回缓冲字节输入流对象*/private BufferedInputStream getBufferedInputStream(InputStream inputStream) {return new BufferedInputStream(inputStream);}
/*** 获取服务端Socket的输入流* @param serverSocket 服务端的socket对象* @return 返回服务端Socket的字节输入流对象* @throws IOException 会抛出IOException*/private InputStream getInputStream(Socket serverSocket) throws IOException {return serverSocket.getInputStream();}
}
⑤多线程版优化-线程池
public class ServerClient {public static void main(String[] args) throws IOException {//创建ServerSocket对象,设置好端口号ServerSocket ss = new ServerSocket(10005);//创建线程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(//核心线程数量5,//线程池的总数量10,//临时线程空闲时间:多长时间没有任务就销毁60,//空闲时间单位TimeUnit.MINUTES,//阻塞队列,允许多少个线程排队new ArrayBlockingQueue<>(5),//创建线程的方式Executors.defaultThreadFactory(),//任务拒绝策略new ThreadPoolExecutor.AbortPolicy());
while (true){Socket accept = ss.accept();ThreadSocket threadSocket = new ThreadSocket(accept);poolExecutor.submit(threadSocket);final int activeCount = poolExecutor.getActiveCount();System.out.println(activeCount);}
}
}
public class ThreadSocket implements Runnable{Socket socket;
public ThreadSocket(Socket socket) {this.socket = socket;}
@Overridepublic void run() {//获取输入流,准备读取数据InputStream inputStream = null;try {inputStream = getInputStream(socket);} catch (IOException e) {e.printStackTrace();}
//输入流优化BufferedInputStream bis = getBufferedInputStream(inputStream);
//创建一个集合(可选)保存接收到的数据ArrayList<byte[]> listBytes = getArrayList();
//读取数据并保存到listBytes集合中try {readData(bis, listBytes);} catch (IOException e) {e.printStackTrace();}
//设置写出路径String s = "src\\tcp\\socket_tcp04\\serverClient_file";File folder = getFolder(s);
//检查文件夹是否存在inspectFolder(folder);
//获取唯一文件名final String fileName = getFileName();
//设置文件后缀名String nameSuffix = ".mp3";
//在设置好的路径下创建名为【fileName】的空文件File file = getFile(folder, fileName,nameSuffix);
//创建输出流,准备写出数据FileOutputStream fos = null;try {fos = getFileOutputStream(file);} catch (FileNotFoundException e) {e.printStackTrace();}//优化输出流BufferedOutputStream osw = getBufferedOutputStream(fos);//遍历集合并写出数据try {traverseAndWrite(listBytes, osw);} catch (IOException e) {e.printStackTrace();}
//获取输出流,准备给反馈OutputStream os = null;try {os = socket.getOutputStream();} catch (IOException e) {e.printStackTrace();}//优化BufferedOutputStream bos = null;if (os != null){bos = new BufferedOutputStream(os);}
//写反馈try {if (bos!=null){bos.write("保存成功".getBytes());bos.flush();}
} catch (IOException e) {e.printStackTrace();}
//释放资源try {socket.close();} catch (IOException e) {e.printStackTrace();}
}
/*** 遍历数组并写出* @param listBytes 保存数据的集合* @param osw 输出流* @throws IOException 会抛出IO异常*/private void traverseAndWrite(ArrayList<byte[]> listBytes, BufferedOutputStream osw) throws IOException {for (byte[] listByte : listBytes) {osw.write(listByte);osw.flush();}}
/*** 输出流优化,缓冲字节输出流* @param fos 需要优化的字节流* @return 缓冲字节输出流对象*/private BufferedOutputStream getBufferedOutputStream(FileOutputStream fos) {return new BufferedOutputStream(fos);}
/*** 创建输出流* @param file 要写出的文件(路径+文件名)* @return 返回字节输出流对象* @throws FileNotFoundException 抛出FileNotFoundException异常*/private FileOutputStream getFileOutputStream(File file) throws FileNotFoundException {return new FileOutputStream(file);}
/*** 把内容写入到哪* @param folder 目录(目标文件夹)* @param fileName 文件名(目标文件名)* @param nameSuffix 文件后缀名(格式.jpg/.mp3等)* @return 返回File对象*/private File getFile(File folder, String fileName,String nameSuffix) {return new File(folder, fileName +nameSuffix);}
/*** 获取唯一文件名* @return 返回唯一文件名(String)*/private String getFileName() {return UUID.randomUUID().toString().replace("-","");}
/*** 检查目标文件夹是否存在* @param folder 目标文件夹*/private void inspectFolder(File folder) {if (!folder.exists()){if (folder.mkdir()){System.out.println("文件夹创建成功");}}}
/*** 设置写出路径* @param path 目标位置* @return 返回File对象*/private File getFolder(String path) {return new File(path);}
/*** 读取数据* @param bis 缓冲字节输入流对象* @param listBytes 存储数据的集合* @throws IOException 会抛出IOException*/private void readData(BufferedInputStream bis, ArrayList<byte[]> listBytes) throws IOException {byte[] bytes = new byte[1024];int length;while ((length = bis.read(bytes)) != -1){listBytes.add(Arrays.copyOfRange(bytes,0,length));}}
/*** 创建集合* @return ArrayList<byte[]>*/private ArrayList<byte[]> getArrayList() {return new ArrayList<>();}
/*** 优化输入流* @param inputStream 字节输入流* @return 返回缓冲字节输入流对象*/private BufferedInputStream getBufferedInputStream(InputStream inputStream) {return new BufferedInputStream(inputStream);}
/*** 获取服务端Socket的输入流* @param serverSocket 服务端的socket对象* @return 返回服务端Socket的字节输入流对象* @throws IOException 会抛出IOException*/private InputStream getInputStream(Socket serverSocket) throws IOException {return serverSocket.getInputStream();}
}
(8)问题【待解决】
虽然实现了多线程和重复接收的问题,但在测试的时候还有个问题,记录一下
文件写好之后,由于服务端的ServerSocket没有关闭,在服务端读取完数据并写出后,其状态是被java虚拟机使用的,故此文件是无法删除的,但能正常访问~
如图:
我传了三个文件
然后我去文件夹看一下状态
无法删除
但是可以正常听
关闭服务器
文件会正常