网络编程及其实现 网络编程概念什么是网络编程网络编程就是我们书写代码实现对于不同的进程之间进行网络通信网络数据的传递注意进行通信的主机是同一台主机或者不同主机都可行只要是两个不同的进程就行我们日常通过访问网页获取需要的网络资源视频图片就是使用网络编程实现的。如何实现网络编程网络编程的实质就是传递数据编写代码因此我们只要完成这两部分就能实现网络编程。传递数据我们网络通信传递数据是有“协议”的--规定了数据传输的规则这些“协议”来自于“传输层协议”普遍使用的是UDP或者TCP协议。那么我们就要简单先了解一下这两个传输层协议的特性理解我们传输数据规则的大概要求后续再详细介绍这些协议。1.UDP协议无连接不可靠传输面向数据报半双工无连接每次数据的传输直接传输不用保存通信双方信息我只要知道发给谁直接发就行不可靠传输数据在传输过程中可能会出现“丢包”数据丢失UDP不会管这些丢失的数据通信双方也不知道是否有数据丢失但是传输效率更高面向数据报数据会被构造成一个“数据报”形式才会在网络中传输半双工一个通信只能“单向传输”2.TCP协议有连接可靠传输面向字节流全双工有连接通信双方要在传输数据之前保留双方核心信息IP和端口号断开连接后删除信息可靠传输数据在传输过程中可能会出现“丢包”数据丢失TCP协议会尝试重传数据对抗丢包就算最后仍然“丢包”双方都会知道是否有数据丢失传输效率相较于较低面向字节流数据按照字节流传输类似文件IO操作更加灵活全双工一个通信能“双向传输”编写代码编写代码其实就是我们程序员在应用层编写代码调用传输层操作系统接口API从而配置好“协议”以及进行通信传输网络编程的基本概念我们知道网络编程是双方的网络通信因此我们通过一个常见的网络编程例子辅以解释我们网络编程中常见到的概念客户端请求服务的一方比如请求下载视频的我们主机服务端给请求提供服务的一方提供下载视频的资源网站请求一般是先发送信息的一方进行的操作响应收到消息后做做出回应的一方的操作发送端源主机接收端目的主机网络编程套接字Socket上面我们说到进行网络编程的进行需要“编写代码”我们编写代码本质就是在应用层使用代码调用操作系统的接口使用其中实现的传输层“协议”。这个操作系统提供的接口就是Socket通过Socket我们可以调用传输层中协议规定数据形式的实现。可以理解成Socket就像“遥控器”遥控我们的“空调”网卡进行传输数据的规范和传输我们常使用的Socket有两类流套接字使用传输层TCP协议具有TCP协议的特性数据报套接字使用传输层UDP协议具有UDP协议的特性马上我们会介绍到两类套接字实现的通信流程的工作流程。UDP数据报套接字编程API介绍DatagramSocketDatagramSocket是UDP Socket用于发送和接收UDP数据报构造方法方法签名方法说明DatagramSocket()创建一个UDP数据报套接字的Socket绑定到任意随机端口一般用于客户端DatagramSocket(int port)创建一个UDP数据报套接字的socket绑定到指定端口一般用于服务端注服务端指定端口号方便客户端找到它发送请求给它客户端随机端口号一般客户端在我们主机上端口号是由操作系统分配的不定并且服务端可以根据我们发送过去的数据报提取出我们客户端的“端口号”功能方法方法签名方法说明void receive(DatagramPacket p)从此套接字接收数据报如果没有接收到数据报该方法会阻塞等待void send(DatagramPacket p)从此套接字发送数据报包不会阻塞等待直接发送void close()关闭此数据报套接字注receive方法是“输出型函数”相当于传进去一个参数作为“容器”承载数据回来。DatagramPacketDatagramPacket是UDP socket发送和接收的数据报数据想要发送就要先封装成该形式构造方法常用的方法签名方法说明DatagramPacket(byte[] buf,int length)构造一个DatagramPacket以用来接收数据报接收的数据保存在字节数组第一个参数buf中接收指定长度第二个参数lengthDatagramPacket(byte[] buf,int offset,int length,SocketAddress address)构造一个DatagramPacket以用来发送数据报发送的数据为字节数组第一个参数buf中从0到指定长度第二个参数length。address指定目的主机的IP和端口号​功能方法方法签名方法说明InetAddress getAddress()​从接收的数据报中获取发送端主机IP地址或从发送的数据报中获取接收端主机IP地址​int getPort(从接收的数据报中获取发送端主机的端口号或从发送的数据报中获取接收端主机端口号byte[] getData()获取数据报中的数据UDP通信模型实现图示就是UDP通信模型工作的流程和需要实现的操作。代码示例Sever服务器import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class UDPSever { //基于UDP协议进行网络通信的服务器 //操纵网卡的遥控器 DatagramSocket socketnull; //构造方法 //服务器的端口号要指定这样客户端才能找到系统随机则找不到 public UDPSever(int port) throws SocketException { socketnew DatagramSocket(port); } //服务器启动 public void start() throws IOException { System.out.println(服务器已启动); //服务器的基本工作流程 while (true) { //1.接收客户端请求 //构造数据报接收 DatagramPacket datagramPacket new DatagramPacket(new byte[1024], 1024); socket.receive(datagramPacket); //假设我们传递的都是字符串将数据报解析成原始要求 String request new String(datagramPacket.getData(),0, datagramPacket.getLength()); //2.处理客户端请求 String response process(request); //根据响应内容构造数据报发送回去 //值得注意的是我们UDP无连接特性因此客户端的IP和端口从请求数据报中提取 DatagramPacket resPacket new DatagramPacket(response.getBytes(), response.getBytes().length, datagramPacket.getAddress(), datagramPacket.getPort()); //3.返回响应给客户端 socket.send(resPacket); //4.打印日志报告 System.out.printf([%s:%d]客户端:req:%s,res:%s\n, datagramPacket.getAddress(), datagramPacket.getPort() , request, response); } } //这里写一个最简单的回显服务器请求是什么就重复一遍请求当作响应发送回去 private String process(String request) { return request; } public static void main(String[] args) throws IOException { UDPSever udpSevernew UDPSever(9090); udpSever.start(); } }Client客户端import java.io.IOException; import java.net.*; import java.util.Scanner; public class UDPClient { //基于UDP协议进行网络通信的客户端 DatagramSocket datagramSocketnull; String SeverIP; int SeverPort; //构造方法 //客户端端口根据操作系统进行分配因为无所谓 public UDPClient(String ip,int port) throws SocketException { //因为UDP的无连接特性每次启动服务器都要用IP和端口号连接一次服务器也不算连接就是知道发送对象是谁 this.SeverIPip; this.SeverPortport; datagramSocketnew DatagramSocket(); } //启动客户端 public void start() throws IOException { System.out.println(客户端已启动); //客户端的工作基本流程 //1.发送请求给服务器 while (true) { System.out.println(请输入你要发送的请求-); Scanner scanner new Scanner(System.in); String request scanner.next(); if (request.equals(exists)) { return; } //根据请求构造发送的数据报 DatagramPacket reqPacket new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(SeverIP), SeverPort); datagramSocket.send(reqPacket); //2.接收服务器返回响应 //接收响应数据报 DatagramPacket resPacket new DatagramPacket(new byte[1024], 1024); datagramSocket.receive(resPacket); //3.解析响应 String response new String(resPacket.getData(),0,resPacket.getLength()); //4.展示服务器响应结果 System.out.println(服务器响应结果是: response); } } public static void main(String[] args) throws IOException { UDPClient udpClientnew UDPClient(127.0.0.1,9090); udpClient.start(); } }注1.传输的数据是封装成数据报的形式发送还是解析都要先构造数据报或者分用数据报2.由于UDP的无连接特性因此服务器传回IP与端口号需要从接收的请求数据报中去提取TCP流套接字编程API介绍ServerSocketserversocket是创建TCP服务端Socket的API构造方法方法签名方法说明ServerSocketint port创建一个服务端流套接字Socket并绑定到指定端口​功能方法方法签名方法说明Socket accept()开始监听指定端口创建时绑定的端口有客户端连接后返回一个服务端Socket对象并基于该Socket建立与客户端的连接否则阻塞等待void close()关闭此套接字这个类的主要作用就是领着“客户端的请求”来找到创建Socket文件对象进行真正的客户端服务器连接进行后续的请求响应操作SocketSocket是客户端的Socket或服务器中接收到客户端建立连接accept方法请求后返回的服务器Socket。不管是客户端的还是服务器的Socket都是双方建立连接以后保存对端信息及用来与对方收发数据的构造方法方法签名方法说明SocketString host,int port创建一个客户端流套接字Socket并与对应IP的主机上对应端口的进程建立连接功能方法方法签名方法说明InetAddress getInetAddress()返回套接字所连接的地址InputStream getInputStream()返回此套接字的输入流OutputStream getOutputStream()返回此套接字的输出流TCP通信模型实现图示为TCP流套接字的网络通信基本流程Sever服务器import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TCPSever { //作为接待客户端连接请求 ServerSocket serverSocket null; //构造方法指定端口号便于被客户端指定连接 public TCPSever(int port) throws IOException { serverSocket new ServerSocket(port); } //启动服务器 public void start() { System.out.println(服务器启动); // ExecutorService executorService Executors.newCachedThreadPool(); //服务器要一直运行处理客户端请求 while (true) { executorService.submit(() - { Socket socket null; try { socket serverSocket.accept(); processConnection(socket); } catch (IOException e) { throw new RuntimeException(e); } }); } } //真正处理连接 private void processConnection(Socket socket) { System.out.printf([%s:%d]服务器已经上线\n, socket.getInetAddress(), socket.getPort()); //开始进行真正通信 try (InputStream inputStream socket.getInputStream(); OutputStream outputStream socket.getOutputStream(); PrintWriter printWriter new PrintWriter(outputStream)) { Scanner scannernew Scanner(inputStream); //服务器的基本工作流程 while (true) { //1.接收客户端请求 if(!scanner.hasNext()){ System.out.printf([%s:%d]客户端已经下线\n,socket.getInetAddress(),socket.getPort()); break; } String requestscanner.next(); //2.处理客户端请求 String responseprocess(request); //3.返回服务器的响应 printWriter.println(response); printWriter.flush(); //4.打印日志 System.out.printf([%s:%d]客户端req%s,res%s\n,socket.getInetAddress(),socket.getPort(),request,response); } } catch (IOException e) { throw new RuntimeException(e); } } private String process(String s) { return s; } public static void main(String[] args) throws IOException { TCPSever tcpSevernew TCPSever(9090); tcpSever.start(); } }Client客户端import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner; public class TCPClient { Socket socketnull; //存储服务器的IP和端口号 String SeverIP; int SeverPort; //构造方法 public TCPClient(String IP,int port) throws IOException { this.SeverIP IP; this.SeverPort port; socket new Socket(SeverIP, SeverPort); } //启动客户端 public void start() { System.out.println(客户端启动); //客户端的工作基本流程 try(InputStream inputStreamsocket.getInputStream(); OutputStream outputStreamsocket.getOutputStream(); PrintWriter printWriternew PrintWriter(outputStream)) { Scanner reqnew Scanner(System.in); while (true) { //1.用户输入请求 System.out.println(请输入你的请求-); String requesetreq.next(); if(requeset.equals(exists)){ break; } //2.发送用户请求给服务器 printWriter.println(requeset); printWriter.flush(); //3.接收服务器的响应信息 Scanner scannernew Scanner(inputStream); if(!scanner.hasNext()){ break; } String responsescanner.next(); //4.展示响应信息结果 System.out.println(response); } } catch (IOException e) { throw new RuntimeException(e); } } public static void main(String[] args) throws IOException { TCPClient tcpClientnew TCPClient(127.0.0.1,9090); tcpClient.start(); } }注1.由于服务器要应对多个客户端的连接因此这里采用“线程池”来处理“accept的连接客户端和服务器”操作2.服务器中的ServiceSocket相当于“接客前台”的作用领着客户端到真正“负责连接”的人Socket相当于是“负责连接”的人建立连接后由Socket处理后续的请求和响应操作相当于前台接过来然后socket进行服务长短连接我们知道TCP的特性有连接因此客户端和服务器传输数据连接时间的长短就决定了此次连接是长连接还是短连接。长连接进行多次传输请求和接收响应双方一直保持着连接状态可以进行多次通信短连接进行一次传输请求和接收响应之后就断开连接只进行一次通信关于长短连接也有各自的优劣之处以及使用场合短连接适合于客户端使用一般客户端拿到自己需要的数据后就会断开了客户端与服务器的连接次数少的情况请求频次少比如查阅网页资料长连接适合于需要一直进行连接传输的场景网络聊天和打游戏短连接连接中断频次高占用资源效率较低长连接只进行一次连接断开比较节省资源效率较高同时关于提升我们TCP服务器的效率还有“IO多路复用”--一个线程处理多个socket同一时刻连接的客户端多但不是每个客户端都在传输数据因此一个线程处理多个可以尽可能节省资源提升效率但是IO多路复用在Java标准库中封装的过于麻烦大家有兴趣可以去了解。