socket编程是以IO为理论基础的,理论学得差不多也很难实现编程,毕竟里面的类和方法平时都不怎么用,难得尝试编了个程,记录一下。
1.几个概念
Channel:管道,连通客户端和服务端传输数据;
Buffer:缓冲区,通过管道传输数据必须经过的地方;
Selector:选择器,单线程可以通过选择器处理多个管道;本文没有使用;
2.案例功能
启动服务端,再启动客户端与服务端连接,在客户端的控制台输入命令获取服务端的效果。本样例只处理了“time”命令,获取服务端的当前时间,其他命令都返回“非指定命令,无返回值”。详看代码注释。
3.服务端代码
1 import java.net.InetSocketAddress; 2 import java.nio.ByteBuffer; 3 import java.nio.channels.ServerSocketChannel; 4 import java.nio.channels.SocketChannel; 5 import java.nio.charset.StandardCharsets; 6 import java.util.Date; 7 import java.util.LinkedList; 8 9 public class NIOService { 10 static int PORT = 9011; 11 public static void main(String[] args) throws Exception{ 12 //存储客户端连接 13 LinkedList<SocketChannel> clients = new LinkedList<>(); 14 //1.服务端开启监听:接受客户端 15 ServerSocketChannel ss = ServerSocketChannel.open(); 16 ss.bind(new InetSocketAddress(PORT)); 17 //2.只接受客户端,不阻塞 18 ss.configureBlocking(false); 19 20 while (true) { 21 // 接受客户端的连接 22 // client在Java层面是一个对象,在内核层面是一个fd 23 SocketChannel client = ss.accept(); 24 if (client == null) { 25 //while循环进来没有 连到客户端就不管 26 } else { 27 //和client传输数据使用的socket->fd 28 client.configureBlocking(false); 29 //获取客户端的端口号 30 int port = client.socket().getPort(); 31 System.out.println("接收到客户端的连接,client port: " + port); 32 //将客户端添加到列表里 33 clients.add(client); 34 } 35 //可以在堆里,堆外,相关内容,可以看看JVM直接内存 36 ByteBuffer buffer = ByteBuffer.allocateDirect(4096); 37 38 //遍历已经链接进来的客户端 的管道channel里有没有数据 39 for (SocketChannel c : clients) { 40 //每循环一次都是一次系统调用,都是一次用户内核态的切换 41 int num = c.read(buffer); 42 if (num > 0) { 43 buffer.flip(); 44 byte[] bytes = new byte[buffer.limit()]; 45 buffer.get(bytes); 46 String s = new String(bytes); 47 System.out.println("端口为"+c.socket().getPort() + "的客户端发来命令:" +s); 48 String res = ""; 49 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(4096); 50 if("time".equals(s)){ 51 //获取时间 52 long l = System.currentTimeMillis(); 53 res = "time: "+new Date(l).toString(); 54 System.out.println(res); 55 }else{ 56 res = "非指定命令,无返回值"; 57 } 58 byteBuffer.put(res.getBytes(StandardCharsets.UTF_8)); 59 byteBuffer.flip(); 60 c.write(byteBuffer); 61 } 62 buffer.clear(); 63 } 64 } 65 } 66 }
4.客户端代码
1 import java.io.*; 2 import java.net.Socket; 3 import java.nio.channels.ServerSocketChannel; 4 import java.nio.channels.SocketChannel; 5 import java.util.Scanner; 6 7 public class Client { 8 public static void main(String[] args) { 9 try { 10 //建立一个客户端连到9010的服务端 11 Socket client = new Socket("127.0.0.1",9011); 12 //设置发送命令的长度 13 client.setSendBufferSize(20); 14 /** 15 * 关闭Nagle算法:该算法是将多个命令打包一起发送给服务端,避免网络拥挤。 16 * 但是现在网络宽松,随便发也没事。所以关闭。 17 */ 18 client.setTcpNoDelay(false); 19 20 OutputStream out = client.getOutputStream(); 21 InputStream input = client.getInputStream(); 22 InputStream in = System.in; 23 BufferedReader reader = new BufferedReader(new InputStreamReader(in)); 24 Scanner scan = new Scanner(System.in); 25 while(true){ 26 String line = scan.nextLine(); 27 //String line = reader.readLine(); 28 if(line != null ){ 29 //将输入的命令用字节数组存起来,通过客户端client的输出流发送给服务端 30 byte[] bb = line.getBytes(); 31 out.write(bb); 32 } 33 byte b[] = new byte[1024]; 34 /** 35 从输入流里读 东西 到 b数组,如果没有东西,就会一直读,阻塞。 36 len获取当前输入流里的内容长度,英文占1个长度,中文占3个长度 37 */ 38 int len = input.read(b); 39 System.out.println(new String(b)); 40 } 41 } catch (IOException e) { 42 e.printStackTrace(); 43 } 44 } 45 46 }