1 Socket
🐰 基本介绍:
套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准;
通信的两端都要有Socket,是两台机器间通信的端点;
网络通信其实就是Socket间的通信;
Socket允许程序把网络连接当作一个流,数据可以在两个Socket间通过IO传输;
一般主动发起通信的应用程序属于客户端,等待通信请求的为服务端
Socket该如何理解?
2 TCP网络通信编程
2.1 TCP字节流编程
因资源有限,笔者操作均在同一台计算机完成
题目摘自:韩顺平Java_TCP字节流编程1、2
2.1.1 案例:客户端发送数据,服务端接收并显示
编写一个服务器端,和一个客户端。服务端在 9999端口监听,客户端连接到服务端,并发送"hello,server",然后退出。服务端接收到客户端发送的信息后,输出该信息,并退出。
服务端代码:
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; /** * @author 兴趣使然黄小黄 * @version 1.0 * 服务端 */ public class SocketTCPServer1 { public static void main(String[] args) throws IOException { //1.在本机的9999端口监听,等待连接,要求本机没有其他服务在监听9999 ServerSocket serverSocket = new ServerSocket(9999); System.out.println("服务端,在9999端口监听,等待连接.."); //2.当没有客户端连接9999端口时,程序会阻塞,等待连接 //如果有客户端连接,则会返回Socket对象,程序继续 Socket socket = serverSocket.accept(); System.out.println("服务端 socket=" + socket.getClass()); //3.通过socket.getInputStream() 读取客户端写入数据通道的数据,并显示 InputStream inputStream = socket.getInputStream(); //4.IO读取 byte[] buf = new byte[1024]; int readLen = 0; while ((readLen = inputStream.read(buf)) != -1){ System.out.println(new String(buf, 0, readLen)); } //5.关闭资源 inputStream.close(); socket.close(); serverSocket.close(); System.out.println("服务端退出......"); } }
客户端代码:
import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; /** * @author 兴趣使然黄小黄 * @version 1.0 * 客户端,发送"hello,server"给服务端 */ public class SockerTCPClient1 { public static void main(String[] args) throws IOException { //1.连接服务器(ip 端口) //连接本机的9999端口,如果连接成功,返回Socket对象 Socket socket = new Socket(InetAddress.getLocalHost(), 9999); System.out.println("客户端 socket返回=" + socket.getClass()); //2.连接上后,生成Socket,通过输出流,写入数据到 数据通道 OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello,server".getBytes()); //3.关闭流对象 outputStream.close(); socket.close(); System.out.println("客户端退出......"); } }
结果如下
2.1.2 案例进阶:双向通信
1.编写一个服务端,和一个客户端;
2.服务端在9999端口监听;
3.客户端连接到服务端,发送"hello,server",并接收服务端回发的"hello,cilent",再退出;
4.服务器端接收到客户端发送的信息,输出,并发送"hello, cilent",再退出
该案例为案例1的进阶,可以直接在上述代码中进行修改。
服务端代码:
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /** * @author 兴趣使然黄小黄 * @version 1.0 * 服务端 */ public class SocketTCPServer1 { public static void main(String[] args) throws IOException { //1.在本机的9999端口监听,等待连接,要求本机没有其他服务在监听9999 ServerSocket serverSocket = new ServerSocket(9999); System.out.println("服务端,在9999端口监听,等待连接.."); //2.当没有客户端连接9999端口时,程序会阻塞,等待连接 //如果有客户端连接,则会返回Socket对象,程序继续 Socket socket = serverSocket.accept(); System.out.println("服务端 socket=" + socket.getClass()); //3.通过socket.getInputStream() 读取客户端写入数据通道的数据,并显示 InputStream inputStream = socket.getInputStream(); //4.IO读取 byte[] buf = new byte[1024]; int readLen = 0; while ((readLen = inputStream.read(buf)) != -1){ System.out.println(new String(buf, 0, readLen)); } socket.shutdownInput(); //5.写入数据到数据通道 OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello, client".getBytes()); socket.shutdownOutput(); //6.关闭资源 inputStream.close(); outputStream.close(); socket.close(); serverSocket.close(); System.out.println("服务端退出......"); } }
客户端代码
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; /** * @author 兴趣使然黄小黄 * @version 1.0 * 客户端,发送"hello,server"给服务端 */ public class SockerTCPClient1 { public static void main(String[] args) throws IOException { //1.连接服务器(ip 端口) //连接本机的9999端口,如果连接成功,返回Socket对象 Socket socket = new Socket(InetAddress.getLocalHost(), 9999); System.out.println("客户端 socket返回=" + socket.getClass()); //2.连接上后,生成Socket,通过输出流,写入数据到 数据通道 OutputStream outputStream = socket.getOutputStream(); outputStream.write("hello,server".getBytes()); socket.shutdownOutput(); //3.读取服务端数据 InputStream inputStream = socket.getInputStream(); byte[] buf = new byte[1024]; int readLine = 0; while ((readLine = inputStream.read(buf)) != -1){ System.out.println(new String(buf, 0, readLine)); } //4.关闭流对象 inputStream.close(); outputStream.close(); socket.close(); System.out.println("客户端退出......"); } }
注意使用 socket.shutdownOutput() 设置结束标记
类比一下生活中两个人聊天,对方如果不停止讲话,或者告诉你说完了,你始终无法确定对方是否已经说完了话,因此,你会一直处于听的状态(如果礼貌的前提下!)
网络通信也一样,负责监听的服务端,并不知道客户端是否已经说完了话,因此,客户端在发送消息完毕时,应该给服务端发送一个结束标记!只有这样,服务端才知道该何时停止读取(Input)状态,进行下一步操作。
结果:
2.2 TCP字符流编程
需求同案例进阶:双向通信,不同点是,这次我们尝试使用字符流来实现!
服务端代码:
import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * @author 兴趣使然黄小黄 * @version 1.0 * 服务端 */ public class SocketTCPServer1 { public static void main(String[] args) throws IOException { //1.在本机的9999端口监听,等待连接,要求本机没有其他服务在监听9999 ServerSocket serverSocket = new ServerSocket(9999); System.out.println("服务端,在9999端口监听,等待连接.."); //2.当没有客户端连接9999端口时,程序会阻塞,等待连接 //如果有客户端连接,则会返回Socket对象,程序继续 Socket socket = serverSocket.accept(); System.out.println("服务端 socket=" + socket.getClass()); //3.通过socket.getInputStream() 读取客户端写入数据通道的数据,并显示 InputStream inputStream = socket.getInputStream(); //4.IO读取,使用字符流 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String s = bufferedReader.readLine(); System.out.println(s); //5.写入数据到数据通道 OutputStream outputStream = socket.getOutputStream(); BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write("hello,client"); bufferedWriter.newLine(); bufferedWriter.flush(); //6.关闭资源 bufferedReader.close(); bufferedWriter.close(); socket.close(); serverSocket.close(); System.out.println("服务端退出......"); } }
客户端代码
import java.io.*; import java.net.InetAddress; import java.net.Socket; /** * @author 兴趣使然黄小黄 * @version 1.0 * 客户端,发送"hello,server"给服务端 */ public class SockerTCPClient1 { public static void main(String[] args) throws IOException { //1.连接服务器(ip 端口) //连接本机的9999端口,如果连接成功,返回Socket对象 Socket socket = new Socket(InetAddress.getLocalHost(), 9999); System.out.println("客户端 socket返回=" + socket.getClass()); //2.连接上后,生成Socket,通过输出流,写入数据到 数据通道 OutputStream outputStream = socket.getOutputStream(); //字符流需要包装和转化 BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream)); bufferedWriter.write("hello,server"); //插入一个换行符表示写入内容结束 bufferedWriter.newLine(); //需要刷新 bufferedWriter.flush(); //3.读取服务端数据 InputStream inputStream = socket.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); String s = bufferedReader.readLine(); System.out.println(s); //4.关闭流对象 bufferedReader.close(); bufferedWriter.close(); socket.close(); System.out.println("客户端退出......"); } }
2.3 网络上传文件
需求如下:将客户端的图片,通过网络上传到服务器,服务器回复消息
1.编写一个服务端,一个客户端;
2.服务端在8888端口监听;
3.客户端连接到服务端,发送一张图片;
4.服务端接收到图片后将客户端发送的图片保存到src目录,并发送“收到图片”后退出;
5.客户端在接收到服务端“收到图片”后退出;
6.要求使用StreamUtils,java(自己封装的)
🐱 思路分析:
先将磁盘上的文件读入客户端,存储在字节数组中(图片是二进制);
将字节数组(文件数据)通过Socket获取的输出流输出到数据通道上;
在服务端,需要通过Socket获取输入流存储到字节数组中;
然后,在通过输出流将字节数组的数据保存到磁盘上;
通过Socket获得输出流,将“收到图片”打入数据通道,结束;
客户端通过输入流,读取数据,接收到“收到图片”提示,显示信息,结束。
StreamUtils.java
import java.io.ByteArrayOutputStream; import java.io.InputStream; /** * @author 兴趣使然黄小黄 * @version 1.0 */ public class StreamUtils { /** * 将输入流转化成 byte[],即可以将文件读入到字节数组 * @param is * @return * @throws Exception */ public static byte[] streamToByteArray(InputStream is) throws Exception{ ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] bytes = new byte[1024]; int len = 0; while ((len=is.read(bytes))!=-1){ bos.write(bytes, 0, len); } byte[] array = bos.toByteArray(); bos.close(); return array; } }
服务端
import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * @author 兴趣使然黄小黄 * @version 1.0 * 服务端 */ public class TCPFileUploadServer { public static void main(String[] args) throws Exception { //1.服务端在本机8888端口监听 ServerSocket serverSocket = new ServerSocket(8888); System.out.println("服务端在8888端口监听"); //2.等待连接 Socket socket = serverSocket.accept(); //3.读取客户端的数据 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); byte[] bytes = StreamUtils.streamToByteArray(bis); //4.将数组写入到指定路径 String filePath = "D:\\Ideaproject2021\\JavaSE\\src\\upload\\zzf2.jpg"; BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath)); bos.write(bytes); bos.close(); //5.向客户端发送,收到图片 BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); writer.write("收到图片"); writer.newLine(); writer.flush(); socket.shutdownOutput(); //关闭其他资源 writer.close(); bis.close(); socket.close(); serverSocket.close(); } }
客户端
import java.io.*; import java.net.InetAddress; import java.net.Socket; /** * @author 兴趣使然黄小黄 * @version 1.0 * 客户端 */ public class TCPFileUploadClient { public static void main(String[] args) throws Exception { //1.客户端连接服务端,得到socket对象 Socket socket = new Socket(InetAddress.getLocalHost(), 8888); //2.创建读取磁盘文件的输入流 String filePath = "C:\\Users\\26510\\Desktop\\csdn\\zzf.jpg"; BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath)); //3.把文件写入到字节数组中 byte[] bytes = StreamUtils.streamToByteArray(bis); //4.将字节数组的数据发送到服务端 BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); bos.write(bytes); bis.close(); socket.shutdownOutput(); //5.获取信息 BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String s = reader.readLine(); System.out.println(s); //关闭流 reader.close(); bos.close(); socket.close(); } }
运行后,图片成功上传到服务端