Top
  1. TCP通信
  2. UDP通信

1. TCP通信

1.1. Socket通讯模型

1.1.1. Server端多线程模型

通过上一节我们已经知道了如何使用ServerSocket与Socket进行通讯了,但是这里存在着一个问题,就是只能“p2p”点对点。一个服务端对一个客户端。若我们想让一个服务端可以同时支持多个客户端应该怎么做呢?这时我们需要分析之前的代码。我们可以看到,当服务端的ServerSocket通过accept方法侦听到一个客户端Socket连接后,就获取该Socket并与该客户端通过流进行双方的通讯了,这里的问题在于,只有不断的调用accept方法,我们才能侦听到不同客户端的连接。但是若我们循环侦听客户端的连接,又无暇顾及与连接上的客户端交互,这时我们需要做的事情就是并发。我们可以创建一个线程类ClientHandler,并将于客户端交互的工作全部委托线程来处理。这样我们就可以在当一个客户端连接后,启动一个线程来负责与客户端交互,而我们也可以循环侦听客户端的连接了。

我们需要对服务端的代码进行修改:

/**
 *  Server端应用程序*
 */
public class Server {
	public static void main(String[] args) {
		ServerSocket server = null;
		try {
			//创建ServerSocket并申请服务端口为8088
			server = new ServerSocket(8088);
			
			while(true){
				//循环侦听客户端的连接
				Socket socket = server.accept();
				//当一个客户端连接后,启动线程来处理该客户端的交互
				new ClientHandler(socket).start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			if(server != null){
				try {
					server.close();
				} catch (IOException e) {
				}
			}
		}
	}
}
/**
 * 线程类
 * 该线程的作用是并发与客户端进行交互
 * 这里的代码就是原来在Server中客户端连接后交互的代码
 */
class ClientHandler extends Thread{
	private Socket socket;
	public ClientHandler(Socket socket){
		this.socket = socket;
	}
	public void run(){
		try {
			//获取输入流,用于读取客户端发送过来的消息
			InputStream in = socket.getInputStream();
			BufferedReader reader
				= new BufferedReader(
					new InputStreamReader(
						in,"UTF-8"
					)
				);
			
			//获取输出流,用于向该客户端发送消息
			OutputStream out = socket.getOutputStream();
			PrintWriter writer
				= new PrintWriter(
					new OutputStreamWriter(
						out,"UTF-8"	
					),true
				);
			
			//读取客户端发送的消息
			String message = reader.readLine();
			System.out.println("客户端说:"+message);
			
			//向客户端发送消息
			writer.println("你好客户端!");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

经过上面的改动,我们再次启动服务端,这个时候我们会发现,我们启动若干客户端都可以被服务器所接受并进行交互了。

2. UDP通信

2.1. DatagramPacket

2.1.1. 创建接收包

DatagramPacket:UDP数据报基于IP建立的,每台主机有65536个端口号可以使用。数据报中字节数限制为65536-8 。包含8字节的头信息。

构造接收包:

	DatagramPacket(byte[] buf, int length)

将数据包中Length长的数据装进Buf数组。

	DatagramPacket(byte[] buf, int offset, int length) 

将数据包中从Offset开始、Length长的数据装进Buf数组。

2.1.2. 创建发送包

构造发送包:

	DatagramPacket(byte[] buf, int length, InetAddress clientAddress, int clientPort)

从Buf数组中,取出Length长的数据创建数据包对象,目标是clientAddress地址,clientPort端口,通常用来发送数据给客户端。

	DatagramPacket(byte[] buf, int offset, int length, InetAddress clientAddress, int clientPort)

从Buf数组中,取出Offset开始的、Length长的数据创建数据包对象,目标是clientAddress地址,clientPort端口,通常用来发送数据给客户端。

2.2. DatagramSocket

2.2.1. 服务端接收

DatagramSocke用于接收和发送UDP的Socket实例 。

	DatagramSocket(int port)

创建实例,并固定监听Port端口的报文。通常用于服务端。

其中方法:

	   receive(DatagramPacket d)

接收数据报文到d中。receive方法产生 “阻塞”。会一直等待知道有数据被读取到。

2.2.2. 客户端发送

无参的构造方法DatagramSocket()通常用于客户端编程,它并没有特定监听的端口,仅仅使用一个临时的。程序会让操作系统分配一个可用的端口。

其中方法:

	send(DatagramPacket dp)

该方法用于发送报文dp到目的地。

代码如下:

	/**
	 *  Server端程序
	 */
	public class Server {
	public static void main(String[] args) {
		DatagramSocket socket = null;
		try {
			socket = new DatagramSocket(8088);//申请8088端口
			byte[] data = new byte[1024];
			DatagramPacket packet
				= new DatagramPacket(data, data.length);//创建接收包
			socket.receive(packet);//会产生阻塞,读取发送过来的数据
			String str = new String(packet.getData(),0,packet.getLength());//从包中取数据
			System.out.println(str);
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			if(socket != null){
				socket.close();//关闭释放资源
			}
		}
	}
}
/**
 *  Client端程序
 */
public class Client {
	public static void main(String[] args) {
		DatagramSocket socket = null;
		try {
			socket = new DatagramSocket();//创建Socket
			byte[] data = "你好服务器!".getBytes();
			DatagramPacket packet = new DatagramPacket(
										data, 
										data.length,
										InetAddress.getByName("localhost"),
										8088
									);//创建发送包
			socket.send(packet);//发送数据
			
			
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			if(socket != null){
				socket.close();//关闭以释放资源
			}
		}
	}
}