Java网络编程UDP与TCP(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:

        接收端:

// 创建接收端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必须要掌握,本章只是简单入门。



        

猜你喜欢

转载自blog.csdn.net/yy455363056/article/details/80210461