赞
踩
摘要:读完本章您将对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:
接收端:
- // 创建接收端Socket, 绑定本机IP地址, 绑定指定端口
- DatagramSocket socket = new DatagramSocket(6789);
-
- // 创建接收端Packet, 用来接收数据
- DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
-
- // 用Socket接收Packet, 未收到数据时会阻塞
- socket.receive(packet);
-
- // 关闭Socket
- socket.close();
-
- // 从Packet中获取数据
- byte[] data = packet.getData();
- int len = packet.getLength();
- String s = new String(data, 0, len, "UTF-8");
- System.out.println(s);

发送端:
- String s = "测试UDP!";
-
- // 创建发送端Socket, 绑定本机IP地址, 绑定任意一个未使用的端口号
- DatagramSocket socket = new DatagramSocket();
-
- // 创建发送端Packet, 指定数据, 长度, 地址, 端口号
- DatagramPacket packet = new DatagramPacket(s.getBytes("UTF-8"), s.getBytes().length, InetAddress.getByName("127.0.0.1"), 6789);
-
- // 使用Socket发送Packet
- socket.send(packet);
-
- // 关闭Socket
- socket.close();
三、TCP
Java实现TCP数据传输涉及到的类有Socket、ServerSocket。由此可看出TCP分客户端服务端,而UDP不分客户端服务端。Socket客户端服务端的读写是有先后顺序的,建立连接之后,客户端需要先向服务端写数据,写完之后需要调用socket.shutdownOutput()方法告诉服务端已经写完,这样服务端read()才会返回-1,客户端写完数据再读服务端返回的数据;而服务端是先读客户端传输过来的数据,再写需要向客户端传输的数据。如果是客户端先读服务端返回的数据再写向服务端发送的数据,这时服务端始终会先执行读客户端传输过来的数据,这时客户端却还没写,读的数据就会是空的。下面用代码演示:
1、服务端先写再读,客户端先读再写,服务端读的数据是空的。
服务端代码:
- System.out.println("服务端控制台输出");
- // 创建服务端serverSocket
- ServerSocket serverSocket = new ServerSocket(8091);
-
- // 服务端等待连接,如果未收到请求将一直阻塞
- Socket socket = serverSocket.accept();
-
- // 从socket中获取输出流,向客户端写数据
- OutputStream outputStream = socket.getOutputStream();
- // 从socket中获取输入流,读取客户端写的数据
- InputStream inputStream = socket.getInputStream();
-
- String s = "服务端返回给客户端的数据,服务端返回数据时间:" + System.nanoTime();
- // 服务端先写,往客户端返回数据
- outputStream.write(s.getBytes());
- socket.shutdownInput();
- // 服务端后读客户端传输过来的数据
- byte[] buf = new byte[1024];
- int len=0;
- StringBuffer sb = new StringBuffer();
- while((len=inputStream.read(buf))!=-1){
- System.out.println("服务端读数据");
- sb.append(new String(buf,0,len));
- }
- //传输过来的数据为空
- System.out.println("在时间点为" + System.nanoTime() + "时服务端接收客户端的数据是【"+sb+"】");
-
- inputStream.close();
- serverSocket.close();

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

执行后控制台输出:
- 服务端控制台输出
- 在时间点为1970625548654245时服务端接收客户端的数据是【】
- 客户端控制台输出
- 在时间点为1970625549359944时客户端读到服务端返回的数据【服务端返回给客户端的数据,服务端返回数据时间:1970625548025315】
- 我是客户端发来的数据1970625549474071
- 客户端已关闭
- // 服务端返回数据时间: 1970625548025315
- // 客户端读到数据时间 1970625549359944
- // 客户端写数据时间 1970625549474071
- // 服务端读客户端数据的时间 1970625548654245
为了更好的说明字符串内容都加了个时间戳,由控制台打印内容可看出,服务端并没有读到客户端发过来的数据,由时间可知,服务端读的时候,客户端还没向服务端写完。
2、客户端先写再读,服务端先读再写
客户端代码:
- System.out.println("客户端控制台输出");
- // 创建一个未连接的socket连接
- Socket socket = new Socket();
- SocketAddress sa = new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 8091);
- // 建立连接
- socket.connect(sa);
-
- // 创建一个连接到端口为8091、地址为127.0.0.1的连接,如果服务端未开启会抛出异常
- //socket = new Socket("127.0.0.1", 8091);
-
- // 从socket中获取输入流,读取服务端返回的数据
- InputStream inputStream = socket.getInputStream();
- // 从socket中获取输出流,向服务端写数据
- OutputStream outputStream = socket.getOutputStream();
- // 客户端向服务端写数据
- String s = "我是客户端发来的数据"+ System.nanoTime();
- outputStream.write(s.getBytes());
- // 必须要关闭socket的输出流,告诉对方已经写完,对方用read读才能读到-1,
- // 如果用outputStream.close()会关掉整个socket连接,后续不能再操作
- socket.shutdownOutput();
- // 客户端读服务端返回的数据
- byte[] buf = new byte[1024];
- int len=0;
- StringBuffer sb = new StringBuffer();
- while((len=inputStream.read(buf))!=-1){
- sb.append(new String(buf,0,len));
- }
- System.out.println("在时间点为" + System.nanoTime() + "时客户端读到服务端返回的数据【"+sb+"】");
- socket.close();
- System.out.println("客户端已关闭");

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

控制台输出:
- 服务端控制台输出
- 在时间点为1971871977863049时服务端接收客户端的数据是【我是客户端发来的数据1971871977069907】
- 客户端控制台输出
- 在时间点为1971871978901276时客户端读到服务端返回的数据【服务端返回给客户端的数据,服务端返回数据时间:1971871978031776】
- 客户端已关闭
结论:由输出结果可知,服务端收到了客户端发来的数据,客户端也收到了服务端返回的数据。
注意:写数据之后只有关闭输出流了,对方用read()方法读才有可能返回-1,如若未关闭,读方法一直会被阻塞,直到超时。在socket中关闭流有两种方式
socket.shutdownOutput()
outputStream.close()
二者的区别就是前者是半关闭,后者是将对应的socket连接也关闭了。
对于Socket还有其他知识点,比如NIO,下章将会详细介绍,要想熟悉NIO,Socket必须要掌握,本章只是简单入门。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。