Java网络编程之Socket网络通信

    技术2022-09-01  111

    package cn.webSocket.service; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; /** * * 今天整理以下网络编程的基础Demo; * * 网络编程: * * 1. 软件架构: * BS: Browser/Server(浏览器/服务器) * CS: Client/Server(客户端/服务器) * * 2. 网络通信三要素: * * 2.1. 网络通信协议: * 计算机必须遵守的协议,其中对数据的传输格式,传输速率,传输步骤等做了相应的规范,只有遵守相应的规则计算机之间才能进行准确无误的交互; * (TCP/IP协议:传输控制协议/因特网互联协议(Transmission Control Protocol/Internet Protocol * TCP协议是面向连接的通信协议:意思是说在数据传递前,会首先建立发送端和接收端之间的逻辑连接,然后进行数据传输; * 目前TCP能够极大程度上提供两台计算机之间可靠无差别的数据传输;其主要可靠性体现在数据交互前的准备阶段: * 第一阶段:客户端向服务端发送连接请求,等待服务器确认; * 第二阶段:服务器端回应客户端,收到连接请求; * 第三阶段:客户端再次向服务端发送确认信息,服务器收到,确认连接成功; * 以上的三次连接信息交互保证了数据传输的可靠性;完成上面的连接验证后,就是正式的数据交互;由于TCP协议在数据传输上的安全性能显著, * 目前应用的场景较为广泛,如文件下载,网页浏览等... * * UDP协议:用户数据报协议(User Datagram Protocol);UDP协议是面向无连接的一种协议,其特点是:数据传输时,无需事先建立与服务器的通信连接, * 不管服务器是否启动成功,直接将数据,数据源和目的地封装进入一个限定64K以内大小的数据包中然后发送;这种协议其实是一种不安全的协议; * 它存在的缺点就是它的不可靠性.虽然因没有了预连接使其传输速度相对加快,但是容易丢失数据.所以在日常的应用中主要是应用在视频会议或语音聊天等地方; * * ); * * 协议规范了不同设备如何连入因特网,以及相互之间如何数据传递的规则; * 其内部包含了一系列用户数据通讯处理的各种协议,并且采用的4层的分层模型,每一层都会呼叫下一层提供的协议来完成自己的需求;) * * 2.2. IP地址:用来记录每一台设备的唯一标识; * 起源:IP地址早期只有IPv4,是一个32位的二进制数,通常被分为4个字节,以a.b.c.d的形式组成;例如192.168.31.1; * 其中的a,b,c,d都是0~255之间的十进制整数,大约最多为42亿个;在2011年2月的数据统计中,IPv4的IP已全数分配完; * 后来:为了扩大地址空间,重新定义了IPv6的地址空间,采用128位地址长度,每16个字节一组,分成8组16进制数来表示; * 如:ABCD:EF01:2345:6789:ABCD:EF01:2345:6789;后来的统计成IPv6的方式分配IP可以为地球上的每一粒沙子编著一个地址; * * 2.3. 端口号 * 网络通信本质上是不同应用程序间的两个进程之间的通信;为了区分计算机中各自的进程,就会用到端口来区分; * IP标注网络中不同的设备,而端口号则是标示设备中不同进程; * 端口号:使用两个字节表示的整数,取值范围在0~65535之间;其中0~1023的端口大都会被知名的网络服务和应用占取,普通的应用程序则可以从 * 1024之后取.如果端口取值已被其他进程占据,则新的程序将无法启动,除非解除对应的端口占用; * * 也就是说:通过网络协议+IP地址+端口号,就可以区别网络中不同设备,进程的交互; * * 3. TCP通讯规则: * 3.1. 服务端程序需要预先启动来等待客户端程序的请求; * 3.2. 客户端主动连接成功服务器端方可进行通讯,而服务器端不可以主动连接客户端; * 3.3. java.net包中提供了两种常见协议的支持(TCP,UDP)用来TCP通讯,里面提供了一些低层次的通信细节;其中会用到两个主要的类java.net.Socket|java.netServiceSocket; * java.net.Socket:客户端(封装客户端套接字); * java.netServiceSocket:服务端; * * 好,接下来就开始模拟一下关于Java中Socket通讯的Demo示例; * * 在这里说明一下,起始的时候没有搞明白Socket到底是个什么东西,既然通讯需要客户端和服务器,那是不是我也需要拥有一个服务器和本地的开发机来配合; * 才能够完成我的Socket通讯测试.直到后来经过多番研习后才明白其中的奥义.希望能够通过下面的Demo来清晰的展现Socket通讯面纱后的真容; * * 整体的还是和以往一样,首先创建自己的测试类: * 但是这里也说明一下,因为我们是客户端和服务器的交互,所以必定有两个独立的程序来发送各自的请求与响应,那么既然是两个独立的程序,而且各自还有自己 * 独立的信息需要请求和发送,那么各自必定需要有一个类似main函数的程序执行入口来发起自己的数据; * * 而且Socket通讯的核心也就是网络变成的核心:通讯协议+IP地址+端口号; * * Socket和ServiceSocket是两个终端各自封装的底层通讯类,故而必然是两个执行的入口; * * 大意如上,或有不清晰之处,多加思量...抱拳! * * 好 下面的类为服务器端模拟测试类; * @author TANJIYUAN * */ public class Service { /** * 程序入口|主函数; * * 在这里阐述一下大致的通信导向: * * 1. 服务端 启动,创建ServerSocket对象,等待连接; * 2. 客户端 启动,创建Socket对象,请求连接; * 3. 服务端 接收连接,通过accept()方法监听获取客户端Socket对象; * 4. 客户端 通过Socket对象获取对应的输出流,向服务端发送数据; * 5. 服务端 通过获取的客户端Socket对象获取对应的输入流来读取客户端通过输出流发送的数据; * 6. 服务端 接受到数据处理后通过获取到的客户端Socket对象获取对应的输出流,来向客户端回写数据; * 7. 客户端 通过Socket对象获取输入流来读取服务端回写的数据内容; * 8. 资源释放;连接断开; * * @param args */ public static void main(String[] args) { try { /** * 打印流 * 通过打印流改变打印数据的输出位置; * ------------------------------------------------------------------------- * 这里说明一下: * 如果直接在控制台答应,无法全量看到Service服务器端和Client客户端的打印数据; * 所以引入以一个打印流,将数据内容调转一个自定义地址进行数据信息打印; * 此处初始化一个数据打印地址; */ String path = "D://Service.txt"; // 初始化一个打印流; 控制编码; PrintStream ps = new PrintStream(path, "UTF-8"); // 改变打印流的位置,指定自定义打印流; System.setOut(ps); // ------------------------------------------------------------------------- // 实例化服务器端Socket对象;指定服务器程序的进程端口号(绑定端口); ServerSocket server_socket = new ServerSocket(65534); System.out.println("服务端启动... Weit Conn..."); /** * 通过accept()方法获取客户端Socket对象; * accept()方法会一直处于阻塞状态实时监听,直到获取到Socket对象为止; */ Socket accept = server_socket.accept(); // 获取客户端Socket对象的输出流数据,得到一个数据输入流对象; InputStream inputStream = accept.getInputStream(); // 封装一个自定义缓存数组; byte [] bytArr = new byte[8*1024]; // 读取客户端Socket对象的流数据;(输出流转输入流数据) int index = inputStream.read(bytArr,0,bytArr.length); // 输出打印数据; System.out.print(new String(bytArr,0,index)); // 获取客户端Socket对象的输出流对象; OutputStream outputStream = accept.getOutputStream(); // 向输出流装在数据; outputStream.write("En En Welcome Client......I'm Service".getBytes()); // 资源释放; outputStream.close(); ps.close(); inputStream.close(); accept.close(); server_socket.close(); } catch (IOException e) { e.printStackTrace(); } } } package cn.webSocket.client; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; /** * 以下是客户端模拟测试类; * * 响应的引文可以在服务器端测试类上的注释中进行查看... * * @author TANJIYUAN * */ public class Client { /** * 程序的入口|主函数; * @param args */ public static void main(String[] args) { try { /** * 打印流 * 通过打印流改变打印数据的输出位置; * ------------------------------------------------------------------------- * 这里说明一下: * 如果直接在控制台答应,无法全量看到Service服务器端和Client客户端的打印数据; * 所以引入以一个打印流,将数据内容调转一个自定义地址进行数据信息打印; * 此处初始化一个数据打印地址; */ String path = "D://Client.txt"; // 初始化一个打印流; 控制编码; PrintStream ps = new PrintStream(path, "UTF-8"); // 改变打印流的位置,指定自定义打印流; System.setOut(ps); // ------------------------------------------------------------------------- /** * 初始化客户端Socket对象;指定请求IP以及端口号; * 需要注意:端口号是使用两个字节表示的整数,取值范围在0~65535之间; */ Socket socket = new Socket("127.0.0.1",65534); System.out.println("客户端启动... Will Conn..."); // 获取客户端Socket对象的输出流; OutputStream outputStream = socket.getOutputStream(); // 装在流数据; outputStream.write("Hello Server: I'm Client Client Client...".getBytes()); // 获取Socket对象的流数据;(读取服务器端请求到的输出流数据) InputStream inputStream = socket.getInputStream(); // 封装一个缓存数组; byte [] charArr = new byte [8*1024]; // 读取流数据; int index = inputStream.read(charArr); // 输出打印数据; System.out.print(new String(charArr,0,index)); // 资源释放; outputStream.close(); inputStream.close(); ps.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } package cn.webSocket.service; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; /** * 2.0: 服务端 * * WebSocket数据文件Demo; * * 下面走一个小的数据数据文件交互Demo; * * 同上面的样例规则一样,我们分服务器端和客户端; * * 那么首先创建一个服务器端的测试类"Service"; * @author TANJIYUAN * */ public class Service { /** * 程序入口|主函数; * * @param args */ public static void main(String[] args) { try { // 实例化指定端口的服务器端Socket对象; ServerSocket serverSocket = new ServerSocket(65530); /** * PrintStream; * * 通过打印流改变数据输出方向; * ------------------------------------------------------------------ * 首先初始化一个输出地址; */ String path = "D://Server_Upload.txt"; // 实例化打印流,并指定对应的编码和输出地址; PrintStream ps = new PrintStream(path, "UTF-8"); // 指定自己的打印流; System.setOut(ps); // ------------------------------------------------------------------ // 数据打印; System.out.println("Server start Ok ..."); // 通过accept()方法实时监听获取客户端Socket对象; Socket clientSocket = serverSocket.accept(); // 实例化一个缓冲byte数组; byte [] byteArr = new byte [8*1024]; /** * getInputStream():通过获取到的客户端Socket对象获取客户端Socket对象的输入流; * * 通过指定客户端输入流实例化到一个缓冲输入流中; */ BufferedInputStream bis = new BufferedInputStream(clientSocket.getInputStream()); // 初始化一个数据输出位置; String WritePath = "D://"+System.currentTimeMillis()+".png"; // 实例化一个缓冲输出流; BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(WritePath)); // 预定义一个记录缓冲流读取源数据的标位; int length = 0; // 缓冲流循环读取源数据; while((length = bis.read(byteArr)) != -1) { // 输出流写入数据; bos.write(byteArr, 0, length); } // 提交一次之前写入的数据; bos.flush(); // 再次写入数据; bos.write("Data upload Over! ".getBytes()); // 资源释放; bos.close(); bis.close(); clientSocket.close(); ps.close(); serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } package cn.webSocket.client; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; /** * * 2.0: 客户端; * * 下面是客户端的模拟测试类; * * @author TANJIYUAN * */ public class Client { /** * 程序的入口|主函数; * @param args */ public static void main(String[] args) { try { /** * PrintStream; * * 通过打印流改变数据输出方向; * ------------------------------------------- * 初始化一个输出地址; */ String logPath = "D://Client.txt"; // 实例化打印流,并指定对应的编码和输出地址; PrintStream ps = new PrintStream(logPath,"UTF-8"); // 指定自己定义的打印流; System.setOut(ps); // ------------------------------------------- // 实例化客户端Socket对象,指定请求的服务器IP和对应服务器上的进程端口; Socket socket = new Socket("127.0.0.1",65530); // 数据打印; System.out.println("Socket start Over! "); // 初始化数据源; String path = "D://30139986c0adc88a76de14b375e0531.png"; // 实例化输入流; BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path)); // 实例化一个byte缓冲数组; byte [] charArr = new byte [8*1024]; // 通过客户端Socket对象获取输出流; OutputStream outputStream = socket.getOutputStream(); // 预定义一个记录源数据批量读取的最后位标; int length = 0; // 循环读取源数据; while((length = bis.read(charArr)) != -1) { // 数据写入; outputStream.write(charArr, 0, length); } // 数据提交; outputStream.flush(); // 再次写入; outputStream.write("Socket send Over! ... ".getBytes()); // 资源释放; outputStream.close(); bis.close(); socket.close(); ps.close(); } catch (IOException e) { e.printStackTrace(); } } /** * * @param bytes * @return */ public static byte[] getBytes(char[] chars) { Charset cs = Charset.forName("UTF-8"); CharBuffer cb = CharBuffer.allocate(chars.length); cb.put(chars); cb.flip(); ByteBuffer bb = cs.encode(cb); return bb.array(); } /** * * @param bytes[] * @return */ public static char[] getChars(byte[] bytes) { Charset charSet = Charset.forName("UTF-8"); ByteBuffer bb = ByteBuffer.allocate(bytes.length); bb.put(bytes); bb.flip(); CharBuffer cb = charSet.decode(bb); return cb.array(); } }

     

    Processed: 0.011, SQL: 9