Socket可以实现两个服务之间通信,数据传输.
SocketChannel的作用和Socekt一样,在Java1.4版本引入. 相当于是升级版Socekt,性能比Socket更好.
Socket:服务端通过accept()为每个客户端Socket请求分配一个线程,而每个线程都有可能长时间阻塞,过多的线程会影响服务器的性能.ServerSocket是通过accept()来接受请求,如果不存在,会处于阻塞状态,保持长连接
SocketChannel:方便使用非阻塞通信,服务器端只需要一个线程就能处理客户端所有的Socket请求. ServerSocketChannel是通过注册一个selector选择处理器. selector.select()来查看是否有数据请求接入.
补充下阻塞非阻塞/同步异步的概念:
下面为图形化表示,来体现多个用户端请求服务端时,Socekt和SocketChannel的处理方式
目前我只是理解了,多个请求时,又于Socket的accept阻塞和SocketChannel 的accept/读写方法的非阻塞带来的处理效率的差异,
并无法理解ServerSocket处理多个客户端Socket请求时针对每个请求会新创建1个线程来处理的线索和原因.
#服务器端
package com.glperry.demo.socket;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* Socket服务端
* @Date: 2019-08-30
*/
public class SocketServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null; //开放的套接字
Socket acceptSocket = null; //连接客户端的套接字
Scanner scn = null; //键盘输入流
Scanner fromClient = null; //获取从客户端读取的字节流
PrintWriter toClient = null;
//开放服务端套接字
serverSocket = new ServerSocket(9999);
System.out.println("服务器Socket已启动,等待客户端连接...");
//连接客户端的套接字
acceptSocket = serverSocket.accept();
//监控是否有客户接入
System.out.println("有客户端接入...");
//实例化 从客户端读取的字节流
fromClient = new Scanner(acceptSocket.getInputStream());
//实例化 发送到客户端的字节流
toClient = new PrintWriter(acceptSocket.getOutputStream());
toClient.println("菠萝皓你好!欢迎接入服务器");
toClient.flush();
//实例化 键盘输入流
scn = new Scanner(System.in);
//阻塞等待客户端发送的数据
while (fromClient.hasNext()){
String fromClientData = fromClient.nextLine();
System.out.println("小徐:"+fromClientData);
//获取键盘输入流的数据
String toClientData = scn.nextLine();
toClient.println(toClientData);
toClient.flush();
}
}
}
#客户端
package com.glperry.demo.socket;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
/**
* Socket客户端
* @Date: 2019-08-30
*/
public class SocketClient {
public static void main(String[] args) throws IOException {
Socket socket = null; //与服务器连接的 套接字
Scanner scn = null; //键盘输入流
Scanner fromServer = null; //获取到的服务器发送的键盘字节流
PrintWriter toServer = null; //向服务器发送的字节流
//连接服务器的套接字
socket = new Socket("192.168.10.37",9999);
//注意,服务器端和客户端不能都先获取输入流,那样会导致socket阻塞
//实例化从服务器读的字节流
fromServer = new Scanner(socket.getInputStream());
//实例化向服务器读的字节流
toServer = new PrintWriter(socket.getOutputStream());
//实例化键盘输入流
scn = new Scanner(System.in);
while(fromServer.hasNext()){
//阻塞等待服务器发送消息
String fromServerData = fromServer.nextLine();
System.out.println("服务器端:"+fromServerData);
//获取输入的数据
String toServerData = scn.nextLine();
//发送输入的数据
toServer.println(toServerData);
toServer.flush();
}
}
}
#服务器端
package com.glperry.demo.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* 服务端,接收数据
* @Date: 2019-08-29
*/
public class NioServer{
public static void main(String[] args) throws IOException {
//打开一个服务连接通道
ServerSocketChannel channel = ServerSocketChannel.open();
//创建服务连接地址
InetSocketAddress socketAddress = new InetSocketAddress(8888);
//通道绑定服务地址
channel.bind(socketAddress);
//设置服务端通道非阻塞
channel.configureBlocking(false);
//打开选择处理器
Selector selector = Selector.open();
//模拟多个用户,使用selector选择器进行响应
//首先第一步进来的所有连接都是为了接入
channel.register(selector,SelectionKey.OP_ACCEPT);
//selector 处理接入
while (selector.select()>0){ //阻塞体现在Selector上
//可能客户到请求一次进来多个,进行判断
Iterator iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
//获取其中的每个请求
SelectionKey next = iterator.next();
//判断请求是属于 连接过的还是未连接过的
if(next.isAcceptable()){ //没连接过,直接跟踪,设置为读取状态
SocketChannel accept = channel.accept();//通道允许接入请求
accept.configureBlocking(false);//设置非阻塞
accept.register(selector,SelectionKey.OP_READ); //设置为读取数据
}
if(next.isReadable()){ //连接过,直接读取
SelectableChannel channel2 = next.channel();
channel2.configureBlocking(false);
//读取通道信息
readMsg(channel2);
}
iterator.remove();
}
}
}
private static void readMsg(SelectableChannel channel2) throws IOException {
SocketChannel channel = (SocketChannel) channel2;
SocketAddress localAddress = channel.getLocalAddress();
//设置缓存区
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取数据
int len;
byte[] b = new byte[1024];
while ((len = channel.read(buffer))>0){
buffer.flip();//刷新缓存区
buffer.get(b, 0, len);
System.out.println("服务端接受到数据为:"+new String(b,0,len));
}
}
}
#客户端
package com.glperry.demo.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Scanner;
/**
* 客户端 发送数据
* @Date: 2019-08-29
*/
public class NioClient {
public static void main(String[] args) throws IOException {
//声明连接地址对象 nio框架使用tcp协议绑定ip和端口
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8888);
//打开一个和服务端连接的通道
SocketChannel channel = SocketChannel.open();
channel.connect(socketAddress);
//设置通道为非阻塞
channel.configureBlocking(false);
//设置缓存区大小
ByteBuffer buffer = ByteBuffer.allocate(1024);
//控制台的输入数据输出
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
//清除缓存区的数据
buffer.clear();
//获取控制台的数据
String data = scanner.nextLine();
buffer.put(data.getBytes());
//刷新 缓存区的数据,与IO流的flush类似
buffer.flip();
channel.write(buffer);
}
}
}