
在我们初识完网络知识后我们了解了关于TCP/IP五层模型分别是应用层传输层网络层数据链路层物理层这篇文章我们主要了解关于传输层中相关的俩种协议UDP TCP1.UDP和TCP的特点2.UDP网络编程3.TCP网络编程在传输层中传输层为应用层提供了编程接口即一组类或者方法给我们使用 ---- socket提供了俩组socket分别是UDP和TCPUDP和TCP的特点传输层涉及到了俩种最主要的协议UDP协议和TCP协议UDP无连接不可靠传输面向数据报全双工TCP有连接可靠传输面向字节流全双工有连接 / 无连接连接就是让俩台主机能够互相的进行通信互相保存了对方的核心信息IP和端口号断开连接就是删除对方的核心信息TCP在正式通信之前就需要保存好双方的关键信息通信完毕之后再进行删除UDP在正式通信之前就不需要可靠传输 / 不可靠传输我们在进行网络通信的过程往往有时候会出现丢包的现象正常现象TCP是可靠传输他就是在对抗丢包现象的出现尽可能的将数据包传输给对方但是有时候还是会出现丢包的现象无法达到100%的传输但也可以知道对方是否接收到了数据包UDP则不考虑是否丢不丢包的情况爱咋咋地吧~~~~这样传输效率就会比较高而TCP传输效率就会比较低毕竟他需要要关注丢包的情况面向字节流 / 面向数据报面向字节流与读文件类似读写的方式非常的灵活可以按照规定的字节分多次进行读取面向数据报每次读和写要以一个UDP数据报为单位进行全双工 / 半双工全双工一个通信可以进行双向传输半双工一个通信只能单向传输UDP网络编程其中DatagramPacket就是UDP数据传输的基本单位1.实现一个回显的UDP客户端和服务器当客户端发送一串字符串服务器应该原封不动的返回然后输出在显示器上 ---- 回显服务器端Codepackage UDP; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; import java.nio.charset.StandardCharsets; //服务端 public class UdpServer { private DatagramSocket socket null;//先不初始化 //构造方法 --- port就是端口号 public UdpServer(int port) throws SocketException { socket new DatagramSocket(port) ; } //启动服务器 public void start() throws IOException { System.out.println(Server Start); //因为服务器是7*24h不断工作等待客户端发来的请求~~~所以使用死循环 while(true) { //1.接收请求并解析 //接收请求,需要先申请一个DatagramPacket内存空间 DatagramPacket requestPacket new DatagramPacket(new byte[1024],1024);//DatagramPacket是以数据包为单位进行读取~~~ //receive的构造参数时DatagramPacket所以需要传给他Datagrampacket socket.receive(requestPacket);//客户端请求接收 //因为客户端发送的请求中是二进制数组我们需要将DatagramPacket转换为字符串 ---- 获取二进制数据并从下标0到length转换为字符串 String request new String(requestPacket.getData(),0,requestPacket.getLength()); //2.根据请求计算响应因为我们只需要在显示器打印东西所以不需要计算 //根据请求计算相应这里我们实现的是回显操作客户端发送什么服务器就返回什么即可~~~~ String response process(request); //3.将响应重新返回给客户端 //需要DatagramPacket传字节数组长度getSocketAddress(会返回ip和port)相应也是按照DatagramPocket数据包为单位进行发送~~~~ DatagramPacket responsePacket new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress()); //send构造参数也是DatagramPacket,所以需要我们将字符串转换为DatagramPacket socket.send(responsePacket); //4.打印(打印ip端口号请求内容相应内容) System.out.printf([%s : %d] request : %s, resopnse : %s\n, requestPacket.getAddress().toString(), requestPacket.getPort(), request, response); } } public String process(String request) { return request;//因为这里是回显操作客户端发送什么服务器返回什么即可不需要计算的操作 } public static void main(String[] args) throws IOException { UdpServer server new UdpServer(10086); server.start(); } }客户端Codepackage UDP; import java.io.IOException; import java.net.*; import java.util.Scanner; //客户端 public class UdpClient { private DatagramSocket socket null; private String serverIp; private int serverPort; public UdpClient(String serverIp, int serverPort) throws SocketException { socket new DatagramSocket(); this.serverIp serverIp; this.serverPort serverPort; } public void start() throws IOException { System.out.println(client start!!!); Scanner scanner new Scanner(System.in); while(true) { //1.让用户从控制台输入字符串 System.out.print(请输入一串字符串); String request scanner.next(); if(request.equals(exit)) { break; } //2.将用户输入的字符串转换为UDP数据包类型因为send的类型是数据包类型~~~ DatagramPacket requestPacket new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(this.serverIp), this.serverPort); socket.send(requestPacket);//将请求发送给服务器 //3.获取服务器的响应 --- receive构造也是DatagramPacket所以也需要构造一个DatagramPacket注意这里获取的是字节数组 DatagramPacket responsePacket new DatagramPacket(new byte[1024], 1024); //接收服务器响应 socket.receive(responsePacket); //转换为字符数组因为服务器发送的响应是一个二进制字节数组我们需要转换为String然后打印的显示器上 String response new String(responsePacket.getData(), 0, responsePacket.getLength()); //4.显示响应 System.out.println(响应数据为 response); } } public static void main(String[] args) throws IOException { UdpClient client new UdpClient(127.0.0.1, 10086);//告诉客户端服务器的ip和port client.start(); } }这里有几点要注意1.我们让用户输入的是字符串但是DatagramPacket参数是二进制所以需要将字符串转换为二进制2.服务器返回的是二进制客户端需要接收并且转换为字符串形式才能显示在显示器上否则就是乱码3.服务器应该设置为While死循环因为服务器是7*24小时服务的4.在编写服务器的时候需要我们程序员指定的给定一个端口号而客户端是不需要给定端口号的是一个无参数的构造方法操作系统会自动分配一个空闲的端口号5.服务器需要接收客户端的请求并且解析 --- 根据请求计算响应 ---- 将响应返回给客户端6.客户端需要从控制台读取用户输入的字符串 --- 构造请求发送给服务器 --- 读取服务器返回的响应 --- 显示响应结果到显示器上2.实现一个查字典的UDP客户端和服务器客户端输入一个中文单词服务器要求返回这个中文对应的英文单词然后客户端接收并且显示到显示器上这里就会使用Map保存好中文和对应的英文单词~~~服务器Codepackage UDP; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; import java.util.HashMap; import java.util.Map; //翻译服务器 public class UdpDictionaryServer { private DatagramSocket socket null; //创建词典 public MapString, String map new HashMap(); public UdpDictionaryServer (int port) throws SocketException { socket new DatagramSocket(port); map.put(苹果, apple); map.put(香蕉, banana); map.put(猪, pig); map.put(鸭子, duck); map.put(老虎, tiger); map.put(鱼, fish); map.put(你好, hello); } public void start() throws IOException { System.out.println(Server Start~~~); while(true) { //1.接收请求并解析 --- 需要使用receive接收receive的构造方法是DatagramPakcet DatagramPacket requestPacket new DatagramPacket(new byte[1024], 1024); socket.receive(requestPacket); //receive接收到的是一个二进制字节数组需要转换为字符串 String request new String(requestPacket.getData(), 0, requestPacket.getLength()); //2.根据请求计算响应 String response process(request); //3.计算好的响应返回给客户端 -- 需要使用send但是注意send构造也是需要DatagramPacket的~~~,这里参数是获取字节数组字节数组长度以及接收到请求的IP和Port DatagramPacket responsePacket new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress()); socket.send(responsePacket); //4.显示打印 System.out.printf([%s : %d] request : %s, resopnse : %s\n, requestPacket.getAddress().toString(), requestPacket.getPort(), request, response); } } public String process(String request) { return map.getOrDefault(request, 很抱歉没查找到此单词~~~); } public static void main(String[] args) throws IOException { UdpDictionaryServer server new UdpDictionaryServer(10086); server.start(); } }客户端Codepackage UDP; import java.io.IOException; import java.net.*; import java.util.Scanner; //翻译客户端 public class UdpDictionaryClient { DatagramSocket socket null; private String ip; private int port; public UdpDictionaryClient(String ip, int port) throws SocketException { socket new DatagramSocket(); this.ip ip; this.port port; } public void start() throws IOException { System.out.println(Client Start~~~); Scanner scanner new Scanner(System.in); while(true) { System.out.print(请输入中文); String dictionary scanner.next(); if(dictionary.equals(exit)) { break; } //1.发送请求给服务器 --- 需要使用到send构造方法是DatagramPacket DatagramPacket request new DatagramPacket(dictionary.getBytes(), dictionary.getBytes().length, InetAddress.getByName(this.ip), this.port); socket.send(request); //2.接收服务器的响应 --- 需要使用到receive,构造方法是DatagramPacket DatagramPacket responsePacket new DatagramPacket(new byte[1024], 1024); socket.receive(responsePacket); //因为服务器响应回来的是一个二进制字节数组我们需要转换为字符串 String response new String(responsePacket.getData(), 0, responsePacket.getLength()); System.out.println(翻译后结果为 response); } } public static void main(String[] args) throws IOException { UdpDictionaryClient client new UdpDictionaryClient(127.0.0.1,10086); client.start(); } }TCP网络编程TCP提供了俩个类ServerSocket给服务器使用 和 Socket即给客户端也给服务器使用ServerSocket的任务是负责将连接到服务器的客户端进行统一处理Socket的任务是针对每一个客户端提供具体的服务读写数据1.实现回显的TCP客户端和服务器服务器Codepackage TCP; 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; public class TcpServer { //创建ServerSocket对象 private ServerSocket serverSocket null; public TcpServer(int port) throws IOException { serverSocket new ServerSocket(port); } public void start() throws IOException { System.out.println(Server Start~~~); while(true) { //需要接收客户端的连接 --- 需要使用到accept如果接收到了会返回否则就一直阻塞等待 Socket socket serverSocket.accept();//这里的socket会保存客户端的ip和port每一次连接客户端就会创建一个socket连接但是很快就执行完所以执行完后需要关闭连接~~ //处理连接过程但是服务器只有一个当我们启动多个客户端一个服务器无法接收多个客户端的请求这时就需要创建多线程让多线程去实现多个服务器才能接收不同客户端的请求 Thread t new Thread(() - { try { processConnection(socket); } catch (IOException e) { throw new RuntimeException(e); } }); t.start(); } } public void processConnection(Socket socket) throws IOException { //一次连接的过程中客户端和服务器可能会传输多组数据 System.out.printf(【%s : %d】 客户端上线\n, socket.getInetAddress(), socket.getPort()); //因为TCP使用的是字节流所以我们可以使用InputStram---读OutputStream---写 try (InputStream inputStream socket.getInputStream(); OutputStream outputStream socket.getOutputStream()) { Scanner scanner new Scanner(inputStream); PrintWriter write new PrintWriter(outputStream); while(true) { //1.处理多次请求这里一次循环就处理一个请求 if(!scanner.hasNext()) { System.out.printf(【%s : %d】 客户端下线\n,socket.getInetAddress(), socket.getPort()); break; } String request scanner.next(); //2.根据请求计算响应 --- 这里实现的是回显操作直接返回客户端请求即可 String response process(request); //3.将响应返回给客户端 write.println(response); //刷新缓冲区 write.flush(); //4.打印日志 System.out.printf(【%s : %d】 request -%s response -%s\n, socket.getInetAddress(), socket.getPort(), request, response); } }catch (IOException e) { e.printStackTrace(); } finally { socket.close();//当客户端和服务器连接完毕并执行完上面代码这个连接就失效了我们需要手动关闭socket的连接防止资源泄露 } } public String process(String request) { return request; } public static void main(String[] args) throws IOException { TcpServer server new TcpServer(10086); server.start(); } }每次服务器收到一个客户端的连接都是通过ServerSocket的进行acceptServerSocket只需要进行accept即可不负责提供读写的操作客户端Codepackage TCP; 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 { private Socket socket null; public TcpClient(String ip, int port) throws IOException { socket new Socket(ip, port); } public void start() { System.out.println(Client Start~~~); try(InputStream inputStream socket.getInputStream(); OutputStream outputStream socket.getOutputStream()) { Scanner scanner new Scanner(System.in); PrintWriter write new PrintWriter(outputStream); Scanner read new Scanner(inputStream); while(true) { //1.让用户输入字符串 System.out.print(请输入一串字符串 -); String request scanner.next(); //2.将请求发送给服务器 write.println(request); //刷新缓冲区 write.flush(); //3.接收服务器响应 if(!read.hasNext()) { break; } String response read.next(); //将响应输出在屏幕上 System.out.println(response); } }catch(IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpClient client new TcpClient(127.0.0.1, 10086); client.start(); } }对于TCP的网络编程有三点需要注意1.TCP是面向字节流的即InputStream / OutputStream会对文件进行操作所以文件内部是设置了缓冲区我们写入的文件并不是直接写入文件而是写入到了一个缓冲区的地方当缓冲区满了才会真正写入到文件中 ---- 所以在write的时候需要使用flush对缓冲区进行刷新即使缓冲区没有满也是可以真正写入的2.当前代码中是存在文件资源泄露的情况因为当客户端每次连接上服务器都会触发一次accept都会创建一个Socket就会占用一个文件资源描述符表的位置当客户端源源不断的过来每一次的连接都会产生一个新的socket文件而我们只使用不关闭就会一直占用文件描述符表的位置 ---- 这时就需要我们进行close操作3.我们需要使用多线程才能让多个客户端同时进行操作如果随着客户端数量不断增加我们后续还可以继续考虑线程池减小线程的创建与销毁的操作2.实现翻译字典的TCP客户端和服务器服务器Codepackage TCP; 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.HashMap; import java.util.Map; import java.util.Scanner; public class TcpDictionaryServer { private ServerSocket serverSocket null; private MapString, String map new HashMap(); public TcpDictionaryServer(int port) throws IOException { serverSocket new ServerSocket(port); map.put(你好, hello); map.put(鸭子, duck); map.put(鱼, fish); map.put(猪, pig); map.put(狗, dog); map.put(猫, cat); } public void start() throws IOException { System.out.println(Server Start~~~); //与客户端产生连接连接成功会返回 Socket socket serverSocket.accept(); Thread t new Thread(() - { //使用多线程让多个客户端可以运行避免一个服务器只能服务一个客户端 try { processConnection(socket); } catch (IOException e) { throw new RuntimeException(e); } }); t.start(); } public void processConnection(Socket socket) throws IOException { //客户端和服务器一次连接之间可能传输较多数据 System.out.printf(【%s : %d】 客户端上线\n, socket.getInetAddress(), socket.getPort()); try(InputStream inputStream socket.getInputStream(); OutputStream outputStream socket.getOutputStream()) { Scanner scanner new Scanner(inputStream); PrintWriter write new PrintWriter(outputStream); while(true) { if(!scanner.hasNext()) { System.out.printf(【%s : %d】 客户端下线\n,socket.getInetAddress(), socket.getPort()); break; } //接收请求 String request scanner.next(); //计算响应 String response process(request); //将请求返回给客户端 write.println(response); //刷新 write.flush(); //打印日志 System.out.printf(【%s : %d】 request -%s response -%s\n, socket.getInetAddress(), socket.getPort(), request, response); } }catch (IOException e) { e.printStackTrace(); }finally { socket.close();//当客户端与服务器连接执行完上面的代码连接就失效了这时需要释放socket防止资源泄露 } } public String process(String request) { return map.getOrDefault(request, 很抱歉没查找到此单词~~~); } public static void main(String[] args) throws IOException { TcpDictionaryServer server new TcpDictionaryServer(10086); server.start(); } }客户端Codepackage TCP; 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 TcpDictionaryClient { private Socket socket null; public TcpDictionaryClient(String ip, int port) throws IOException { socket new Socket(ip, port); } public void start() { System.out.println(Client Start~~~); try(InputStream inputStream socket.getInputStream(); OutputStream outputStream socket.getOutputStream()) { Scanner scanner new Scanner(System.in); PrintWriter write new PrintWriter(outputStream); Scanner read new Scanner(inputStream); while(true) { //让用户输入内容 System.out.print(请输入一个中文); String request scanner.next(); //发送请求 write.println(request); write.flush();//刷新缓冲区 //接收服务器响应 if(!read.hasNext()) { break; } String response read.next(); //将服务器响应输入到显示器上 System.out.println(英文是 response); } }catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { TcpDictionaryClient client new TcpDictionaryClient(127.0.0.1, 10086); client.start(); } }