当前位置:   article > 正文

socket实现TCP UDP

socket实现TCP UDP

1、socket通信建立流程

1.1、创建服务端流程

  • 使用 socket 函数来创建 socket服务。

  • 使用 bind 函数绑定端口。

  • 使用 listen 函数监听端口。

  • 使用 accept 函数接收客户端请求。

1.2、创建客户端流程

  • 使用 socket 函数来创建 socket 服务。

  • 使用 connect 函数连接到 socket 服务端。

以下图表演示了客户端与服务端之间的通信流程:

1.3、创建服务端代码

  1. package com.example.dyc.mysocket;
  2. import java.io.*;
  3. import java.net.ServerSocket;
  4. import java.net.Socket;
  5. import java.net.SocketAddress;
  6. import java.util.Date;
  7. import java.util.HashMap;
  8. import java.util.Map;
  9. import java.util.Scanner;
  10. import java.util.concurrent.ExecutorService;
  11. import java.util.concurrent.Executors;
  12. public class MySocketServer {
  13. // 预定义字典
  14. private static final Map<String, String> dictionary;
  15. //字典初始化
  16. static {
  17. dictionary = new HashMap<>();
  18. dictionary.put("apple", "苹果");
  19. dictionary.put("pear", "梨");
  20. }
  21. //定义服务器端口,范围在[0, 65535]
  22. public static final int PORT = 8888;
  23. public static void main(String[] args) throws IOException {
  24. System.out.println(new Date() + ":" + 1);
  25. ServerSocket serverSocket = new ServerSocket(PORT);//从CLOSED到LISTEN状态
  26. System.out.println(new Date() + ":" + 2);
  27. ExecutorService executorService = Executors.newFixedThreadPool(10);
  28. while (true) {
  29. System.out.println(new Date() + ":" + 3);
  30. Socket socket = serverSocket.accept(); // 阻塞,等待客户端发起连接,建立连接,到ESTABLISHED状态
  31. System.out.println(new Date() + ":" + 4);
  32. SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();//得到客户端地址+端口
  33. System.out.println(new Date() + ":" + remoteSocketAddress);
  34. InputStream inputStream = socket.getInputStream();//得到输入流
  35. Scanner scanner = new Scanner(inputStream, "UTF-8");//字符集编码
  36. OutputStream outputStream = socket.getOutputStream();//得到输出流
  37. Writer writer = new OutputStreamWriter(outputStream, "UTF-8");//字符集编码
  38. PrintWriter printWriter = new PrintWriter(writer);
  39. // TCP 是一种流式数据,没有明显分界的
  40. // 隐含着我们的请求一定 XXXX\n
  41. String request = scanner.nextLine();//由scanner得到客户端输入
  42. String response = dictionary.getOrDefault(request, "没有找到");
  43. // 响应的协议也是 XXX\n
  44. printWriter.println(response);//把响应传入输出
  45. printWriter.flush();//发送给客户端
  46. socket.close(); // 关闭连接
  47. }
  48. }
  49. }

1.4、创建客户端代码

  1. package com.example.dyc.mysocket;
  2. import java.io.*;
  3. import java.net.Socket;
  4. import java.net.SocketAddress;
  5. import java.util.Date;
  6. import java.util.Scanner;
  7. public class MySocketClient {
  8. public static void main(String[] args) throws IOException {
  9. System.out.println(new Date() + ":" + 1);
  10. Socket socket = new Socket("127.0.0.1", 8888);//刚建立完连接,传入客户端IP地址和端口
  11. SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();//得到服务器地址+端口
  12. System.out.println(new Date() + ":" + remoteSocketAddress);
  13. InputStream inputStream = socket.getInputStream();//得到输入流
  14. Scanner scanner = new Scanner(inputStream, "UTF-8");//字符集编码
  15. OutputStream outputStream = socket.getOutputStream();//得到输出流
  16. Writer writer = new OutputStreamWriter(outputStream, "UTF-8");//字符集编码
  17. PrintWriter printWriter = new PrintWriter(writer);
  18. printWriter.println("apple");//把apple传入输出
  19. printWriter.flush();//输出发送给服务器
  20. String response = scanner.nextLine();//由scanner得到输入的服务器响应
  21. System.out.println(new Date() + ":" + response);
  22. socket.close();//关闭连接
  23. }
  24. }

2、socket实现BIO

2.1、BIO

传统的网络通讯模型,就是BIO,同步阻塞IO, 其实就是服务端创建一个ServerSocket, 然后就是客户端用一个Socket去连接服务端的那个ServerSocket, ServerSocket接收到了一个的连接请求就创建一个Socket和一个线程去跟那个Socket进行通讯。接着客户端和服务端就进行阻塞式的通信,客户端发送一个请求,服务端Socket进行处理后返回响应,在响应返回前,客户端那边就阻塞等待,什么事情也做不了。 这种方式的缺点, 每次一个客户端接入,都需要在服务端创建一个线程来服务这个客户端,这样大量客户端来的时候,就会造成服务端的线程数量可能达到了几千甚至几万,这样就可能会造成服务端过载过高,最后崩溃死掉。

2.2、代码实现

Client

  1. import java.net.InetSocketAddress;
  2. import java.net.Socket;
  3. import java.text.SimpleDateFormat;
  4. import java.util.Date;
  5. public class MyBIOClient {
  6. public static void main(String[] args) throws Exception {
  7. // 创建 Socket 客户端
  8. Socket socket = new Socket();
  9. // 与服务端建立连接
  10. socket.connect(new InetSocketAddress("127.0.0.1", 8081));
  11. SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
  12. int counter = 0;
  13. while (counter < 5) {
  14. String now = simpleDateFormat.format(new Date());
  15. // 发送请求
  16. socket.getOutputStream().write(now.getBytes("UTF-8"));
  17. socket.getOutputStream().flush();
  18. Thread.sleep(1000);
  19. counter++;
  20. }
  21. // 若方法运行结束后,不调用 close 函数,服务端则会报错:java.net.SocketException: Connection reset
  22. socket.close();
  23. System.out.println("客户端关闭了 Socket 连接~!");
  24. }
  25. }

Serve

  1. import java.net.ServerSocket;
  2. import java.net.Socket;
  3. import java.util.concurrent.LinkedBlockingDeque;
  4. import java.util.concurrent.ThreadPoolExecutor;
  5. import java.util.concurrent.TimeUnit;
  6. public class MyBIOServe {
  7. public static void main(String[] args) throws Exception {
  8. // 创建 Socket 服务端,并设置监听的端口
  9. ServerSocket serverSocket = new ServerSocket(8081);
  10. // 创建线程池以执行客户端请求(防止因请求过多,而导致的阻塞)
  11. ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(1, 10, 60,
  12. TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
  13. while (true) {
  14. // 阻塞方法,监听客户端请求
  15. Socket socket = serverSocket.accept();
  16. System.out.println("\r\n" + socket);
  17. // 创建自定义请求处理器
  18. SocketHandler handler = new SocketHandler(socket);
  19. // 处理客户端请求
  20. poolExecutor.execute(handler);
  21. }
  22. }
  23. }
SocketHandler
  1. public class SocketHandler implements Runnable {
  2. private Socket socket;
  3. private static final byte[] BUFFER = new byte[1024];
  4. @Override
  5. public void run() {
  6. try {
  7. while (true){
  8. System.out.println(Thread.currentThread().getName());
  9. // 读取客户端 Socket 请求数据
  10. int read = socket.getInputStream().read(BUFFER);
  11. if (read != -1) {
  12. System.out.println(new String(BUFFER, "UTF-8"));
  13. }else{
  14. socket.close();
  15. System.out.println("服务端关闭了 Socket 连接~!");
  16. break;
  17. }
  18. }
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }
  22. }
  23. public SocketHandler(Socket socket) {
  24. this.socket = socket;
  25. }
  26. }

3、NIO

3.1、NIO

NIO: NIO是一种同步非阻塞IO, 基于Reactor模型来实现的。其实相当于就是一个线程处理大量的客户端的请求,通过一个线程轮询大量的channel,每次就获取一批有事件的channel,然后对每个请求启动一个线程处理即可。这里的核心就是非阻塞,就那个selector一个线程就可以不停轮询channel,所有客户端请求都不会阻塞,直接就会进来,大不了就是等待一下排着队而已。这里面优化BIO的核心就是,一个客户端并不是时时刻刻都有数据进行交互,没有必要死耗着一个线程不放,所以客户端选择了让线程歇一歇,只有客户端有相应的操作的时候才发起通知,创建一个线程来处理请求。

3.2、代码实现

MyNIOClient2 

  1. import java.io.IOException;
  2. import java.net.InetSocketAddress;
  3. import java.nio.ByteBuffer;
  4. import java.nio.channels.SocketChannel;
  5. import java.util.Scanner;
  6. public class MyNIOClient2 {
  7. public static void main(String[] args) {
  8. //创建远程地址
  9. InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
  10. SocketChannel channel = null;
  11. //定义缓存
  12. ByteBuffer buffer = ByteBuffer.allocate(1024);
  13. try {
  14. //开启通道
  15. channel = SocketChannel.open();
  16. //连接远程远程服务器
  17. channel.connect(address);
  18. Scanner sc = new Scanner(System.in);
  19. while (true) {
  20. System.out.println("客户端即将给 服务器发送数据..");
  21. String line = "clinet2:" + sc.nextLine();
  22. if (line.equals("exit")) {
  23. break;
  24. }
  25. //控制台输入数据写到缓存
  26. buffer.put(line.getBytes("UTF-8"));
  27. //重置buffer 游标
  28. buffer.flip();
  29. //数据发送到数据
  30. channel.write(buffer);
  31. //清空缓存数据
  32. buffer.clear();
  33. //读取服务器返回的数据
  34. int readLen = channel.read(buffer);
  35. if (readLen == -1) {
  36. break;
  37. }
  38. //重置buffer游标
  39. buffer.flip();
  40. byte[] bytes = new byte[buffer.remaining()];
  41. //读取数据到字节数组
  42. buffer.get(bytes);
  43. System.out.println("收到了服务器发送的数据 : " + new String(bytes, "UTF-8"));
  44. buffer.clear();
  45. }
  46. } catch (IOException e) {
  47. e.printStackTrace();
  48. } finally {
  49. if (null != channel) {
  50. try {
  51. channel.close();
  52. } catch (IOException e) {
  53. e.printStackTrace();
  54. }
  55. }
  56. }
  57. }
  58. }

MyNIOService

  1. import java.io.IOException;
  2. import java.net.InetSocketAddress;
  3. import java.net.ServerSocket;
  4. import java.net.Socket;
  5. import java.nio.ByteBuffer;
  6. import java.nio.channels.*;
  7. import java.util.Iterator;
  8. import java.util.Scanner;
  9. import java.util.concurrent.LinkedBlockingDeque;
  10. import java.util.concurrent.ThreadPoolExecutor;
  11. import java.util.concurrent.TimeUnit;
  12. public class MyNIOService extends Thread {
  13. //1.声明多路复用器
  14. private Selector selector;
  15. //2.定义读写缓冲区
  16. private ByteBuffer readBuffer = ByteBuffer.allocate(1024);
  17. private ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
  18. //3.定义构造方法初始化端口
  19. public MyNIOService(int port) {
  20. init(port);
  21. }
  22. //4.main方法启动线程
  23. public static void main(String[] args) {
  24. new Thread(new MyNIOService(8888)).start();
  25. }
  26. //5.初始化
  27. private void init(int port) {
  28. try {
  29. System.out.println("服务器正在启动......");
  30. //1)开启多路复用器
  31. this.selector = Selector.open();
  32. //2) 开启服务通道
  33. ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
  34. //3)设置为非阻塞
  35. serverSocketChannel.configureBlocking(false);
  36. //4)绑定端口
  37. serverSocketChannel.bind(new InetSocketAddress(port));
  38. //5)注册,标记服务通标状态
  39. serverSocketChannel.register(this.selector, SelectionKey.OP_ACCEPT);
  40. System.out.println("服务器启动完毕");
  41. } catch (IOException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. public void run() {
  46. while (true) {
  47. try {
  48. //1.当有至少一个通道被选中,执行此方法
  49. this.selector.select();
  50. //2.获取选中的通道编号集合
  51. Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
  52. //3.遍历keys
  53. while (keys.hasNext()) {
  54. SelectionKey key = keys.next();
  55. //4.当前key需要从集合中移出,如果不移出,下次循环会执行对应的逻辑,造成业务错乱
  56. keys.remove();
  57. //5.判断通道是否有效
  58. if (key.isValid()) {
  59. try {
  60. //6.判断是否可读
  61. if (key.isAcceptable()) {
  62. accept(key);
  63. }
  64. } catch (CancelledKeyException e) {
  65. //出现异常断开连接
  66. key.cancel();
  67. }
  68. try {
  69. //7.判断是否可读
  70. if (key.isReadable()) {
  71. read(key);
  72. }
  73. } catch (CancelledKeyException e) {
  74. //出现异常断开连接
  75. key.cancel();
  76. }
  77. try {
  78. //8.判断是否可写
  79. if (key.isWritable()) {
  80. write(key);
  81. }
  82. } catch (CancelledKeyException e) {
  83. //出现异常断开连接
  84. key.cancel();
  85. }
  86. }
  87. }
  88. } catch (IOException e) {
  89. e.printStackTrace();
  90. }
  91. }
  92. }
  93. private void accept(SelectionKey key) {
  94. try {
  95. //1.当前通道在init方法中注册到了selector中的ServerSocketChannel
  96. ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
  97. //2.阻塞方法, 客户端发起后请求返回.
  98. SocketChannel channel = serverSocketChannel.accept();
  99. //3.serverSocketChannel设置为非阻塞
  100. channel.configureBlocking(false);
  101. //4.设置对应客户端的通道标记,设置次通道为可读时使用
  102. channel.register(this.selector, SelectionKey.OP_READ);
  103. } catch (IOException e) {
  104. e.printStackTrace();
  105. }
  106. }
  107. //使用通道读取数据
  108. private void read(SelectionKey key) {
  109. try {
  110. //清空缓存
  111. this.readBuffer.clear();
  112. //获取当前通道对象
  113. SocketChannel channel = (SocketChannel) key.channel();
  114. //将通道的数据(客户发送的data)读到缓存中.
  115. int readLen = channel.read(readBuffer);
  116. //如果通道中没有数据
  117. if (readLen == -1) {
  118. //关闭通道
  119. key.channel().close();
  120. //关闭连接
  121. key.cancel();
  122. return;
  123. }
  124. //Buffer中有游标,游标不会重置,需要我们调用flip重置. 否则读取不一致
  125. this.readBuffer.flip();
  126. //创建有效字节长度数组
  127. byte[] bytes = new byte[readBuffer.remaining()];
  128. //读取buffer中数据保存在字节数组
  129. readBuffer.get(bytes);
  130. System.out.println("收到了从客户端 " + channel.getRemoteAddress() +
  131. " : " + new String(bytes, "UTF-8"));
  132. //注册通道,标记为写操作
  133. channel.register(this.selector, SelectionKey.OP_WRITE);
  134. } catch (Exception e) {
  135. }
  136. }
  137. //给通道中写操作
  138. private void write(SelectionKey key) {
  139. //清空缓存
  140. this.readBuffer.clear();
  141. //获取当前通道对象
  142. SocketChannel channel = (SocketChannel) key.channel();
  143. //录入数据
  144. Scanner scanner = new Scanner(System.in);
  145. try {
  146. System.out.println("即将发送数据到客户端..");
  147. String line = scanner.nextLine();
  148. //把录入的数据写到Buffer中
  149. writeBuffer.put(line.getBytes("UTF-8"));
  150. //重置缓存游标
  151. writeBuffer.flip();
  152. channel.write(writeBuffer);
  153. //清空writeBuffer
  154. writeBuffer.clear();
  155. channel.register(this.selector, SelectionKey.OP_READ);
  156. } catch (Exception e) {
  157. e.printStackTrace();
  158. }
  159. }
  160. }

4、Netty

ps:后面再补充

5、UDP

中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服。UDP没有TCP的握手、确认、窗口、重传、拥塞控制等机制,UDP是一个无状态的传输协议,所以它在传递数据时非常快。没有TCP的这些机制,UDP较TCP被攻击者利用的漏洞就要少一些。因为UDP没有TCP那些可靠的机制,在数据传递时,如果网络质量不好,就会很容易丢包。

当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。
QQ语音,QQ视频,TFTP

代码实现:

UDPServer

  1. import java.io.IOException;
  2. import java.net.DatagramPacket;
  3. import java.net.DatagramSocket;
  4. import java.net.InetAddress;
  5. /*
  6. * 服务器端,实现基于UDP的用户登陆
  7. */
  8. public class UDPServer {
  9. public static void main(String[] args) throws IOException {
  10. /*
  11. * 接收客户端发送的数据
  12. */
  13. // 1.创建服务器端DatagramSocket,指定端口
  14. DatagramSocket socket = new DatagramSocket(8800);
  15. // 2.创建数据报,用于接收客户端发送的数据
  16. byte[] data = new byte[1024];// 创建字节数组,指定接收的数据包的大小
  17. DatagramPacket packet = new DatagramPacket(data, data.length);
  18. // 3.接收客户端发送的数据
  19. System.out.println("****服务器端已经启动,等待客户端发送数据");
  20. socket.receive(packet);// 此方法在接收到数据报之前会一直阻塞
  21. // 4.读取数据
  22. String info = new String(data, 0, packet.getLength());
  23. System.out.println("我是服务器,客户端说:" + info);
  24. /*
  25. * 向客户端响应数据
  26. */
  27. // 1.定义客户端的地址、端口号、数据
  28. InetAddress address = packet.getAddress();
  29. int port = packet.getPort();
  30. byte[] data2 = "欢迎您!".getBytes();
  31. // 2.创建数据报,包含响应的数据信息
  32. DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port);
  33. // 3.响应客户端
  34. socket.send(packet2);
  35. // 4.关闭资源
  36. socket.close();
  37. }
  38. }

UDPClient

  1. import java.io.IOException;
  2. import java.net.DatagramPacket;
  3. import java.net.DatagramSocket;
  4. import java.net.InetAddress;
  5. import java.net.SocketException;
  6. import java.net.UnknownHostException;
  7. /*
  8. * 客户端
  9. */
  10. public class UDPClient {
  11. public static void main(String[] args) throws IOException {
  12. /*
  13. * 向服务器端发送数据
  14. */
  15. // 1.定义服务器的地址、端口号、数据
  16. InetAddress address = InetAddress.getByName("localhost");
  17. int port = 8800;
  18. byte[] data = "用户名:admin;密码:123".getBytes();
  19. // 2.创建数据报,包含发送的数据信息
  20. DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
  21. // 3.创建DatagramSocket对象
  22. DatagramSocket socket = new DatagramSocket();
  23. // 4.向服务器端发送数据报
  24. socket.send(packet);
  25. /*
  26. * 接收服务器端响应的数据
  27. */
  28. // 1.创建数据报,用于接收服务器端响应的数据
  29. byte[] data2 = new byte[1024];
  30. DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
  31. // 2.接收服务器响应的数据
  32. socket.receive(packet2);
  33. // 3.读取数据
  34. String reply = new String(data2, 0, packet2.getLength());
  35. System.out.println("我是客户端,服务器说:" + reply);
  36. // 4.关闭资源
  37. socket.close();
  38. }
  39. }

参考文献:

Socket 之 BIO、NIO、Netty 简单实现 - 知乎 (zhihu.com)

什么是NIO?NIO和BIO,AIO之间的区别是什么? - 知乎 (zhihu.com)

java.nio.Buffer 中的 flip()方法_wrap.flip()-CSDN博客

JAVA Socket 实现 UDP 编程-CSDN博客

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

闽ICP备14008679号