服务器的基本写法:
//创建一个Channel,这个服务器要绑定端口和Ip地址,这个客户端只连接这个Ip和端口就可了
ServerSocketChannel channel=ServerSocketChannel.open();
//绑定端口和地址
channel.bind(new InetSocketAddress(InetAddress.getByName("192.168.213.1"),8888));
//设置为异步模式,也就是不阻塞的模式
channel.configureBlocking(false);
while(true){
//进行监听,没有客户端连接返回为null
SocketChannel ss=channel.accept();
//打印
System.out.println("server:"+ss);
//设置一下时间的间隔
Thread.sleep(2000);
}
客户端的基本写法:
public static void main(String[] args) throws Exception {
//创建一个客户端的Socket的Channel的接口,这个对象的创建也是要通过静态方法Open来创建的,同理这个方法只是打开了
//连接,还没有绑定到具体的Server
SocketChannel sc=SocketChannel.open();
//设置一下我们的客户端的连接方式
sc.configureBlocking(false);
//进行具体的绑定操作
SocketAddress address=new InetSocketAddress("localhost",8888);
//进行连接服务器的操作,同时这个方法返回的这个客户端是否成功连接到Socket上
//如果是阻塞模式,那么则ture表示已经成功连接,对于非阻塞模式的返回的false表示正在连接,因为服务器响应要一点时间
boolean b=sc.connect(address);
//finishConnect()表示方法的是完成我们连接的处理
//也就是说这个方法会尝试去连接,直到成功
while(sc.finishConnect()==false){
System.out.println("正在连接...");
}
System.out.println("连接已经完成...");
System.out.println("client:"+b);
}
高级的Selector的基本的操作:
我们的服务器的练习代码如下:
public class MySelectionServerChannel {
public static void main(String[] args) throws Exception {
// 先创建一个通道挑选器对象,在这个NIO当中大部分的对象都是用的是Open方法来创建的,同理下面的这个方法也是这样子的
// 这个通道挑选器对象一创建就会维护了三个Key对象集合
Selector selector = Selector.open();
// 创建一个服务器Channel的通道,使用静态的Open方法来创建一个已经连接的ServerSocketChannel的对象,
// 但是他还没有绑定
ServerSocketChannel ssc = ServerSocketChannel.open();
// 进行绑定我们的服务器的端口和ip地址,但是这个ip地址只能是本地的ip地址,不能是别的地方的地址,因为在我们的
// Socket编程中的SocketServer只叫我们的绑定一个端口号就可以了,所以说不能这个地址只能是本地的地址
ssc.bind(new InetSocketAddress("localhost", 8888));
// 进行配置一下我们的NIO的通信的方式,是否为阻塞模式,在我们的java io当中的大部分有关IO操作都是阻塞的
// 所以他的性能有点low,因为如果我们的想要做多件事的话就只能开多个线程,但是在cpu的在多个线程之间变换的开销
// 是相当的大的,所以NIO的主要就是用非阻塞的模式进行开发
// 方法解释:ture为阻塞,false:为非阻塞
ssc.configureBlocking(false);
// 把这个通道向我们的Selector来注册,同时向我们的Selector注册的通道一定要是非阻塞的可以
// 同时在把我们的通道向我们的Selector注册时要指定我们的这个通道的动作key(也就是感兴趣的事件)
// 目的是为了在我们的Selector进行挑选select()时把一遍一遍的轮询(while(true)),如果找到我们的这个事件和我们的通道
// 注册时的事件一致,也就是事件发生时,就会把我们的发送这个这个key给挑选形成一个SelectedKey
// 这个注册的操作op只有4种,都是在我们的SelectionKey中的成员,分别是OP_ACCEPT,OP_CONNECT,OP_READ,OP_WRITE
// 但是对于我们的服务器来说只会用到一个事件OP_ACCEPT
ssc.register(selector, SelectionKey.OP_ACCEPT);
// 客户端的Channel
SocketChannel ss = null;
// 定义我们的客户的字节缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (true) {
// 进行挑选工作,这个方法是阻塞的,所以在通道挑选器的所有通道只有发生一个事件时,都会向下走
selector.select();
// 进行取出我们的相应的key,返回的是selectedKey的集合
Set<SelectionKey> selKeys = selector.selectedKeys();
// 进行取出相应的Key的操作
for (Iterator<SelectionKey> it = selKeys.iterator(); it.hasNext();) {
SelectionKey key = it.next();
// 如果这个key(也就是这个通道)表示的连接的动作,这个是我们的服务器特有的方法
if (key.isAcceptable()) {
// 得到我们的注册的通道,同时这个通道是我们的服务器的通道
ServerSocketChannel sschannel = (ServerSocketChannel) key.channel();
// 进行不断的客户端接收请求
// 接收客户端接收的请求,得到我们的客户端的socketChannel对象
ss = sschannel.accept();
// 同时这个ss也要设置为非阻塞的模式,不然会出现问题的
ss.configureBlocking(false);
// 然后把我们的客户端的主要的SocketChannel也给注册到我们的选择器当中,
// 在我们的selector的选择器当中可以进行标明三种感兴趣事件
ss.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE);
System.out.println("Server:我们的客户端发送请求了!!!");
}
if (key.isConnectable()) {
ss = (SocketChannel) key.channel();
System.out.println("server:有发送了可以连接的对象...");
}
// 可以读取客户发送过来的数据
if (key.isReadable()) {
ss = (SocketChannel) key.channel();
// 进行clear操作,目的是为放开我们buffer的限制,同时重新开始读入数据到字节缓冲区
// position=0;limit=capity;
buffer.clear();
// 进行读取客户发送过来的数据,当我们的服务器没有发送数据过来时,这个read读取数据返回值为0,所以这个就有点用while就有点不好了
int len = ss.read(buffer);
if (len == -1) {
// 如果等于-1,说明数据已经读取完成了,那么这个通道也就可以关闭了
key.cancel();
} else if (len >= 0) {
// 进行拍板操作
buffer.flip();// limit=position;position=0;
// 把我们的字节缓冲区转换成字节数据,目的是为了转换成字符串
byte byt[] = buffer.array();
String str = new String(byt, 0, buffer.limit());
System.out.println("server:接收我们的客户端的数据为:" + str);
}
}
// 给我们的客户端发送数据
if (key.isWritable()) {
ss = (SocketChannel) key.channel();
// 首先清空缓冲区的数据position=0;limit=capital;
buffer.clear();
// 给我们的缓冲区中加入数据
buffer.put("hello I'm Server".getBytes());
// 进行拍板,这样子的话就不会写入缓冲区当中不是我们的数据给客户端了
buffer.flip();
// 进行把数据写入缓冲区当中
ss.write(buffer);
// buffer.clear();
System.out.println("Server:正在给我们的客户端写入数据...");
}
// System.out.println(key);
// 进行清空我们的SelectedKey,因为他不会自动的清除
it.remove();
// Thread.sleep(2000);
}
}
}
}
客户端的操作如下:
简单客户端一:
public class MyClientSendTest {
public static void main(String[] args) throws Exception {
//创建一个客户端的Socket的Channel的接口,这个对象的创建也是要通过静态方法Open来创建的,同理这个方法只是打开了
//连接,还没有绑定到具体的Server
SocketChannel sc=SocketChannel.open();
//设置一下我们的客户端的连接方式
sc.configureBlocking(false);
//进行具体的绑定操作
SocketAddress address=new InetSocketAddress("localhost",8888);
//进行连接服务器的操作,同时这个方法返回的这个客户端是否成功连接到Socket上
//如果是阻塞模式,那么则ture表示已经成功连接,对于非阻塞模式的返回的false表示正在连接,因为服务器响应要一点时间
boolean b=sc.connect(address);
//finishConnect()表示方法的是完成我们连接的处理
//也就是说这个方法会尝试去连接,直到成功
while(sc.finishConnect()==false){
System.out.println("正在连接...");
}
//进行给我们的服务器发送数据
//创建字符缓冲区数据
ByteBuffer buf=ByteBuffer.allocate(1024);
int i=1;
while(true){
buf.put(("I'm Client!!!"+i+"\r\n").getBytes());
i++;
buf.flip();
sc.write(buf);
buf.clear();
}
}
}
简单客户端二:
public class MyClientSendTest2 {
public static void main(String[] args) throws Exception {
//下面我们的要用到是是我们的通道工具方法
ReadableByteChannel inputChannel=Channels.newChannel(System.in);
//创建一个和服务器连接的对象,下面的这个操作包括了连接服务器的操作了
SocketChannel sc=SocketChannel.open(new InetSocketAddress("localhost",8888));
// //把我们的客户端进行连接服务器处理,上面的这一步已经做了下面的这一步了
// sc.connect(remote)
//进行分配一个缓冲区
ByteBuffer buffer=ByteBuffer.allocate(1024);
//现在进行数据的读写操作
while(true){
//把控制台输入的数据输入缓冲区当中
inputChannel.read(buffer);
//把我们的缓冲区当中的数据进行拍板处理
buffer.flip();
//然后把我们的缓冲区中的数据写到服务器当中
sc.write(buffer);
System.out.println("我们的已经向服务器发送了数据!!!");
//进行清空缓冲区处理
buffer.clear();
}
}
}
在我们的客户端使用Selector的代码如下:
public class MySelectionClientChannel {
public static void main(String[] args) throws Exception {
//创建一个客户端通道挑选器,主要是负责特定的客户端接口select的操作
Selector selector=Selector.open();
//创建一个客户端的Socket的Channel的接口,这个对象的创建也是要通过静态方法Open来创建的,同理这个方法只是打开了
//连接,还没有绑定到具体的Server
SocketChannel sc=SocketChannel.open();
//设置一下我们的客户端的连接方式
sc.configureBlocking(false);
//进行具体的绑定操作
SocketAddress address=new InetSocketAddress("localhost",8888);
//进行连接服务器的操作,同时这个方法返回的这个客户端是否成功连接到Socket上
//如果是阻塞模式,那么则ture表示已经成功连接,对于非阻塞模式的返回的false表示正在连接,因为服务器响应要一点时间
boolean b=sc.connect(address);
//finishConnect()表示方法的是完成我们连接的处理
//也就是说这个方法会尝试去连接,直到成功
while(sc.finishConnect()==false){
System.out.println("正在连接...");
}
//下面是我们的客户端口和服务器的主要的通信操作代码,把我们的这个客户端通道注册到我们的通道挑选器当中,在
//客户端主要感兴趣的地方操作是OP_CONNECT,OP_WRITE,OP_READ
sc.register(selector,SelectionKey.OP_CONNECT|SelectionKey.OP_WRITE|SelectionKey.OP_READ);
//创建一个buffer
ByteBuffer buffer=ByteBuffer.allocate(1024);
int count=1;
//创建出具体的通道连接器
//然后进行具体的挑选操作
while(true){
//进行挑选出key,这个方法是一具阻塞的方法
selector.select();
//如果select能够往下运行,那么说明我们的这个挑选器已经挑选出我们当前这个客户端感兴趣的事件了,那么我们就可
//拿到具体的事件key集合了
Set<SelectionKey> selectKeys=selector.selectedKeys();
//进行取出具体的SelectedKey
for(Iterator<SelectionKey> it=selectKeys.iterator();it.hasNext();){
//得到具体的Key
SelectionKey selectedKey=it.next();
//然后进行判断出具体的key的操作
if(selectedKey.isConnectable()){
//得到具体的具体通道对象
sc=(SocketChannel) selectedKey.channel();
System.out.println("Client:这里有一个服务器发送过来的请求...");
}
//进行具体的读数据
if(selectedKey.isReadable()){
// String data=SocketChannelTools.readDataFromServer(sc,ByteBuffer.allocate(1024));
//先清空buffer
buffer.clear();
//然后把数据读入这个缓冲区当中
sc.read(buffer);
//然后进行拍板操作
buffer.flip();
String data=new String(buffer.array(),0,buffer.limit());
System.out.println("Client:我已经读取服务器发送了数据="+data);
}
//进行具体的写数据
if(selectedKey.isWritable()){
// SocketChannelTools.writeDataToServer(sc,"hello,I'm Client");
//先清空缓冲区数据
buffer.clear();
//然后把数据写入这个缓冲区当中
buffer.put(("hello,I'm Client"+count++).getBytes());
//然后进行拍板操作
buffer.flip();
//最后写入给服务器
sc.write(buffer);
System.out.println("Client:我已经给服务器发送了数据");
}
}
}
}
//写入读写的工具方法
}