TCP-UDP-线程池

TCP通信过程



主要API








用线程池技术实现的服务器和客户端案例

3.1

Server

package demoServer;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 服务端应用程序
 * @author Administrator
 * 
 */
public class Server {
	//运行在服务端的Socket
	private ServerSocket server;
	//线程池,用于管理客户端连接的交互线程
	private ExecutorService threadPool;
	//保存所有客户端输出流的集合
	private List<PrintWriter> allOut;
	/**
	 * 构造方法,用于初始化服务端
	 * @throws IOException 
	 */
	public Server() throws IOException{
		try {
			/*
			 * 创建ServerSocket时需要指定服务端口
			 */
			System.out.println("初始化服务端");
			server = new ServerSocket(8088);
			//初始化线程池
			threadPool = 
				Executors.newFixedThreadPool(50);
			
			//初始化存放所有客户端输出流的集合
			allOut = new ArrayList<PrintWriter>();
			
			System.out.println("服务端初始化完毕");
			
			
			
			
		} catch (IOException e) {
			e.printStackTrace();
			throw e;
		}
	}
	/**
	 * 服务端开始工作的方法
	 */
	public void start(){
		try{
			/*
			 * ServerSocket的accept方法
			 * 用于监听8088端口,等待客户端的连接
			 * 该方法是一个阻塞方法,直到一个
			 * 客户端连接,否则该方法一直阻塞。
			 * 若一个客户端连接了,会返回该客户端的
			 * Socket
			 */
			while(true){
				System.out.println("等待客户端连接...");
				Socket socket = server.accept();
				/*
				 * 当一个客户端连接后,启动一个线程
				 * ClientHandler,将该客户端的
				 * Socket传入,使得该线程处理与该
				 * 客户端的交互。
				 * 这样,我们能再次进入循环,接收
				 * 下一个客户端的连接了。
				 */
				Runnable handler
					= new ClientHandler(socket);
//				Thread t = new Thread(handler);
//				t.start();
				/*
				 * 使用线程池分配空闲线程来处理
				 * 当前连接的客户端
				 */
				threadPool.execute(handler);
			}
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args){
		Server server;
		try {
			server = new Server();
			server.start();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("服务端初始化失败");
		}	
	}
	
	/**
	 * 服务端中的一个线程,用于与某个客户端
	 * 交互。
	 * 使用线程的目的是使得服务端可以处理多
	 * 客户端了。
	 * @author Administrator
	 *
	 */
	class ClientHandler implements Runnable{
		//当前线程处理的客户端的Socket
		private Socket socket;
		/**
		 * 根据给定的客户端的Socket,创建
		 * 线程体
		 * @param socket
		 */
		public ClientHandler(Socket socket){
			this.socket = socket;
			/*
			 * 通过socket获取远端的地址信息
			 * 对于服务端而言,远端就是客户端了
			 */
			InetAddress address 
						= socket.getInetAddress();
			//获取远端计算机的IP地址
			String ha = address.getHostAddress();
//		address.getCanonicalHostName()
			//获取客户端的端口号
			int port = socket.getPort();
			System.out.println(
							ha+":"+port+" 客户端连接了");
		}
		/**
		 * 该线程会将当前Socket中的输入流获取
		 * 用来循环读取客户端发送过来的消息。
		 */
		public void run() {
			/*
			 * 定义在try语句外的目的是,为了在
			 * finally中也可以引用到
			 */
			PrintWriter pw = null;
			try{
				/*
				 * 为了让服务端与客户端发送信息,
				 * 我们需要通过socket获取输出流。
				 */
				OutputStream out
					= socket.getOutputStream();
				//转换为字符流,用于指定编码集
				OutputStreamWriter osw
					= new OutputStreamWriter(
												out,"UTF-8");
				//创建缓冲字符输出流
				pw = new PrintWriter(osw,true);
				
				/*
				 * 将该客户端的输出流存入共享集合
				 * 以便使得该客户端也能接收服务端
				 * 转发的消息
				 */
				allOut.add(pw);
				
				
				/*
				 * 通过刚刚连上的客户端的Socket获取
				 * 输入流,来读取客户端发送过来的信息
				 */
				InputStream in 
					=	socket.getInputStream();
				/*
				 * 将字节输入流包装为字符输出流,这样
				 * 可以指定编码集来读取每一个字符
				 */
				InputStreamReader isr
					= new InputStreamReader(
												in,"UTF-8");
				/*
				 * 将字符流转换为缓冲字符输入流
				 * 这样就可以以行为单位读取字符串了 
				 */
				BufferedReader br
					= new BufferedReader(isr);
				
				String message = null;
				//读取客户端发送过来的一行字符串
				/*
				 * 读取客户端发送过来的信息这里
				 */
				while((message = br.readLine())!=null){
//					System.out.println(
//							"客户端说:" + message);
					pw.println(message);
				}
							
			}catch(Exception e){
				//在Windows中的客户端,
				//报错通常是因为客户端断开了连接
				
			}finally{
				/*
				 * 首先将该客户端的输出流从共享
				 * 集合中删除。
				 */
				allOut.remove(pw);
				/*
				 * 无论是linux用户还是windows
				 * 用户,当与服务端断开连接后
				 * 我们都应该在服务端也与客户端
				 * 断开连接
				 */
				try {
					socket.close();
				} catch (IOException e) {
				}
				System.out.println(
							"一个客户端下线了...");
			}
			
		}
		
	}
	
}







3.2
client


package demoClient;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * 客户端应用程序
 * @author Administrator
 *
 */
public class Client {
	//Socket,用于连接服务端的ServerSocket
	private Socket socket;
	/**
	 * 客户端构造方法,用于初始化客户端
	 * @throws Exception 
	 */
	public Client() throws Exception{	
		try {
			/*
			 * 创建Socket对象时,就会尝试根据
			 * 给定的地址与端口连接服务端。
			 * 所以,若该对象创建成功,说明与
			 * 服务端连接正常。
			 * 
			 *
			 * 
			 */
			System.out.println("正在连接服务端...");
			socket = new Socket(
									"localhost",8088);
			System.out.println("成功连接服务端");
		} catch (Exception e) {
			throw e;
		}	
	}
	/**
	 * 客户端启动方法
	 */
	public void start(){
		try{
			//创建并启动线程,来接收服务端的消息
			Runnable runn = 
				new GetServerInfoHandler();
			Thread t = new Thread(runn);
			t.start();
			
			
			/*
			 * 可以通过Socket的getOutputStream()
			 * 方法获取一条输出流,用于将信息发送
			 * 至服务端
			 */
			OutputStream out = 
							socket.getOutputStream();
			/*
			 * 使用字符流来根据指定的编码集将字符串
			 * 转换为字节后,在通过out发送给服务端
			 */
			OutputStreamWriter osw
				= new OutputStreamWriter(
													out,"UTF-8");
			/*
			 * 将字符流包装为缓冲字符流,就可以
			 * 按行为单位写出字符串了
			 */
			PrintWriter pw 
				= new PrintWriter(osw,true);
			
			
			
			/*
			 * 创建一个Scanner,用于接收用户
			 * 输入的字符串
			 */
			Scanner scanner 
						= new Scanner(System.in);
			while(true){
				String str = scanner.nextLine();
				pw.println(str);
			}	
			
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args){
		try{
			Client client = new Client();
			client.start();
		}catch(Exception e){
			e.printStackTrace();
			System.out.println("客户端初始化失败");
		}
	}
	/**
	 * 该线程的作用是循环接收服务端发送过来
	 * 的信息,并输出到控制台
	 * @author Administrator
	 */
	class GetServerInfoHandler 
								implements Runnable{
		public void run() {
			try{
				/*
				 * 通过Socket获取输入流
				 */
				InputStream in
					= socket.getInputStream();
				//将输入流转换为字符输入流,指定编码
				InputStreamReader isr
					= new InputStreamReader(
												in,"UTF-8");
				//将字符输入流转换为缓冲流
				BufferedReader br
					= new BufferedReader(isr);
				
				String message = null;
				//循环读取服务端发送的每一个字符串
				while((message=br.readLine())!=null){
					//将服务端发送的字符串输出到控制台
					System.out.println(message);
				}		
			}catch(Exception e){
				
			}
		}
		
	} 
	
}



UDP


4.1

客户端通信过程

/*
* 向服务端发送数据的步骤:
* 1:创建好Socket(一次就行)
* 2:准备数据
* 3:创建数据包
* 4:将数据存入包中 (3,4是一步完成的)
* 5:将数据包通过socket发送给服务端
*/



代码

package demoServer.udp;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * 客户端
 * @author Administrator
 *
 */
public class Client {
	/**
	 * 客户端的启动方法
	 */
	public void start(){
		try{
			/*
			 * 向服务端发送数据的步骤:
			 * 1:创建好Socket(一次就行)
			 * 2:准备数据
			 * 3:创建数据包
			 * 4:将数据存入包中 (3,4是一步完成的)
			 * 5:将数据包通过socket发送给服务端
			 */
			DatagramSocket socket
						= new DatagramSocket();
			
			String str = "你好!服务端!";
			byte[] data 
						= str.getBytes("UTF-8");
			
			//打包:准备包裹,填写地址,装入数据
			InetAddress address
				= InetAddress.getByName(
													"localhost");
			int port = 8088;	
			//创建发送包
			DatagramPacket sendPacket
				= new DatagramPacket(
					data,
					data.length,
					address,
					port
				);
			//将包发送出去
			socket.send(sendPacket);
			
			/*
			 * 接收服务端发送回来的信息
			 */
			data = new byte[100];
			DatagramPacket recvPacket
				= new DatagramPacket(
						data,
						data.length
				);
			//接收数据到包中
			//注意,该方法是个阻塞方法。
			socket.receive(recvPacket);
			
			//拆包拿数据
			byte[] d = recvPacket.getData();
			//有效数据长度
			int dlen = recvPacket.getLength();
			/**
			 * String(
			 * 	byte[] b,
			 * 	int offset,
			 *  int len,
			 *  String charsetName)
			 *  将给定的字节数组中,从offset处
			 *  开始连续len个字节,再根据给定的字符集
			 *  转换为字符串
			 */
			String info 
				= new String(d,0,dlen,"UTF-8");
			System.out.println("服务端说:"+info);
			
			
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args){
		Client client = new Client();
		client.start();
	}
}


4.2

服务器通信过程


/*
* 接收包的步骤:
* 1:创建Socket(一次)
* 2:创建一个合适大小的包
* 3:通过socket接收数据到包中
* 4:拆包取数据
*/

代码


package demoClient.udp;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * 服务端
 * @author Administrator
 *
 */
public class Server {
	/**
	 * 服务端的启动方法
	 */
	public void start(){
		try{
			/*
			 * 接收包的步骤:
			 * 1:创建Socket(一次)
			 * 2:创建一个合适大小的包
			 * 3:通过socket接收数据到包中
			 * 4:拆包取数据
			 */
			DatagramSocket socket
					= new DatagramSocket(8088);
			
			byte[] data = new byte[100];
			DatagramPacket recvPacket
				= new DatagramPacket(
						data,
						data.length
				);
			//接收数据到包中
			//注意,该方法是个阻塞方法。
			socket.receive(recvPacket);
			
			//拆包拿数据
			byte[] d = recvPacket.getData();
			//有效数据长度
			int dlen = recvPacket.getLength();
			/**
			 * String(
			 * 	byte[] b,
			 * 	int offset,
			 *  int len,
			 *  String charsetName)
			 *  将给定的字节数组中,从offset处
			 *  开始连续len个字节,再根据给定的字符集
			 *  转换为字符串
			 */
			String info 
				= new String(d,0,dlen,"UTF-8");
			System.out.println("客户端说:"+info);
			
			//回复客户端
			String str = "你好!客户端!";
			data = str.getBytes("UTF-8");
			
			//打包:准备包裹,填写地址,装入数据
			InetAddress address
				= recvPacket.getAddress();
			int port = recvPacket.getPort();	
			//创建发送包
			DatagramPacket sendPacket
				= new DatagramPacket(
					data,
					data.length,
					address,
					port
				);
			//将包发送出去
			socket.send(sendPacket);
			
			
			
			
		}catch(Exception e){
			e.printStackTrace();
		}		
	}
	public static void main(String[] args){
		Server server = new Server();
		server.start();
	}
}



猜你喜欢

转载自blog.csdn.net/jk823394954/article/details/78955811