1、Java语言对TCP协议网络通信使用java.net包中的Socket和ServerSocket进行支持;Socket被称为“套接字”,用来描述IP地址和端口;
Socket与ServerSocket的通讯过程简要描述如下:
2、java.net.Socket类中的构造方法和常用方法
方法声明 | 方法描述 |
---|---|
Socket(String host, int port) | 通过指定服务器的IP地址以及服务端口号创建Socket对象 |
InputStream getInputStream() | 返回与当前socket相关的输入流对象; |
OutputStream getOutputStream() | 返回与当前socket相关的输出流对象; |
3、java.net.ServerSocket类中的构造方法和常用方法:
方法声明 | 方法描述 |
---|---|
ServerSocket(int port) | 通过指定服务端口号创建ServerSocket对象 |
Socket accept() | 监听客户端的请求,并接受连接,返回一个Socket对象; |
import java.io.*; public class IOUtils { public static void close(Writer w) { if (w != null) { try { w.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void close(Reader r) { if (r != null) { try { r.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void close(OutputStream os) { if (os != null) { try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void close(InputStream is){ if(is!=null){ try { is.close(); } catch (IOException e) { e.printStackTrace(); } } } }
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class SocketUtils { public static void close(Socket socket){ if(socket != null){ try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void close(ServerSocket serverSocket) { if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void write(BufferedWriter bw, String str) throws IOException { bw.write(str); bw.newLine(); bw.flush(); } public static String read(BufferedReader br) throws IOException { //读取数据,如果没有数据可读就阻塞代码 String s = br.readLine(); System.out.println(s); return s; } }
import java.io.*; import java.net.ServerSocket; import java.net.Socket; /** * 服务端,接受客户端的连接,向客户端读写数据 */ public class Server { public static void main(String[] args) { ServerSocket server = null; Socket socket = null; InputStream inputStream = null; BufferedReader br = null; OutputStream outputStream = null; BufferedWriter bw = null; try { server = new ServerSocket(10010); System.out.println("服务器启动成功"); System.out.println("等待客户端连接"); //调用ServerSocket的accept方法,等待并可以接受客户端的请求,并返回当前的Socket对象 //阻塞代码 socket = server.accept(); System.out.println("接收到客户端的连接,Ip地址:" + socket.getInetAddress().getHostAddress()); //获取输入流 inputStream = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(inputStream)); outputStream = socket.getOutputStream(); bw = new BufferedWriter(new OutputStreamWriter(outputStream)); //一直循环读取数据 while (true) { //读取数据,如果没有数据可读就阻塞代码 String s = SocketUtils.read(br); //数据写回客户端 SocketUtils.write(bw, "服务器返回的数据:" + s); } } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.close(bw); IOUtils.close(br); IOUtils.close(outputStream); IOUtils.close(inputStream); SocketUtils.close(socket); SocketUtils.close(server); } } }
import java.io.*; import java.net.Socket; import java.util.Scanner; /** * 连接服务器端,向服务器读写数据 */ public class Client { public static void main(String[] args) { Socket socket = null; InputStream inputStream = null; BufferedReader br = null; OutputStream outputStream = null; BufferedWriter bw = null; try { System.out.println("客户端启动,准备连接服务器"); socket = new Socket("192.168.140.254", 10010); System.out.println("连接服务器成功,准备发送数据"); outputStream = socket.getOutputStream(); bw = new BufferedWriter(new OutputStreamWriter(outputStream)); inputStream = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(inputStream)); Scanner scanner = new Scanner(System.in); System.out.println("请输入数据:"); while (true) { //接收控制台输入的数据阻塞代码 String s = scanner.next(); SocketUtils.write(bw, s); //读取数据 SocketUtils.read(br); } } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.close(bw); IOUtils.close(br); IOUtils.close(outputStream); IOUtils.close(inputStream); SocketUtils.close(socket); } } }
4、上述案例中,运行多个client,结果如?
idea设置可以启动多个client
运行多个client只有一个client能够发送消息
1、基于TCP协议的通讯,客户端和服务器端都使用Socket对象获取输入流和输出流;使用IO流对象读写数据进行通讯;
2、ServerSocket的accept方法是阻塞的,当服务器端接受了一个客户端请求建立连接后,就不会为其他客户端服务;如果需要服务器端为多个客户端服务,必须为每个客户端启动一个新的线程;
3、ThreadServer类负责处理客户端的请求
import java.io.*; import java.net.Socket; public class ThreadServer extends Thread{ private Socket socket; public ThreadServer(Socket socket) { this.socket = socket; } @Override public void run() { InputStream inputStream = null; BufferedReader br = null; OutputStream outputStream = null; BufferedWriter bw = null; //获取输入流 try { //获取输入流 inputStream = socket.getInputStream(); br = new BufferedReader(new InputStreamReader(inputStream)); //获取输出流 outputStream = socket.getOutputStream(); bw = new BufferedWriter(new OutputStreamWriter(outputStream)); //一直循环读取数据 while (true) { //读取数据,如果没有数据可读就阻塞代码 String s = read(br); //数据写回客户端 write(bw, "服务器返回的数据:" + s); } } catch (IOException e) { e.printStackTrace(); } finally { IOUtils.close(bw); IOUtils.close(br); IOUtils.close(outputStream); IOUtils.close(inputStream); SocketUtils.close(socket); } } /* * 读 */ private static String read(BufferedReader br) { try { //读取数据,如果没有数据可读就阻塞代码 String s = br.readLine(); System.out.println("服务器端接收到的数据:" + s); return s; } catch (IOException e) { e.printStackTrace(); } return ""; } /* * 写 */ private static void write(BufferedWriter bw, String str) { try { bw.write(str); bw.newLine(); bw.flush(); } catch (IOException e) { e.printStackTrace(); } } }
import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; public class Server1 { public static void main(String[] args) { ServerSocket server = null; try { server = new ServerSocket(10010); System.out.println("服务器启动成功"); System.out.println("等待客户端连接"); //调用ServerSocket的accept方法,等待并可以接受客户端的请求,并返回当前的Socket对象 //阻塞代码 while (true) { Socket socket = server.accept(); System.out.println("接收到客户端的连接,Ip地址:" + socket.getInetAddress().getHostAddress()); //启动线程,处理客户端和服务端通讯 new ThreadServer(socket).start(); } } catch (IOException e) { e.printStackTrace(); } finally { SocketUtils.close(server); } } }
4、结论
在一个线程里处理多个客户端请求会发生阻塞问题;
如果要同时服务多个客户端,则必须为每个客户端启动一个线程提供服务;
channel.register
,会记录通道对应的感兴趣的事件。仅当我们调用时key.cancel()
,它将从此表中删除。selector.select()
,选择器将查找登记表,找到可用的键,并将它们的引用复制到该选择表中。选择器不会清除此表中的通道感兴趣的事件(这意味着,即使我们selector.select()
再次调用,它也不会清除现有项目)import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.Iterator; import java.util.Set; /* NIO服务端 */ public class NIOServer { //通道管理器 private Selector selector; public static void main(String[] args) throws IOException { //new NIOServer().init(10010).listen(); NIOServer nioServer = new NIOServer(); nioServer = nioServer.init(10020); nioServer.listen(); } /* 初始化NIOServer */ private NIOServer init(int port) throws IOException { //获取ServerSocketChannel通道 ServerSocketChannel sc = ServerSocketChannel.open(); //设置非阻塞模式 sc.configureBlocking(false); //设置通道绑定的端口 sc.socket().bind(new InetSocketAddress(port)); //获取通道管理器 selector = Selector.open(); //将通道管理器与通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件 sc.register(selector, SelectionKey.OP_ACCEPT); //只有当该事件到达时,Selector.select()会返回,否则一直阻塞 return this; } /* 监听客户端的连接和发送数据 */ private void listen() throws IOException { System.out.println("服务端启动成功,开始监听客户端连接"); //循环处理 客户端连接事件和可读事件 使用轮训访问selector while(true){ //abstract int select() /* selector对象(内部循环监听注册再selector的通道,感兴趣的事件触发) 如果没有感兴趣的事件触发,就会阻塞代码。 【当有注册的事件到达时,方法返回,否则阻塞。】 */ selector.select(); //处理事件 返回此选择器的选择键集。 Set<SelectionKey> selectionKeys = selector.selectedKeys();/*abstract Set<SelectionKey> selectedKeys()*/ //获取迭代器 Iterator<SelectionKey> iterator = selectionKeys.iterator(); //循环处理(被触发的事件,可能有多个) while (iterator.hasNext()){ SelectionKey key = iterator.next(); //删除已经选择的key,防止重复处理 iterator.remove(); //客户端请求连接事件 if (key.isAcceptable()) { /*返回创建此键的通道。 该方法即使在取消键之后仍将继续返回通道。*/ ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); // 获得客户端连接通道,返回SocketChannel SocketChannel socketChannel = serverSocketChannel.accept(); /*final SelectableChannel configureBlocking(boolean block) block - 如果true那么这个通道将被置于阻塞模式 如果false那么它将被放置为非阻塞模式 */ socketChannel.configureBlocking(false); //向客户端发消息 socketChannel.write(ByteBuffer.wrap(new String("hello world").getBytes())); /* SocketChannel对象注期到selector对象中,感兴趣的事件是读事件 【在与客户端连接成功后,为客户端通道注册SelectionKey.OP_READ事件。】 */ socketChannel.register(selector, SelectionKey.OP_READ); System.out.println("客户端请求连接事件"); }else if (key.isReadable()) {// 有可读数据事件 /*获取客户端传输数据可读取消息通道。*/ SocketChannel channel = (SocketChannel) key.channel(); /*static ByteBuffer allocate(int capacity) 创建读取数据缓冲器,分配一个新的字节缓冲区。 capacity - 新的缓冲区的容量,以字节为单位 */ ByteBuffer buffer = ByteBuffer.allocate(1024); //一次性全部读取 int read = channel.read(buffer); byte[] data = buffer.array(); String str = new String(data,0,data.length); System.out.println("receive message from client, size:" + buffer.position() + " 接收到客户端发送来的数据: " + str); //向客户端发消息 channel.write(ByteBuffer.wrap(new String("发送的数据:"+str).getBytes())); }else{ System.out.println("其他事件被触发了,暂不处理,"+key); } } } } }
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Scanner; import java.util.Set; /* NIO客户端 */ public class NIOClient { // 管道管理器 private Selector selector; public static void main(String[] args) throws IOException { new NIOClient().init("192.168.140.177", 10020).start(); } public NIOClient init(String serverIp, int port) throws IOException { // 获取socket通道对象 SocketChannel channel = SocketChannel.open(); //设置非阻塞 channel.configureBlocking(false); // 获得通道管理器 selector = Selector.open(); // 客户端连接服务器,需要调用channel.finishConnect();才能实际完成连接。 /*abstract boolean connect(SocketAddress remote) 参数 remote - 要连接该通道的远程地址 结果 true如果连接建立, false如果该通道处于非阻塞模式并且连接操作正在进行中 *InetSocketAddress(String hostname, int port) 参数 hostname - 主机名 port - 端口号 结果 创建Socket地址 */ channel.connect(new InetSocketAddress(serverIp, port)); // 为该通道注册SelectionKey.OP_CONNECT事件 /*final SelectionKey register(Selector sel, int ops) 参数 sel - 要注册该频道的选择器 ops - 为结果键设置的兴趣 结果 表示该通道与给定选择器的注册的键 */ channel.register(selector, SelectionKey.OP_CONNECT/*用于Socket连接操作的操作集位。 */); return this; } public void start() throws IOException { Scanner sc = new Scanner(System.in); System.out.println("客户端启动"); // 轮询访问selector while (true) { // 选择注册过的io操作的事件(第一次为SelectionKey.OP_CONNECT) selector.select(); Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> ite = selectionKeys.iterator(); while (ite.hasNext()) { SelectionKey key = ite.next(); // 删除已选的key,防止重复处理 ite.remove(); if (key.isConnectable()) { SocketChannel channel = (SocketChannel) key.channel(); // 如果正在连接,则完成连接 if (channel.isConnectionPending()) { channel.finishConnect(); } channel.configureBlocking(false); // 向服务器发送消息 channel.write(ByteBuffer.wrap("send message to server:i am client".getBytes())); // 连接成功后,注册接收服务器消息的事件 channel.register(selector, SelectionKey.OP_READ/*读操作的操作位。*/); System.out.println("客户端连接成功"); } //有可读数据事件。 else if (key.isReadable()) { SocketChannel channel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); //把通道的数据读到ByteBuffer对象中 channel.read(buffer); byte[] data = buffer.array(); String str = new String(data); System.out.println("接收到服务端发过来的数据:,size:" + buffer.position() + " ,str: " + str); /*ByteBuffer outBuffer = ByteBuffer.wrap(("client.".concat(str)).getBytes()); channel.write(outBuffer);*/ //阻塞代码 String s = sc.next(); channel.write(ByteBuffer.wrap(s.getBytes())); } else { System.out.println("其他的事件被触发:" + key); } } } } }