Java EE(12)——网络编程——UDP/TCP回显服务器

打印 上一主题 下一主题

主题 946|帖子 946|积分 2838

前言

本文主要介绍UDP和TCP相关的API,而且基于这两套API实现回显服务器
UDP和TCP

UDP和TCP属于网络五层模型中传输层的协议
特点:
UDP:无连接,不可靠,面向数据包,全双工
TCP:有连接,可靠,面向字节省,全双工
1.无连接和有连接
这里所说的连接不是指物理意义上的通过实物来举行绑定,而是虚拟的连接。举个例子,你打电话给对方,对方接通之后你才能举行后续通信,这就是有连接;无连接就相当于QQ发消息,无论对方是否同意,消息都能发过去
2.可靠和不可靠
岂论是哪种通信方式,实际上都无法保证数据一定能传输成功。以是,这里的可靠指的是,能获取到数据的传输环境。即使传输失败了我能知道它传输失败了,重传就是了;不可靠指的是信息传输之后就不管了,传输成功与否都和我无关,更不会重传
3.全双工和半双工
全双工通信答应通信的双方可以同时发送和吸收数据,半双工通信同一时间内只能在同一方向上传输
什么是回显服务器

回显的意思是无论客户端给服务器发送什么哀求,服务器会把客户端的哀求原样返回
1.UCP回显服务器

1.1API介绍

Java中UDP协议的API有两个,一个是DatagramSocket,一个是DatagramPacket
1.1.1DatagramSocket类

作用:用于应用程序之间发送和吸收UDP数据报
构造方法:
  1. //不指定端口号,由系统随机分配
  2. DatagramSocket()
  3. //指定端口号
  4. DatagramSocket(int port)
复制代码
其他方法:
  1. //接收一个数据报
  2. receive(DatagramPacket p)
  3. //发送一个数据报
  4. send(DatagramPacket p)
复制代码
1.1.2DatagramPacket类

作用:封装UDP数据报的数据和目的地点信息。它包含要发送的数据,目的主机的端标语和IP地点
构造方法:
  1. //字节数组,字节数组长度,服务器IP,服务器端口号
  2. //这是传入的IP地址和端口号是固定值,因为服务器的IP和端口号一般不变
  3. //所以作为DatagramSocket类的send方法的参数,用于客户端向服务器发送请求
  4. DatagramPacket(byte buf[], int length,InetAddress address, int port)
  5. //字节数组,字节数组长度
  6. //这个和上面有所不同,主要用于服务器向客户端返回请求
  7. //一会代码再具体讲解
  8. DatagramPacket(byte buf[], int length, SocketAddress address)
  9. //字节数组,字节数组长度
  10. //作为DatagramSocket类的receive方法的参数,用于客户端接收服务器的响应   或者   用于服务器接收客户端的请求
  11. DatagramPacket(byte buf[], int length)
复制代码
1.2UDP回显服务器代码

  1. import java.io.IOException;
  2. import java.net.DatagramPacket;
  3. import java.net.DatagramSocket;
  4. import java.net.SocketException;
  5. public class EchoSever {
  6.     private final DatagramSocket socket;
  7.     //传入端口号
  8.     public EchoSever(int port) throws SocketException {
  9.         socket = new DatagramSocket(port);
  10.     }
  11.     //启动服务器
  12.     public void start() throws IOException {
  13.         System.out.println("服务器启动");
  14.         //服务器死循环就行了,想要关闭服务器直接杀进程
  15.         while (true) {
  16.             //1.读取请求并解析
  17.             DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
  18.             //receive从网卡读取到UDP数据报,载荷部分放到byte数组里
  19.             //UDP报头存放在requestPacket其他属性里
  20.             //还能通过requestPacket知道源IP/端口
  21.             //receive具有阻塞功能
  22.             socket.receive(requestPacket);
  23.             //将接收到的字节数组转换为字符串
  24.             //requestPacket.getLength()得到的长度是有效长度,不一定是4096
  25.             String request = new String(requestPacket.getData(),0,requestPacket.getLength());
  26.             //2.根据请求计算相应(回显服务器啥都不用做)
  27.             String response = process(request);
  28.             //3.返回响应到客户端
  29.             //response.getBytes()得到String内部的字节数组
  30.             //当String里面全都是英文字符的时候,response.length()是可以的
  31.             //因为一个英文字母对于一个字节,但是一个汉字对应多个字节
  32.             //requestPacket.getSocketAddress()找到对应客户端的IP和端口号
  33.             DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
  34.                     response.getBytes().length,requestPacket.getSocketAddress());
  35.             socket.send(responsePacket);
  36.             //打印日志
  37.             //IP,端口号,请求,响应
  38.             System.out.printf("[%s:%d] request:%s,response:%s\n",requestPacket.getAddress().toString(),
  39.                     responsePacket.getPort(),request,response);
  40.         }
  41.     }
  42.     //根据请求计算相应
  43.     public String process(String request){
  44.         return request;
  45.     }
  46.     //
  47.     public static void main(String[] args) throws IOException {
  48.         EchoSever sever = new EchoSever(9090);
  49.         sever.start();
  50.     }
  51. }
复制代码
留意1:DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length,requestPacket.getSocketAddress());
因为requestPacket数据报保存了客户端的IP地点和端标语,以是通过getSocketAddress()能获取到客户端的IP地点和端标语
//
其次,客户端的IP地点和端标语是系统随机分配的,而且服务器会同时处理多个客户端哀求,这些客户端的IP和端标语都不一样,通过getSocketAddress()才能准确的找到对应客户端,而不是输入固定的IP和端标语
//
这就是为什么该方法用于服务器返回客户端的响应,而DatagramPacket(byte buf[], int length,InetAddress address, int port)用于客户端向服务器发送哀求
//
对比两个方法不难发现,前者获取到的IP和端标语是不固定的,而后者是指定IP和端标语
留意2:为什么服务器要指定端标语?而客户端的端标语是随机分配?
讲服务器比作餐厅,客户端比作顾客。顾客来餐厅吃饭,坐的桌子就相当于端标语,顾客今天坐001号桌,改天坐002号桌,人家乐意坐哪就坐那;但是餐厅的位置肯定是固定的,不可能今天餐厅在河滨,明天餐厅就跑到半山腰去了吗,餐厅的位置不能改变,否则顾客找不到餐厅的位置
1.2UDP客户端代码

  1. import java.io.IOException;
  2. import java.net.*;
  3. import java.util.Scanner;
  4. public class EchoClient {
  5.     private final DatagramSocket socket;
  6.     //这里的IP是String
  7.     private final String severIP;
  8.     private final int severPort;
  9.     //传入服务器IP和端口
  10.     public EchoClient(String severIP,int severPort) throws SocketException {
  11.         this.socket = new DatagramSocket();
  12.         this.severIP = severIP;
  13.         this.severPort = severPort;
  14.     }
  15.     //启动客户端
  16.     public void start() throws IOException {
  17.         System.out.println("客户端启动");
  18.         Scanner in = new Scanner(System.in);
  19.         while (true){
  20.             //提示用户要输入请求了
  21.             System.out.print("-> ");
  22.             //1.从控制台读取要发送的请求数据
  23.             //在用户输入之前有阻塞效果
  24.             if (!in.hasNext()){
  25.                 break;
  26.             }
  27.             String request = in.next();
  28.             //2.请求并发送
  29.             //字节数组,字节数组长度,服务器IP,服务器端口号
  30.             DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
  31.                     InetAddress.getByName(severIP),severPort);
  32.             socket.send(requestPacket);
  33.             //3.读取服务器返回的响应
  34.             DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
  35.             socket.receive(responsePacket);
  36.             //4.将响应打印到控制台
  37.             String response = new String(responsePacket.getData(),0,responsePacket.getLength());
  38.             System.out.println(response);
  39.         }
  40.     }
  41.     //
  42.     public static void main(String[] args) throws IOException {
  43.         EchoClient client = new EchoClient("127.0.0.1",9090);
  44.         client.start();
  45.     }
  46. }
复制代码
2.TCP回显服务器

2.1API介绍

Java中TCP协议的API有两个,一个是SeverSocket,一个是Socket
2.1.1SeverSocket类

作用:用于服务器监听来自客户端的TCP连接哀求
构造方法:
  1. //不指定端口号,由系统随机分配
  2. ServerSocket()
  3. //指定端口号
  4. ServerSocket(int port)
复制代码
其他方法:
  1. //监听并接受客户端传入的连接请求。此方法有阻塞效果,直到有客户端连接
  2. Socket accept():
复制代码
2.1.2Socket类

作用:主要用于客户端和服务器之间建立TCP连接
构造方法:
  1. //通过传入IP和端口号连接到指定的主机(服务器)
  2. Socket(String host, int port)
复制代码
其他方法:
  1. //返回此套接字(实例)的输入流,用于接收数据
  2. getInputStream()
  3. //返回此套接字(实例)的输出流,用于发送数据
  4. getOutputStream()
复制代码
2.1TCP回显服务器代码

  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.io.OutputStream;
  4. import java.io.PrintWriter;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. import java.util.Scanner;
  8. public class TCPEchoSever {
  9.     private final ServerSocket socket;
  10.     //
  11.     public TCPEchoSever(int port) throws IOException {
  12.         socket = new ServerSocket(port);
  13.     }
  14.     //启动服务器
  15.     private void start() throws IOException {
  16.         System.out.println("服务器启动");
  17.         while (true){
  18.             //将服务器和客户端连接
  19.             //accept()有阻塞效果,等待客户端建立联系
  20.             Socket clientSocket = socket.accept();
  21.             //每与一个客户端建立连接,都创建一个线程来执行客户端的请求
  22.             Thread thread = new Thread(()->{
  23.                 //服务器和客户端交互
  24.                 try {
  25.                     processConnection(clientSocket);
  26.                 } catch (IOException e) {
  27.                     throw new RuntimeException(e);
  28.                 }
  29.             });
  30.             thread.start();
  31.         }
  32.     }
  33.     //
  34.     public void processConnection(Socket clientSocket) throws IOException {
  35.         System.out.printf("[%s:%d] 服务器上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
  36.             //inputStream从网卡读数据
  37.         try(InputStream inputStream = clientSocket.getInputStream();
  38.             //OutputStream往网卡写数据
  39.             OutputStream outputStream = clientSocket.getOutputStream()) {
  40.             //从网卡读数据
  41.             //byte[] array = new byte[1024];int ret = inputStream.read(array);
  42.             PrintWriter printWriter = new PrintWriter(outputStream);
  43.             Scanner scanner = new Scanner(inputStream);
  44.             while (true){
  45.                 //读取完毕,当客户端下线的时候产生
  46.                 //在用户输入之前,hasNext()有阻塞效果
  47.                 //当客户端断开连接时,scanner.hasNext()返回false并中断循环
  48.                 if (!scanner.hasNext()){
  49.                     System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());
  50.                     break;
  51.                 }
  52.                 //1.读取请求并解析
  53.                 //用户传过来的请求必须带有空白符,没有的话就会阻塞
  54.                 String request = scanner.next();
  55.                 //2.计算响应
  56.                 String response = process(request);
  57.                 //3.返回响应
  58.                 //outputStream.write(response.getBytes(),0,response.getBytes().length);//这个方式不方便添加空白符
  59.                 //通过PrintWriter来封装outputStream
  60.                 //添加\n
  61.                 printWriter.println(response);
  62.                 //刷新缓冲区
  63.                 printWriter.flush();
  64.                 //打印日志
  65.                 System.out.printf("[%s:%d] request:%s,response:%s\n",clientSocket.getInetAddress(),
  66.                         clientSocket.getPort(),request,response);
  67.             }
  68.         } catch (IOException e) {
  69.             throw new RuntimeException(e);
  70.         }finally {
  71.             clientSocket.close();
  72.         }
  73.     }
  74.     //计算响应
  75.     private String process(String request) {
  76.         return request;
  77.     }
  78.     //
  79.     public static void main(String[] args) throws IOException {
  80.         TCPEchoSever sever = new TCPEchoSever(9090);
  81.         sever.start();
  82.     }
  83. }
复制代码
留意:为什么要调用clientSocket.close()?
因为每和一个客户端连接都会创建一个clientSocket套接字,它负责和客户端交互,即便客户端进程停止了,客户端的socket会被操作系统回收,但服务器中的clientSocket仍然会占用文件描述符和内存资源。当文件资源耗尽时,就无法与新的客户端建立连接
2.2TCP客户端代码

  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.io.OutputStream;
  4. import java.io.PrintWriter;
  5. import java.net.Socket;
  6. import java.util.Scanner;
  7. public class TCPEchoClient {
  8.     private final Socket socket;
  9.     public TCPEchoClient(String severIp,int port) throws IOException {
  10.             //与服务器建立联系
  11.         socket = new Socket(severIp,port);
  12.     }
  13.     //
  14.     public void start(){
  15.         System.out.println("客户端启动");
  16.         try(InputStream inputStream = socket.getInputStream();
  17.             OutputStream outputStream = socket.getOutputStream()) {
  18.             //读取控制台
  19.             Scanner scannerConsole = new Scanner(System.in);
  20.             Scanner scannerNetWork = new Scanner(inputStream);
  21.             PrintWriter printWriter = new PrintWriter(outputStream);
  22.             while (true){
  23.                 System.out.print("->");
  24.                 //在用户输入之前,hasNext()有阻塞效果
  25.                 if (!scannerConsole.hasNext()){
  26.                     break;
  27.                 }
  28.                 //1.从控制台输入请求
  29.                 String request = scannerConsole.next();
  30.                 //2.发送请求
  31.                 //让请求的结尾有\n
  32.                 printWriter.println(request);
  33.                 //刷新缓冲区
  34.                 printWriter.flush();
  35.                 //3.从服务器读取响应
  36.                 String response = scannerNetWork.next();
  37.                 //4.将响应打印到控制台
  38.                 System.out.println(response);
  39.             }
  40.         } catch (IOException e) {
  41.             throw new RuntimeException(e);
  42.         }
  43.     }
  44.     //
  45.     public static void main(String[] args) throws IOException {
  46.         TCPEchoClient client = new TCPEchoClient("127.0.0.1",9090);
  47.         client.start();
  48.     }
  49. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

悠扬随风

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表