Socket
java中的网络通信是通过Socket实现的,之前说过TCP/IP协议只是一套规则,并不能具体工作,就像程序中的接口一样,而Socket是TCP/IP协议的一个具体的实现。Socket分为ServerSocket和Socket两大类,ServerSocket用于服务端,可以通过方法监听请求,监听到请求后返回Scoket,Socket用于具体完成数据传输,客户端直接使用Socket发起请求并传输数据。
ServerSocket的使用可以分为三步:
1、创建ServerSocket
2、调用创建出来的ServerSocket的accept方法进行监听。accept方法是阻塞方法,也就是说调用accept方法后程序会停下来等待连接请求,在接收到请求前程序将不会往下走,当接收到请求后会返回一个Socket。
3、使用accept方法返回的Socket与客户端进行通信。
public static void main(String[] args) {
try {
//创建一个serverSocket监听一个8080端口
ServerSocket server = new ServerSocket(8089);
//等待请求
Socket socket = server.accept();
//接收到请求后使用socket通信
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = reader.readLine();
System.out.println("收到客户端信息:"+line);
//向客户端发送信息
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.println("我收到了信息:"+line);
pw.flush();
pw.close();
reader.close();
socket.close();
server.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
客户端Socket的用法差不多类似。
public static void main(String[] args) {
String msg = "我是客户端";
try {
Socket cli = new Socket("127.0.0.1", 8089);
PrintWriter pw = new PrintWriter(cli.getOutputStream());
BufferedReader br = new BufferedReader(new InputStreamReader(cli.getInputStream()));
pw.write(msg);
pw.flush();
String line = br.readLine();
System.out.println("收到服务端信息:"+line);
pw.close();
br.close();
cli.close();
} catch (Exception e) {
e.printStackTrace();
}
NioSocket
从JDK1.4开始,java增加了新的io模式——nio,nio在底层采用了新的处理方式,极大的提高了IO的效率。我们使用的Socket也属于IO的一种,nio提供了相应的工具:ServerSocketChannel和SocketChannel,它们分别对应原来的ServerSocket和Socket。
要理解NioSocket的使用必须先理解三个概念:Buffer、Channel和Selector。举个例子。大学时有人卖电话卡,提供送货上门服务,只要有人打电话,他就送过去、收钱在回去,然后等下一个电话,这就相当于普通的Socket处理请求的模式。如果请求不是很多,这是没有问题的。而像现在的电商配送模式——送快递就类似于NioSocket。快递并不会一件一件的送,而是将很多件货一起拿去送,而且在中转站都有专门的分拣员负责按配送范围把货物分给不同的送货员,这样效率就提高了很多。Buffer就是所要送的货物,Channel就是送货员(或者开往某个区域的配货车),Selector就是中转战的分拣员。
NioSocket使用中首先要创建ServerSocketChannel,然后注册Selector,接下来就可以用Selector接收请求并处理了。
ServerSocketChannel可以使用自己的静态工程方法open创建。每个ServerSocketChannel对应一个ServerSocket,可以调用其socket方法来获取,不过如果直接使用获取到ServerSocket来监听请求,那还是原来的处理模式,一般使用获取到的ServerSocket来绑定端口。ServerSocketChannel可以通过configureBlocking方法来设置是否采用阻塞模式,如果要采用非阻塞模式可以用configureBlocking(false)来设置,设置了非阻塞模式之后就可以调用register方法注册Selector来使用了(阻塞模式不可以使用Selector)。
Selector可以使用自己的静态工程方法open创建,创建后通过Channel的register方法注册到ServerSocketChannel或者SocketChannel上,注册完之后Selector就可以通过select方法来等等请求,select方法有一个long类型的参数,代表最长等待时间,如果在这段时间里接收到了相应操作的请求则返回可以处理的请求的数量,否则在超时后返回0,程序继续往下走,如果传入的参数为0或者调用无参数的重载方法,select方法会采用阻塞模式直到有相应操作的请求出现。当接收到请求后Selector调用selectedKeys方法返回SelectedKey的集合。
selectedKey保存了处理当前请求的Channel和Selector,并且提供了不同的操作类型。Channel在注册Selector的时候可以通过register的第二个参数选择特定的操作,这里的操作就是在selectedKey中定义的,一共有4种:
SelectionKey.OP_ACCEPT
SelectionKey.OP_CONNECT
SelectionKey.OP_READ
SelectionKey.OP_WRITE
分别表示接收请求操作、连接操作、读操作和写操作,只有在register方法中注册了相应的操作Selector才会关心相应类型操作的请求。
Channel和Selector并没有谁属于谁的关系,就像数据库里的多对多的关系,不过Selecor这个分拣员分拣的更细,它可以按不同类型来分拣,分拣后的结果保存在SelectionKey中,可以分别通过SelectionKey的channel方法和selector方法来获取对应的Channel和Selector,而且还可以通过isAcceptable、isConnectable、isReadable和isWritable方法来判断是什么类型的操作。
NioSocket中服务端的处理过程可以分为5步:
1、创建ServerSocketChannel并设置相应参数
2、创建Selector并注册到ServerSocketChannel上
3、调用Selector的select方法等待请求
4、Selector接收到请求后使用selectionKeys返回SelectionKey集合
5、使用SelectionKey获取到Channel、Selector和操作类型并进行具体操作
public static void main(String[] args) throws IOException {
//创建ServerSocketChannel,监听8080端口
ServerSocketChannel ssc = ServerSocketChannel.open();
//通过ServerSocket绑定8080端口
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){
//等待请求,每次等待阻塞3s,超过3s后线程继续向下运行,如果传入0或者不传参则一直阻塞
if(selector.select(3000)==0){
System.out.println("等待请求超时");
continue;
}
System.out.println("处理请求");
//获取待处理的SelectionKey
Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
while (keyIter.hasNext()) {
SelectionKey key = (SelectionKey) keyIter.next();
try {
//接收到连接请求时
if(key.isAcceptable()){
handler.handleAccept(key);
}
//读数据
if(key.isReadable()){
handler.handleRead(key);
}
} catch (IOException e) {
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{
//获取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("接收到客户端:"+receivedString);
//返回数据给客户端
String sendString = "收到的数据:"+receivedString;
buffer = ByteBuffer.wrap(sendString.getBytes(localCharset));
sc.write(buffer);
sc.close();
}
}
}