当前位置:   article > 正文

【JavaEE】网络原理:UDP数据报套接字编程和TCP流套接字编程

【JavaEE】网络原理:UDP数据报套接字编程和TCP流套接字编程

目录

1.UDP数据报套接字编程

1.1 DatagramSocket

1.2 DatagramPacket

1.3 InetSocketAddress

1.4 基于UDP实现回响服务器

2.TCP流套接字编程

2.1 ServerSocket

2.2 Socket

2.3 基于TCP实现回响服务器


1.UDP数据报套接字编程

API 介绍

1.1 DatagramSocket

DatagramSocket 是UDP Socket,用于发送和接收UDP数据报。

DatagramSocket 的构造方法:

方法签名方法说明
DatagramSocket()创建⼀个UDP数据报套接字的Socket,绑定到本机任意⼀个随机端口(⼀般用于客户端)
DatagramSocket(int port)创建⼀个UDP数据报套接字的Socket,绑定到本机指定的端口(⼀般用于服务端)

DatagramSocket 的方法:

方法签名方法说明
void receive(DatagramPacket p)从此套接字接收数据报(如果没有接收到数据报,该方法会阻塞等待)
void send(DatagramPacket p)从此套接字发送数据报包(不会阻塞等待,直接发 送)
void close()关闭此数据报套接字

1.2 DatagramPacket

DatagramPacket 是UDP Socket发送和接收的数据报

DatagramPacket 构造方法:

方法签名方法说明
DatagramPacket(byte[] buf, int length)构造⼀个DatagramPacket以⽤来接收数据报,接收的 数据保存在字节数组(第⼀个参数buf)中,接收指定⻓度(第⼆个参数length)
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)构造⼀个DatagramPacket以用来发送数据报,发送的数据为字节数组(第⼀个参数buf)中,从0到指定长度(第⼆个参数length)。address指定目的主机的IP 和端口号

DatagramPacket 方法:

方法签名方法说明
InetAddress getAddress()从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
int getPort()从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
byte[] getData()获取数据报中的数据

构造UDP发送的数据报时,需要传入SocketAddress ,该对象可以使用 InetSocketAddress 来创建。

1.3 InetSocketAddress

InetSocketAddress ( SocketAddress 的子类 )构造方法:

方法签名方法说明
InetSocketAddress(InetAddress addr, int port)创建⼀个Socket地址,包含IP地址和端⼝号

1.4 基于UDP实现回响服务器

服务器端代码:

  1. import java.io.IOException;
  2. import java.net.DatagramPacket;
  3. import java.net.DatagramSocket;
  4. import java.net.SocketException;
  5. import java.text.SimpleDateFormat;
  6. import java.util.Calendar;
  7. public class UdpEchoServer {
  8. private DatagramSocket socket = null;
  9. public UdpEchoServer(int port) throws SocketException {
  10. socket = new DatagramSocket(port); //port:端口号
  11. }
  12. public void start() throws IOException {
  13. System.out.println("服务器启动!");
  14. while (true) {
  15. //每次循环,就是一个请求-响应过程
  16. //1.读取请求并解析
  17. DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
  18. socket.receive(requestPacket);
  19. //基于字节数组构造String
  20. String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
  21. //2.根据请求计算响应(对于回显服务器来说,这一步啥都不做)
  22. String response = process(request);
  23. //把响应返回客户端
  24. // 构造一个 DatagramPacke 作为相应对象
  25. DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
  26. 0, response.getBytes().length,
  27. requestPacket.getSocketAddress()); //得到 INetAddress 对象, 这个对象包含了 ip 和端口号
  28. socket.send(responsePacket);
  29. //打印日志
  30. Calendar calendar = Calendar.getInstance();
  31. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  32. String dateTime = sdf.format(calendar.getTime());
  33. System.out.printf("[%s:%d time:%s] req: %s, resp: %s\n", requestPacket.getAddress().toString(),
  34. requestPacket.getPort(),dateTime, request, response);
  35. }
  36. }
  37. public String process(String request) {
  38. return request;
  39. }
  40. public static void main(String[] args) throws IOException {
  41. UdpEchoServer server = new UdpEchoServer(9090);
  42. server.start();
  43. }
  44. }

客户端代码

  1. import java.io.IOException;
  2. import java.net.*;
  3. import java.util.Scanner;
  4. public class UdpEchoClient {
  5. private DatagramSocket socket = null;
  6. private String serverIp;
  7. private int serverPort;
  8. public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
  9. this.serverIp = serverIp;
  10. this.serverPort = serverPort;
  11. socket = new DatagramSocket();
  12. }
  13. public void start() throws IOException {
  14. System.out.println("客户端启动!");
  15. Scanner scanner = new Scanner(System.in);
  16. while (true) {
  17. System.out.print("->"); //提示用户接下来要输入内容
  18. //1.从控制台读取要发送的数据
  19. if (!scanner.hasNext()) {
  20. break;
  21. }
  22. String request = scanner.next();
  23. //2.构造请求并发送
  24. DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
  25. InetAddress.getByName(serverIp), serverPort);
  26. socket.send(requestPacket);
  27. //3.读取服务器的响应
  28. DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
  29. socket.receive(responsePacket);
  30. //4.把响应显示到控制台上
  31. String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
  32. System.out.println(response);
  33. }
  34. }
  35. public static void main(String[] args) throws IOException {
  36. UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
  37. client.start();
  38. }
  39. }

运行效果:

 编写⼀个英译汉的服务器. 只需要重写 process

  1. import java.io.IOException;
  2. import java.net.SocketException;
  3. import java.util.HashMap;
  4. //词典服务器
  5. public class UdpDictServer extends UdpEchoServer {
  6. private HashMap<String, String> hashMap = new HashMap<>();
  7. public UdpDictServer(int port) throws SocketException {
  8. super(port);
  9. hashMap.put("cat", "小猫");
  10. hashMap.put("dog", "小狗");
  11. hashMap.put("chicken", "坤坤");
  12. hashMap.put("aaaaaaaaaa", "测试");
  13. }
  14. //start 方法完全从父类中继承下来即可
  15. //process 方法要进行重写,加入另外的业务逻辑,进行翻译
  16. @Override
  17. public String process(String request) {
  18. return hashMap.getOrDefault(request, "您查的单词不存在!");
  19. }
  20. public static void main(String[] args) throws IOException {
  21. UdpDictServer server = new UdpDictServer(9090);
  22. server.start();
  23. }
  24. }

 运行效果

2.TCP流套接字编程

API 介绍

2.1 ServerSocket

ServerSocket 是创建TCP服务端Socket的API。

ServerSocket 构造方法:

方法签名方法说明
ServerSocket(int port)创建⼀个服务端流套接字Socket,并绑定到指定端⼝

ServerSocket 方法

法签名法说明
Socket accept()开始监听指定端⼝(创建时绑定的端⼝),有客户端 连接后,返回⼀个服务端Socket对象,并基于该 Socket建立与客户端的连接,否则阻塞等待
void close()关闭此套接字

2.2 Socket

Socket 是客户端 Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服 务端Socket。

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

Socket 构造方法:

方法签名方法说明
Socket(String host, int port)创建⼀个客户端流套接字Socket,并与对应IP的主机 上,对应端口的进程建立连接

Socket 方法:

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

2.3 基于TCP实现回响服务器

服务器端代码:

  1. import java.io.*;
  2. import java.net.ServerSocket;
  3. import java.net.Socket;
  4. import java.util.Scanner;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. public class TcpEchoServer {
  8. private ServerSocket serverSocket = null;
  9. public TcpEchoServer(int port) throws IOException {
  10. serverSocket = new ServerSocket(port);
  11. }
  12. public void start() throws IOException {
  13. System.out.println("服务器启动!");
  14. // 创建线程池
  15. ExecutorService pool = Executors.newCachedThreadPool();
  16. while(true) {
  17. // 通过accept方法"接听电话",然后才能进行通信
  18. Socket clientSocket = serverSocket.accept();
  19. pool.submit(new Runnable() {
  20. @Override
  21. public void run() {
  22. processConnection(clientSocket);
  23. }
  24. });
  25. }
  26. }
  27. //通过这个方法来处理一次连接, 连接建立过程中涉及到多次请求的响应交互
  28. private void processConnection(Socket clientSocket) {
  29. System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
  30. //循环的读取客户端的请求并返回响应
  31. try(InputStream inputStream = clientSocket.getInputStream();
  32. OutputStream outputStream = clientSocket.getOutputStream()) {
  33. Scanner scanner = new Scanner(inputStream);
  34. while(true) {
  35. if(!scanner.hasNext()) {
  36. //读取完毕,客户端断开连接,就会产生读取完毕
  37. System.out.printf("[%s:%d] 客户端下线!\n",clientSocket.getInetAddress(),clientSocket.getPort());
  38. break;
  39. }
  40. //1.读取请求并解析 next要读到空白符才会结束
  41. String request = scanner.next();
  42. //2.根据请求计算响应
  43. String response = process(request);
  44. //3.把响应返回给客户端
  45. //通过这种方法可以写回, 但是这种方法不方便给返回的响应中添加\n
  46. //outputStream.write(response.getBytes(),0,response.getBytes().length);
  47. //也可以给 outputStream 套上一层,完成更方便的写入
  48. PrintWriter printWriter = new PrintWriter(outputStream);
  49. printWriter.println(response);
  50. printWriter.flush();
  51. System.out.printf("[%s:%d] req: %s, resp: %s \n",clientSocket.getInetAddress(),clientSocket.getPort(),
  52. request,response);
  53. }
  54. } catch (IOException e) {
  55. throw new RuntimeException(e);
  56. } finally {
  57. try {
  58. clientSocket.close();
  59. } catch (IOException e) {
  60. throw new RuntimeException(e);
  61. }
  62. }
  63. }
  64. public String process(String request) {
  65. return request;
  66. }
  67. public static void main(String[] args) throws IOException {
  68. TcpEchoServer server = new TcpEchoServer(9090);
  69. server.start();
  70. }
  71. }

注意: 如果只是单个线程, 无法同时响应多个客户端. 此处给每个客户端都分配⼀个线程. 为了避免频繁创建销毁线程, 引入线程池

 

客户端代码:

  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 Socket socket = null;
  9. public TcpEchoClient(String serverIp,int serverPort) throws IOException {
  10. //此处可以把这里的ip和port直接传给socket对象
  11. //由于tcp是有连接的,因此 socket 里面就会保存这两信息
  12. socket = new Socket(serverIp,serverPort);
  13. }
  14. public void start() {
  15. System.out.println("客户端启动");
  16. try(InputStream inputStream = socket.getInputStream();
  17. OutputStream outputStream = socket.getOutputStream()) {
  18. Scanner scannerConsole = new Scanner(System.in);
  19. Scanner scannerNetWork = new Scanner(inputStream);
  20. PrintWriter writer = new PrintWriter(outputStream);
  21. while(true) {
  22. //这里的流程和 UDP 的客户端类似
  23. //1.从控制台读取输入的字符串
  24. System.out.print("-> ");
  25. if(!scannerConsole.hasNext()) {
  26. break;
  27. }
  28. String request = scannerConsole.next();
  29. //2.把请求发给服务器, 这里需要使用println来发送, 为了让发送的请求末尾带有 \n
  30. writer.println(request);
  31. // 通过flush刷新缓冲区
  32. writer.flush();
  33. //3.从服务器读取响应
  34. String response = scannerNetWork.next();
  35. //4.把响应显示出来
  36. System.out.println(response);
  37. }
  38. } catch (IOException e) {
  39. throw new RuntimeException(e);
  40. }
  41. }
  42. public static void main(String[] args) throws IOException {
  43. TcpEchoClient client = new TcpEchoClient("127.0.0.1",9090);
  44. client.start();
  45. }
  46. }

 使用PrintWrite记得手动刷新缓冲区:

 运行效果

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/92038
推荐阅读
相关标签
  

闽ICP备14008679号