Java中网络编程基础知识(转载/整理)(二)
如何使服务器端支持多个客户端同时工作?
一个服务器端一般都需要同时为多个客户端提供通讯,如果需要同时支持多个客户端,则必须使用前面介绍的线程的概念。简单来说,也就是当服务器端接收到一个连接时,启动一个专门的线程处理和该客户端的通讯。
客户端的程序不需要改变,不过还是贴出来
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; public class MulSocketClient { public static void main(String[] args) { Socket socket = null; InputStream inputStream = null; OutputStream outputStream = null; String serverIP = "127.0.0.1"; int port = 10000; String[] out_data = {"client_first", "client_second", "client_third"}; try { socket = new Socket(serverIP, port); outputStream = socket.getOutputStream(); inputStream = socket.getInputStream(); byte[] bytes = new byte[1024]; for (int i = 0; i < out_data.length; i++) { // 发送数据 outputStream.write(out_data[i].getBytes()); // 接收数据并显示 int n = inputStream.read(bytes); System.out.println(new String(bytes, 0, n)); } } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { outputStream.close(); inputStream.close(); socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
服务器端改成接收到一个连接,便新启动一个线程响应,具体代码如下:
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class MulThreadSocketServer { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub ServerSocket serverSocket = null; Socket socket = null; int port = 10000; try { serverSocket = new ServerSocket(port); System.out.println("The service has started ... "); while (true) { socket = serverSocket.accept(); // start the thread new Thread(new LogicThread(socket)).start(); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { serverSocket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } class LogicThread implements Runnable { InputStream inputStream = null; OutputStream outputStream = null; String[] data_out = {"server1", "server2", "server3"}; String in_message; Socket socket; public LogicThread(Socket socket) { // TODO Auto-generated constructor stub this.socket = socket; } @Override public void run() { // TODO Auto-generated method stub try { inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); byte[] bytes = new byte[1024]; for (int i = 0; i < data_out.length; i++) { int n = inputStream.read(bytes); in_message = new String(bytes, 0, n); System.out.println("Thread id "+Thread.currentThread().getId()+" recieve "+in_message); outputStream.write(data_out[i].getBytes()); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { inputStream.close(); outputStream.close(); socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
运行效果(先启动服务器端程序,然后执行两次客户端程序)(服务器端的打印结果):
The service has started ... Thread id 8 recieve client_first Thread id 8 recieve client_second Thread id 8 recieve client_third Thread id 9 recieve client_first Thread id 9 recieve client_second Thread id 9 recieve client_third
运行效果(先启动服务器端程序,然后执行两次客户端程序)(客户端的打印结果):
server1
server2
server3
UDP方式的网络编程也在Java语言中获得了良好的支持,由于其在传输数据的过程中不需要建立专用的连接等特点,所以在Java API中设计的实现结构和TCP方式不太一样。当然,需要使用的类还是包含在java.net包中。
在Java API中,实现UDP方式的编程,包含客户端网络编程和服务器端网络编程,主要由两个类实现,分别是:
DatagramSocket,DatagramSocket类实现“网络连接”,包括客户端网络连接和服务器端网络连接。虽然UDP方式的网络通讯不需要建立专用的网络连接,但是毕竟还是需要发送和接收数据,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。
DatagramPacket,DatagramPacket类实现对于网络中传输的数据封装,也就是说,该类的对象代表网络中交换的数据。在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据,都必须被处理成DatagramPacket类型的对象,该对象中包含发送到的地址、发送到的端口号以及发送的内容等。其实DatagramPacket类的作用类似于现实中的信件,在信件中包含信件发送到的地址以及接收人,还有发送的内容等,邮局只需要按照地址传递即可。在接收数据时,接收到的数据也必须被处理成DatagramPacket类型的对象,在该对象中包含发送方的地址、端口号等信息,也包含数据的内容。和TCP方式的网络传输相比,IO编程在UDP方式的网络编程中变得不是必须的内容,结构也要比TCP方式的网络编程简单一些。
这里讨论一下Java中TCP方式与UDP方式的不同:这部分来自Java编程艺术
UDP方式中,Datagram中必须指定股数据报的字节长度,这个长度不能超过64KB。而在TCP方式中则没有这个规定,传送的数据以流的形式,不必考虑其长度。因而UDP方式适用于数据长度固定而且字节长度小的情况;TCP方式适用于大批量集中式数据传输。
UDP方式不保证数据报传输时的到达次序,而TCP方式则不存在这个问题。因而UDP方式适用于与数据接收次序无关的应用,TCP方式适用于有序数据流的传输。
UDP方式无需进行专门的计算机间连接操作,而TCP方式在数据传输前必须首先建立计算机间的连接,而且这种连接必须在数据传输器件继续保持,否则抛出IOException。因而UDP方式用于无需监控实时连接状态的应用。
UDP方式不能保证数据传输的可靠性,它适用于可靠度不高的数据传输应用,而且UDP方式速度快,消耗低,经常用于实时应用中。
一个最简单的UDP传输的例子(客户端):
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; public class SimpleUDPclient { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub // 连接对象,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。 // 类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。 DatagramSocket socket = null; // 发送数据报对象,在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据, // 都必须被处理成DatagramPacket类型的对象, // 该对象中包含发送到的地址、发送到的端口号以及发送的内容等。 DatagramPacket send_packet = null; // 接收数据包对象 DatagramPacket receive_packet = null; // 服务器IP String serverIP = "127.0.0.1"; // 服务器端口号 int port = 10000; // 客户端发送数据内荣 String send_message = "Hello"; try { // 该客户端连接使用系统随机分配的一个本地计算机的未用端口号。 // 在该连接中,不指定服务器端的IP和端口, // 所以UDP方式的网络连接更像一个发射器,而不是一个具体的连接。 // 当然,可以通过制定连接使用的端口号来创建客户端连接。 socket = new DatagramSocket(); // 将发送的内容转换为byte数组 byte[] bytes = send_message.getBytes(); // 将服务器IP转换为InetAddress InetAddress address = InetAddress.getByName(serverIP); // 构造发送数据报的内容 send_packet = new DatagramPacket(bytes, bytes.length, address, port); // 发送数据报 socket.send(send_packet); // 构造一个数据缓冲数组,该数组用于存储接收的服务器端反馈数据, // 该数组的长度必须大于或等于服务器端反馈的实际有效数据的长度 byte[] re_bytes = new byte[1024]; // 以该缓冲数组为基础构造一个DatagramPacket数据包对象 receive_packet = new DatagramPacket(re_bytes, re_bytes.length); // 接收数据内容 socket.receive(receive_packet); // 输出接收到的内容 byte[] message = receive_packet.getData(); // 缓冲区数组中只有一部分数据是反馈数据, // 所以需要使用DatagramPacket对象中的getLength方法获得有效数据的长度 int length = receive_packet.getLength(); System.out.println(new String(message, 0, length)); } catch (SocketException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { socket.close(); } } }
一个最简单的UDP传输的例子(服务器端):
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; public class SimpleUDPServer { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub // 连接对象,DatagramSocket实现的就是发送数据时的发射器,以及接收数据时的监听器的角色。 // 类比于TCP中的网络连接,该类既可以用于实现客户端连接,也可以用于实现服务器端连接。 DatagramSocket socket = null; // 发送数据报对象,在UDP方式的网络编程中,无论是需要发送的数据还是需要接收的数据, // 都必须被处理成DatagramPacket类型的对象, // 该对象中包含发送到的地址、发送到的端口号以及发送的内容等。 DatagramPacket send_packet = null; // 接收数据包对象 DatagramPacket receive_packet = null; // 要监听端口号 int port = 10000; // 服务器发送数据内容 String response_message = "World"; try { // 建立连接,监听端口 socket = new DatagramSocket(port); // 构造一个数据缓冲数组,该数组用于存储接收的服务器端反馈数据, // 该数组的长度必须大于或等于服务器端反馈的实际有效数据的长度 byte[] re_bytes = new byte[1024]; // 以该缓冲数组为基础构造一个DatagramPacket数据包对象 receive_packet = new DatagramPacket(re_bytes, re_bytes.length); // 接收数据内容, 和TCP方式的网络编程类似,服务器端的receive方法是阻塞方法, // 如果客户端不发送数据,则程序会在该方法处阻塞 socket.receive(receive_packet); // 输出接收到的内容 byte[] message = receive_packet.getData(); // 缓冲区数组中只有一部分数据是反馈数据, // 所以需要使用DatagramPacket对象中的getLength方法获得有效数据的长度 int length = receive_packet.getLength(); System.out.println(new String(message, 0, length)); // 将发送的内容转换为byte数组 byte[] bytes = response_message.getBytes(); // 获得客户端连接的InetAddress InetAddress address = receive_packet.getAddress(); // 获得客户端连接的端口号 int client_port = receive_packet.getPort(); // 构造发送数据报的内容 send_packet = new DatagramPacket(bytes, bytes.length, address, client_port); // 发送数据报 socket.send(send_packet); } catch (SocketException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { socket.close(); } } }
运行效果:客户端发送Hello,服务器端响应World。
一个支持多客户端的最简单UDP通信的例子(客户端)
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; public class MulUDPClient { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub DatagramSocket socket = null; DatagramPacket send_packet = null; DatagramPacket receive_packet = null; String serverIP = "127.0.0.1"; int port = 10001; String[] send_messages = {"first", "second", "third", "fourth"}; try { socket = new DatagramSocket(); InetAddress address = InetAddress.getByName(serverIP); for (int i = 0; i < send_messages.length; i++) { byte[] bytes = send_messages[i].getBytes(); int length = bytes.length; send_packet = new DatagramPacket(bytes, length, address, port); socket.send(send_packet); System.out.println(send_messages[i]+" is sent ... "); Thread.sleep(1000); byte[] re_bytes = new byte[1024]; receive_packet = new DatagramPacket(re_bytes, re_bytes.length); socket.receive(receive_packet); byte[] receive_bytes = receive_packet.getData(); int receive_bytes_length = receive_packet.getLength(); System.out.println(new String(receive_bytes, 0, receive_bytes_length)); } } catch (SocketException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { socket.close(); } } }
一个支持多客户端的最简单UDP通信的例子(服务器端)
import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; public class MulThreadUDPServer { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub DatagramSocket socket = null; DatagramPacket receive_packet; int port = 10001; try { socket = new DatagramSocket(port); System.out.println("The server is running ... "); while(true) { byte[] bytes = new byte[1024]; receive_packet = new DatagramPacket(bytes, bytes.length); socket.receive(receive_packet); LogicUDPThread udpThread = new LogicUDPThread(socket, receive_packet, bytes); udpThread.start(); } } catch (SocketException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { socket.close(); } } } class LogicUDPThread extends Thread { private DatagramPacket packet; private DatagramSocket socket; private String message = "Roger"; public LogicUDPThread(DatagramSocket socket, DatagramPacket receive_packet, byte[] bytes) { // TODO Auto-generated constructor stub this.socket = socket; this.packet = receive_packet; System.out.println("the thread is started ... "); System.out.println(new String(bytes, 0, bytes.length)); } @Override public void run() { // TODO Auto-generated method stub InetAddress address = packet.getAddress(); System.out.println(address.toString()); int port = packet.getPort(); try { socket.send(new DatagramPacket(message.getBytes(), message.getBytes().length, address, port)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
运行效果(客户端):
first is sent ...
Roger
second is sent ...
Roger
third is sent ...
Roger
fourth is sent ...
Roger
运行效果(服务器端):
The server is running ... the thread is started ... first /127.0.0.1 the thread is started ... second /127.0.0.1 the thread is started ... third /127.0.0.1 the thread is started ... fourth /127.0.0.1