Socket与NioSocket

Socket

  • java中的socket可以分为
    • Socket(普通)
    • NioSocket

普通Socket的用法

java中的网络通信是通过Socket实现的,Socket分为:

  • ServerSocket
    • 用于服务端,通过accept方法监听请求,监听到请求后返回Socket
  • Socket
    • 用于具体完成数据传输,客户端直接使用Socket发起请求并传输数据

ServerSocket的使用可以分为三步

  1. 创建ServerSocket。
  • ServerSocket的构造方法一共有5个,这里使用ServerSocket(int port)
  1. 调用创建出来的ServerSocket的accept方法进行监听。
  • accept方法是阻塞方法,调用accept方法后程序会停下来等待连接请求,再接收到请求前,程序将不会往下走。
  • 当接收到请求后accept方法会返回一个Socket
  1. 使用accept方法返回的Socket与客户端进行通信
  2. 先启动Server然后启动Client即可完成一次通信。
public class Server {
	public static void main(String[] args){
		try {
			//创建一个ServerSocket监听8080端口
			ServerSocket server = new ServerSocket(8080);		
			//等待请求
			Socket socket = server.accept();
			//接收到请求后使用socket进行通信,创建BufferedReader用于读取数据
			BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String line = is.readLine();
			System.out.println("received from client:"+line);
			//创建PrintWriter,用于发送数据
			PrintWriter pw = new PrintWriter(socket.getOutputStream());
			pw.println("received data:"+line);
			pw.flush();
			//关闭资源
			pw.close();
			is.close();
			socket.close();
			server.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
public class Client {
	public static void main(String[] args){
		String msg = "Client Data";
		try {
			//创建一个Socket,与本机的8080端口连接
			Socket socket = new Socket("127.0.0.1",8080);
			//使用Socket创建PrintWriter和BufferedReader进行读写数据
			PrintWriter pw = new PrintWriter(socket.getOutputStream());
			BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			//发送数据
			pw.println(msg);
			pw.flush();
			//接受数据
			String line = is.readLine();
			System.out.println("received from server:"+line);
			//关闭资源
			pw.close();
			is.close();
			socket.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

NioSocket的用法

  • NioSocket使用中首先要创建ServerSocketChannel,然后注册Selector,接下来就可以用Selector接受请求并处理
  • ServerSocketChannel可以使用自己的静态工厂方法open创建
  • 每个ServerSocketChannel对应一个ServerSocket
  • 可以调用socket方法获取,但一般使用获取到的ServerSocket来绑定端口
  • ServerSocketChannel可以通过configureBlocking设置是否采用阻塞模式
    • configureBlocking(false) 非阻塞
    • 采用非阻塞模式之后既可调用register方法注册Selector来使用了(阻塞模式不可使用Selector)
  • Selector通过静态工厂方法open创建,创建后通过Channel的register方法注册到ServerSocketChannel或者SocketChannel上
  • 注册完后,Selector就可通过select方法等待请求,select有一个long类型参数,代表最长等待时间,如果在这段时间里接收到了相应操作的请求则返回可以处理的请求数量,否则在超时后返回0,程序继续往下走,如果传入的参数为0或者调用无参数的重载方法,select方法会采用阻塞模式知道有相应操作的请求出现。
  • 当接收到请求后Selector调用selectedKeys方法返回SelectionKey的集合
  • SelectionKey保存了处理当前请求的Channel和Selector,并提供了不同的操作类型。Channel在注册Selector时可通过register的第二个参数选择特定的操作,该操作就是在SelectionKey中定义的。
  • SelectionKey中定义了4种操作
    • SelectionKey.OP_ACCEPT 请求操作
    • SelectionKey.OP_CONNECT 连接操作
    • SelectionKey.OP_READ 读操作
    • SelectionKey.OP_WRITE 写操作

NioSocket中服务端的处理过程可以分为5步:

  1. 创建ServerSocketChannel并设置相应参数。
  2. 创建Select并注册到ServerSocketChannel上。
  3. 调用Selector的Select方法等待请求。
  4. Selector接收到请求后使用selectedKeys返回SelectionKey集合。
  5. 使用SelectionKey获取到Channel、Selector和操作类型并进行具体操作。
public class NIOServer {
	public static void main(String[] args) throws Exception{
		//创建ServerSocketChannel,监听8080端口
		ServerSocketChannel ssc = ServerSocketChannel.open();
		ssc.socket().bind(new InetSocketAddress(8080));
		//设置为非阻塞模式
		ssc.configureBlocking(false);
		//为ssc注册选择器
		Selector selector = Selector.open();
		ssc.register(selector, SelectionKey.OP_ACCEPT);
		//创建处理器
		Handler handler = new Handler(1024);
		while(true){
			if (selector.select(3000)==0) {
				System.out.println("等待请求超时");
				continue;
			}
			System.out.println("处理请求...");
			//获取待处理的SelectionKey
			Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
			
			while(keyIter.hasNext()){
				SelectionKey key = keyIter.next();
				try {
					//接收到连接请求时
					if (key.isAcceptable()) {
						handler.handleAccept(key);
					}
					//读数据
					if (key.isReadable()) {
						handler.handleRead(key);
					}
				} catch (Exception e) {
					// TODO: handle exception
					keyIter.remove();
					continue;
				}
				//处理完后,从待处理的SelectionKey迭代器中移除当前所使用的key
				keyIter.remove();
			}
		}
	}
	private static class Handler{
		private int bufferSize = 1024;
		private String localCharset = "UTF-8";
		public Handler(){}
		public Handler(int bufferSize) {
			this(bufferSize,null);
		}
		public Handler(String localCharset) {
			this(-1,localCharset);
		}
		public Handler(int bufferSize, String localCharset) {
			if (bufferSize > 0) {
				this.bufferSize = bufferSize;
			}
			if (localCharset != null) {
				this.localCharset = localCharset;
			}
		}
		
		public void handleAccept(SelectionKey key)throws IOException{
			SocketChannel sc = ((ServerSocketChannel)key.channel()).accept();
			sc.configureBlocking(false);
			sc.register(key.selector(), SelectionKey.OP_READ,ByteBuffer.allocate(bufferSize));
		}

		public void handleRead(SelectionKey key) throws IOException{
			//获取Channel
			SocketChannel sc = (SocketChannel)key.channel();
			//获取buffer并重置
			ByteBuffer buffer = (ByteBuffer)key.attachment();
			buffer.clear();
			//没有读到内容则关闭
			if (sc.read(buffer)==-1) {
				sc.close();
			}else {
				//将buffer转换为读状态
				buffer.flip();
				//将buffer中接收到的值按localCharset格式编码保存到receivedString
				String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
				System.out.println("received from client:"+receivedString);
				//返回数据给客户端
				String sendString = "received data:"+receivedString;
				buffer = ByteBuffer.wrap(sendString.getBytes(localCharset));
				sc.write(buffer);
				//关闭Socket
				sc.close();		
			}
			
		}
		
		
	}
}
  • main方法启动监听

  • 当监听到请求时根据SelectionKey的状态交给内部类Handler进行处理

  • Handler可以通过重载的构造方法设置编码格式和每次读取数据的最大值

  • Handler处理过程中用到了Buffer

  • Buffer是java.nio包中的类,用于存储数据,其中有4个重要属性

    • capacity:容量,在创建时设置,使用过程中不可改变

    • limit:可以使用的上限,开始创建时limit和capacity的值相同,其值不可以超过capacity。

    • position:当前所操作元素所在的索引位置,position从0开始,随着get和put方法自动更新

    • mark:用来暂时保存position的值,position的值保存到mark后就可以修改并进行相关的操作,操作完后可以通过reset方法将mark的值恢复到position。

      • mark默认值为-1,且其值必须小于position的值,若传入的position值小于mark,则将mark设为-1
    • 这四个属性的大小关系为:mark<=position<=limit<=capacity

  • NioServer用到的clear和flip方法

    • clear:重新初始化limit、position、mark三个属性
    • flip:读取保存的数据
    public final Buffer clear() {
	position = 0;
	limit = capacity;
	mark = -1;
	return this;
    }

    public final Buffer flip() {
	limit = position;
	position = 0;
	mark = -1;
	return this;
    }
posted @ 2021-09-07 20:08  Ueao  阅读(267)  评论(0)    收藏  举报