当前位置:   article > 正文

Java网络编程UDP与TCP(Socket编程)_javaudp编程必须要关闭socket吗

javaudp编程必须要关闭socket吗

        摘要:读完本章您将对Java网络编程有一定的了解,知道UDP与TCP的区别,会用Java实现UDP、TCP传输数据。

        一、什么是UDP、TCP。

        网络编程顾名思义就是利用编程语言实现不同终端之间的通信,这其中包括发送端(客户端)通过规定好的协议组装包,在接收端(服务端)将包进行解析,从而提取出对应的信息,实现通信的目的。这里的协议主要有UDP与TCP协议,对应不同的协议,Java有不同的操作类实现,在Java中实现网络编程的类在java.net包下。        

        UDP:UDP是User Datagram Protocol的简称,中文名为用户数据报协议。它是一种面向无连接的传输协议,何为面向无连接,就是在传输数据的时候,不需要判断是否与服务端建立连接就可发送数据,对方是否成功接收并不知情。这样就导致在传输的时候安全性较差,无法保证数据准确抵达,但在传输数据的效率上会稍微快一点。

        TCP:TCP是Transmission Control Protocol的简称,中文名为传输控制协议。它是一种面向连接的、可靠的、基于字节流的传输层通信协议。何为面向连接,就是在传输数据之前,客户端需要与服务端建立连接,连接成功后才能传输数据进而实现通信目的。TCP建立连接遵循三次握手协议,即客户端向服务端发起SYN请求(第一次握手),服务端收到SYN请求并向客户端回应一个ACK+SYN给客户端(第二次握手),客户端收到服务端的SYN报文并回应一个ACK(第三次握手)连接成功,这时可以开始传输数据了。相比于UDP协议,TCP协议较安全可靠,效率上稍微慢一点点。

        二、Java UDP编程

        java udp涉及到的类主要有DatagramSocket、DatagramPacket,浏览两个类的API可知:

        DatagramSocket:


        它的主要构造方法有:


        DatagramSocket()的主要方法有:

        public synchronized void receive(DatagramPacket p) throws IOException{...}

        从此套接字接收数据包,数据报包DatapramPacket 缓冲区填充了接收的数据,数据报包还包含发送方的IP地址和发送机器上的端口号。此方法在未收到数据报包前会一直阻塞。数据报包对象的length字段包含所接收信息的长度。如果信息比包的长度长,该信息将被截短。 

        public void send(DatagramPacket p) throws IOException  {...}

        从此套接字发送数据报包。DatagramPacket 包含的信息指示:将要发送的数据、其长度、远程主机的 IP 地址和远程主机的端口号。

        DatagramPacket :

        此类表示数据报包。数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。

        其主要构造方法有:

        

        主要方法有:

        

        一个UDP Java Demo:

        接收端:

  1. // 创建接收端Socket, 绑定本机IP地址, 绑定指定端口
  2. DatagramSocket socket = new DatagramSocket(6789);
  3. // 创建接收端Packet, 用来接收数据
  4. DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
  5. // 用Socket接收Packet, 未收到数据时会阻塞
  6. socket.receive(packet);
  7. // 关闭Socket
  8. socket.close();
  9. // 从Packet中获取数据
  10. byte[] data = packet.getData();
  11. int len = packet.getLength();
  12. String s = new String(data, 0, len, "UTF-8");
  13. System.out.println(s);

        发送端:

  1. String s = "测试UDP!";
  2. // 创建发送端Socket, 绑定本机IP地址, 绑定任意一个未使用的端口号
  3. DatagramSocket socket = new DatagramSocket();
  4. // 创建发送端Packet, 指定数据, 长度, 地址, 端口号
  5. DatagramPacket packet = new DatagramPacket(s.getBytes("UTF-8"), s.getBytes().length, InetAddress.getByName("127.0.0.1"), 6789);
  6. // 使用Socket发送Packet
  7. socket.send(packet);
  8. // 关闭Socket
  9. socket.close();

        三、TCP

        Java实现TCP数据传输涉及到的类有Socket、ServerSocket。由此可看出TCP分客户端服务端,而UDP不分客户端服务端。Socket客户端服务端的读写是有先后顺序的,建立连接之后,客户端需要先向服务端写数据,写完之后需要调用socket.shutdownOutput()方法告诉服务端已经写完,这样服务端read()才会返回-1,客户端写完数据再读服务端返回的数据;而服务端是先读客户端传输过来的数据,再写需要向客户端传输的数据。如果是客户端先读服务端返回的数据再写向服务端发送的数据,这时服务端始终会先执行读客户端传输过来的数据,这时客户端却还没写,读的数据就会是空的。下面用代码演示:

        1、服务端先写再读,客户端先读再写,服务端读的数据是空的。

        服务端代码:

  1. System.out.println("服务端控制台输出");
  2. // 创建服务端serverSocket
  3. ServerSocket serverSocket = new ServerSocket(8091);
  4. // 服务端等待连接,如果未收到请求将一直阻塞
  5. Socket socket = serverSocket.accept();
  6. // 从socket中获取输出流,向客户端写数据
  7. OutputStream outputStream = socket.getOutputStream();
  8. // 从socket中获取输入流,读取客户端写的数据
  9. InputStream inputStream = socket.getInputStream();
  10. String s = "服务端返回给客户端的数据,服务端返回数据时间:" + System.nanoTime();
  11. // 服务端先写,往客户端返回数据
  12. outputStream.write(s.getBytes());
  13. socket.shutdownInput();
  14. // 服务端后读客户端传输过来的数据
  15. byte[] buf = new byte[1024];
  16. int len=0;
  17. StringBuffer sb = new StringBuffer();
  18. while((len=inputStream.read(buf))!=-1){
  19. System.out.println("服务端读数据");
  20. sb.append(new String(buf,0,len));
  21. }
  22. //传输过来的数据为空
  23. System.out.println("在时间点为" + System.nanoTime() + "时服务端接收客户端的数据是【"+sb+"】");
  24. inputStream.close();
  25. serverSocket.close();

        客户端代码:

  1. System.out.println("客户端控制台输出");
  2. // 创建一个未连接的socket连接
  3. Socket socket = new Socket();
  4. SocketAddress sa = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 8091);
  5. // 建立连接
  6. socket.connect(sa);
  7. // 创建一个连接到端口为8091、地址为127.0.0.1的连接,如果服务端未开启会抛出异常
  8. //socket = new Socket("127.0.0.1", 8091);
  9. // 从socket中获取输入流,读取服务端返回的数据
  10. InputStream inputStream = socket.getInputStream();
  11. // 从socket中获取输出流,向服务端写数据
  12. OutputStream outputStream = socket.getOutputStream();
  13. // 客户端先读服务端返回的数据
  14. byte[] buf = new byte[1024];
  15. int len=0;
  16. StringBuffer sb = new StringBuffer();
  17. while((len=inputStream.read(buf))!=-1){
  18. sb.append(new String(buf,0,len));
  19. }
  20. System.out.println("在时间点为" + System.nanoTime() + "时客户端读到服务端返回的数据【"+sb+"】");
  21. // 客户端后向服务端写传输的数据
  22. String s = "我是客户端发来的数据"+ System.nanoTime();
  23. System.out.println(s);
  24. outputStream.write(s.getBytes());
  25. socket.shutdownOutput();
  26. socket.close();
  27. System.out.println("客户端已关闭");

执行后控制台输出:

  1. 服务端控制台输出
  2. 在时间点为1970625548654245时服务端接收客户端的数据是【】
  1. 客户端控制台输出
  2. 在时间点为1970625549359944时客户端读到服务端返回的数据【服务端返回给客户端的数据,服务端返回数据时间:1970625548025315
  3. 我是客户端发来的数据1970625549474071
  4. 客户端已关闭
  1. // 服务端返回数据时间: 1970625548025315
  2. // 客户端读到数据时间 1970625549359944
  3. // 客户端写数据时间 1970625549474071
  4. // 服务端读客户端数据的时间 1970625548654245

为了更好的说明字符串内容都加了个时间戳,由控制台打印内容可看出,服务端并没有读到客户端发过来的数据,由时间可知,服务端读的时候,客户端还没向服务端写完。

    2、客户端先写再读,服务端先读再写

    客户端代码:

  1. System.out.println("客户端控制台输出");
  2. // 创建一个未连接的socket连接
  3. Socket socket = new Socket();
  4. SocketAddress sa = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 8091);
  5. // 建立连接
  6. socket.connect(sa);
  7. // 创建一个连接到端口为8091、地址为127.0.0.1的连接,如果服务端未开启会抛出异常
  8. //socket = new Socket("127.0.0.1", 8091);
  9. // 从socket中获取输入流,读取服务端返回的数据
  10. InputStream inputStream = socket.getInputStream();
  11. // 从socket中获取输出流,向服务端写数据
  12. OutputStream outputStream = socket.getOutputStream();
  13. // 客户端向服务端写数据
  14. String s = "我是客户端发来的数据"+ System.nanoTime();
  15. outputStream.write(s.getBytes());
  16. // 必须要关闭socket的输出流,告诉对方已经写完,对方用read读才能读到-1,
  17. // 如果用outputStream.close()会关掉整个socket连接,后续不能再操作
  18. socket.shutdownOutput();
  19. // 客户端读服务端返回的数据
  20. byte[] buf = new byte[1024];
  21. int len=0;
  22. StringBuffer sb = new StringBuffer();
  23. while((len=inputStream.read(buf))!=-1){
  24. sb.append(new String(buf,0,len));
  25. }
  26. System.out.println("在时间点为" + System.nanoTime() + "时客户端读到服务端返回的数据【"+sb+"】");
  27. socket.close();
  28. System.out.println("客户端已关闭");

        服务端代码:

  1. System.out.println("服务端控制台输出");
  2. // 创建服务端serverSocket
  3. ServerSocket serverSocket = new ServerSocket(8091);
  4. // 服务端等待连接,如果未收到请求将一直阻塞
  5. Socket socket = serverSocket.accept();
  6. // 从socket中获取输出流,向客户端写数据
  7. OutputStream outputStream = socket.getOutputStream();
  8. // 从socket中获取输入流,读取客户端写的数据
  9. InputStream inputStream = socket.getInputStream();
  10. // 服务端读客户端传输过来的数据
  11. byte[] buf = new byte[1024];
  12. int len=0;
  13. StringBuffer sb = new StringBuffer();
  14. while((len=inputStream.read(buf))!=-1){
  15. sb.append(new String(buf,0,len));
  16. }
  17. System.out.println("在时间点为" + System.nanoTime() + "时服务端接收客户端的数据是【"+sb+"】");
  18. String s = "服务端返回给客户端的数据,服务端返回数据时间:" + System.nanoTime();
  19. // 服务端向客户端写数据
  20. outputStream.write(s.getBytes());
  21. // 写完之后必须调用outputStream.close()或者shutdownOutput()
  22. //socket.shutdownOutput();
  23. outputStream.close();
  24. serverSocket.close();

        控制台输出:

  1. 服务端控制台输出
  2. 在时间点为1971871977863049时服务端接收客户端的数据是【我是客户端发来的数据1971871977069907
  1. 客户端控制台输出
  2. 在时间点为1971871978901276时客户端读到服务端返回的数据【服务端返回给客户端的数据,服务端返回数据时间:1971871978031776
  3. 客户端已关闭

        结论:由输出结果可知,服务端收到了客户端发来的数据,客户端也收到了服务端返回的数据。

        注意:写数据之后只有关闭输出流了,对方用read()方法读才有可能返回-1,如若未关闭,读方法一直会被阻塞,直到超时。在socket中关闭流有两种方式

socket.shutdownOutput()
outputStream.close()

二者的区别就是前者是半关闭,后者是将对应的socket连接也关闭了。

        对于Socket还有其他知识点,比如NIO,下章将会详细介绍,要想熟悉NIO,Socket必须要掌握,本章只是简单入门。



        

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

闽ICP备14008679号