目录

网络编程基础知识

Java的基本网络支持

InetAddress

URLDecoder和URLEncoder

URL,URLConnection和URLPermission

基于TCP协议的网络编程

TCP协议基础

使用ServerSocket创建TCP服务器端

使用Socket进行通信

加入多线程

记录用户信息

半关闭的Socket

使用NIO实现非阻塞Socket通信

使用Java7的AIO实现非阻塞通信

基于UDP协议的网络编程

多点广播(略)

使用代理服务器

Proxy的使用

ProxySelector的使用


网络编程基础知识

网络:把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大,功能强的网络系统,网络中的计算机可以相互传递信息,共享资源

通信协议:网络中要实现可以通信,必须遵的规则约定,通信协议由三部分组成

  • 语义部分:用于决定双方的对话类型
  • 语法部分:用于决定双方的对话格式
  • 变换规则:用于决定双方的应答关系

TCP/IP分层模型 VS OSI(Open System Interconnection)开放系统互连参考模型

IP地址:用于唯一标识网络中的一个通信实体,32bit整数,每8bit用.隔开方便记忆,每位0-255

NIC(Internet Network Infomation Center)统一负责全球IP地址的规划管理,可称为网络信息中心

  • Inter NIC:负责美国以及其他地区的IP管理
  • APNIC:负责亚太地区的IP管理,我国申请IP地址也要通过APNIC,总部设立在日本东京大学
  • RIPE:略

端口:是一个16bit的整数,0-65535的取值范围,通信实体上的应用程序可通过端口与外界交流,端口与应用程序是一对一关系,但是应用程序与端口可以是一对多的关系

  • 公认端口:0-1023,绑定一些特定服务
  • 注册端口:1024-49151,松散绑定一些服务,应用程序应该使用该范围的端口
  • 动态、私有端口:49152-65535:,应用程序动态使用的端口,一般不会主动使用这些端口

Java的基本网络支持

InetAddress

代表IP地址

进行测试:

public static void main(String[] args) throws Exception {//根据主机名获取对应的InetAddress实例InetAddress baiduInetAddress = InetAddress.getByName("www.baidu.com");//判断是否可达System.out.println("判断是否可达:" + baiduInetAddress.isReachable(2000));System.out.println("获取InetAddress实例的IP字符串:" + baiduInetAddress.getHostAddress());//根据原始IP获取对应的InetAddress实例InetAddress localAdderss = InetAddress.getByAddress(new byte[]{127, 0, 0, 1});System.out.println("判断本机是否可达:" + localAdderss.isReachable(2000));System.out.println("获取InetAddress实例对应的全限定域名:" + localAdderss.getCanonicalHostName());
}

URLDecoder和URLEncoder

用于完成普通字符串和application/x-www-form-urlencoded MIME字符串之间的相互转换,当URL地址中包含非西欧字符串时就需要转换

进行测试:

public static void main(String[] args) throws Exception {String encodeString = URLEncoder.encode("hello我是中国人", "UTF-8");System.out.println("encodeString:" + encodeString);String decodeString = URLDecoder.decode(encodeString, "UTF-8");System.out.println("decodeString:" + decodeString);
}

URL,URLConnection和URLPermission

  • URL:统一资源定位符,由协议名,主机,端口和资源组成,在Java中,URL包含一个可以打开到达该资源的输入流,URI只能作为解析,可以将URL理解成URI的特列
  • URLConnection:代表与URL所引用的远程对象的连接
  • URLPermission:用于管理HttpURLConnection的权限问题

实现多线程下载

定义工具类:

public class DownUtil {//定义下载资源的路径private String path;//指定所下载的文件保存的位置private String targetFile;//定义需要多少个线程下载资源private int threadNum;//定义下载的线程对象private DownThread[] threads;//定义下载的文件总大小private int fileSize;public DownUtil(String path, String targetFile, int threadNum) {this.path = path;this.targetFile = targetFile;this.threadNum = threadNum;this.threads = new DownThread[threadNum];}public void download() throws Exception {URL url = new URL(path);HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();fileSize = httpURLConnection.getContentLength();httpURLConnection.disconnect();int currentPartSize = fileSize / threadNum + 1;RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");randomAccessFile.setLength(fileSize);randomAccessFile.close();for (int i = 0; i < threadNum; i++) {int startPos = i * currentPartSize;RandomAccessFile currentPartRandomAccessFile = new RandomAccessFile(targetFile, "rw");currentPartRandomAccessFile.seek(startPos);threads[i] = new DownThread(startPos, currentPartSize, currentPartRandomAccessFile);threads[i].start();}}public double getCompleteRate() {int sumSize = 0;for (int i = 0; i < threadNum; i++) {sumSize += threads[i].length;}return sumSize / fileSize;}private class DownThread extends Thread {private int startPos;private int currentPartSize;private RandomAccessFile randomAccessFile;private int length;public DownThread(int startPos, int currentPartSize, RandomAccessFile randomAccessFile) {this.startPos = startPos;this.currentPartSize = currentPartSize;this.randomAccessFile = randomAccessFile;}@Overridepublic void run() {try {URL url = new URL(path);InputStream inputStream = url.openStream();inputStream.skip(startPos);byte[] bytes = new byte[currentPartSize];int hasRead = 0;while (length < currentPartSize && (hasRead = inputStream.read(bytes)) != -1) {randomAccessFile.write(bytes, 0, hasRead);length += hasRead;}randomAccessFile.close();inputStream.close();} catch (Exception e) {e.printStackTrace();}}}
}

进行测试:

public static void main(String[] args) throws Exception {DownUtil downUtil = new DownUtil("https://download.vebapp.net/FrWPFonpu9nMWOj9Y-X3XH6jxDUf", "E:\\1.jpg", 4);downUtil.download();new Thread(() -> {while (downUtil.getCompleteRate() < 1) {System.out.println("进度:" + downUtil.getCompleteRate());try {Thread.sleep(1000);} catch (Exception ex) {ex.printStackTrace();}}}).start();
}

如需要断点下载,只需增加配置文件进行记录就好了

向web站点发送GET,POST请求

既要使用输入流读取URLConnection响应的内容,又要使用输出流发出请求参数,则一定要先使用输出流,再使用输入流

请求方法:

public class GetPostTest {public static String sendGet(String requestUrl, String param) throws Exception {URL url = new URL(requestUrl + "?" + param);URLConnection urlConnection = url.openConnection();//设置请求头urlConnection.setRequestProperty("accept", "*/*");urlConnection.setRequestProperty("connection", "Keep-Alive");urlConnection.setRequestProperty("User-Agent", "PostmanRuntime/7.24.0");urlConnection.setRequestProperty("Cache-Control", "no-cache");//建立实际连接urlConnection.connect();Map<String, List<String>> headerFields = urlConnection.getHeaderFields();for (String key : headerFields.keySet()) {System.out.println(key + "--->" + headerFields.get(key));}//通过流获取返回的内容InputStream inputStream = urlConnection.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String line;while ((line = bufferedReader.readLine()) != null) {System.out.println(line);}return line;}public static String sendPost(String requestUrl, String param) throws Exception {URL url = new URL(requestUrl);URLConnection urlConnection = url.openConnection();//设置请求头urlConnection.setRequestProperty("accept", "*/*");urlConnection.setRequestProperty("connection", "Keep-Alive");urlConnection.setRequestProperty("User-Agent", "PostmanRuntime/7.24.0");urlConnection.setRequestProperty("Cache-Control", "no-cache");//发送post必须设置以下两行urlConnection.setDoOutput(true);urlConnection.setDoInput(true);PrintWriter printWriter = new PrintWriter(urlConnection.getOutputStream());//发送请求参数printWriter.print(param);Map<String, List<String>> headerFields = urlConnection.getHeaderFields();for (String key : headerFields.keySet()) {System.out.println(key + "--->" + headerFields.get(key));}//通过流获取返回的内容InputStream inputStream = urlConnection.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String line;while ((line = bufferedReader.readLine()) != null) {System.out.println(line);}return line;}
}

进行测试:

public static void main(String[] args) throws Exception {System.out.println("GET请求测试:===========");GetPostTest.sendGet("http://api.yonglihui66.com/unity/XJ/openHall", "name=tony");System.out.println("POST请求测试:===========");GetPostTest.sendPost("http://api.yonglihui66.com/unity/XJ/openHall", "name=tony");
}

基于TCP协议的网络编程

TCP/IP协议是一种可靠的网络协议,通信的两端各建立一个Socket,两端的程序就可以通过虚拟链路进行通信,Java使用Socket对象来代表两端的通信接口,通过Socket产生的IO流来进行网络通信

TCP协议基础

IP协议(Internet Protocol):使Internet成为了一个允许连接不同类型的计算机和不同操作系统的网络,只保证了计算机能发送和接收分组数据,负责将消息从一个主机传送到另一个主机,传送过程中被分割成一个个的小包,但是IP协议不能解决数据分组在传输过程中可能出现的问题,需要安装TCP协议来提供可靠并且无差错的通信服务,属于网络层协议

TCP协议:是一种端对端协议,在两台通信实体之间建议一个用于发送和接收数据的虚拟链路连接,保证了通信的可靠性,属于传输层协议

虽然IP和TCP协议的功能不尽相同,但只有两者结合起来使用,才能保证Internet在复杂的环境下正常运行。凡是要连接到Internet的计算机,必须同时安装这两个协议,所以统称为TCP/IP协议

使用ServerSocket创建TCP服务器端

Java中能接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客户端Socket的连接,如果没有连接,它将一直处于等待状态

常用方法

  • 构造器ServerSocket(int port):用指定端口port创建一个ServerSocket,0-65535范围
  • accept():如果接收到一个客户端Socket的连接请求,将返回一个与客户端对应的Socket(每个TCP连接都有两个Socket),否则该方法一直处于等待状态,线程也被阻塞

ServerSocket使用完毕后,应使用close()方法来关闭该ServerSocket,通常会通过循环不断的调用ServerSocket的accept()方法

使用Socket进行通信

Socket常用方法

  • Socket(String host, int port):创建连接到指定远程主机,远程端口的Socket

当客户端,服务器端产生了对应的Socket之后,程序无须再区分服务端,客户端,而是通过各自的Socket进行通信

常用的Socket通信方法

  • InputStream getInputStream():返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据
  • OutputStream getOutputStream():返回该Socket对象对应的输出流,让程序通过该输入流从Socket中输出数据

创建服务端:

public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(30000);int i = 0;while (true) {Socket socket = serverSocket.accept();System.out.println("收到客户端的请求了:" + ++i);OutputStream outputStream = socket.getOutputStream();PrintWriter printWriter = new PrintWriter(outputStream);printWriter.print("这是服务端返回的:" + (i));printWriter.flush();printWriter.close();socket.close();}
}

创建客户端:

public static void main(String[] args) throws Exception {Socket socket = new Socket("127.0.0.1", 30000);InputStream inputStream = socket.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String s = bufferedReader.readLine();System.out.println(s);bufferedReader.close();
}

先启动服务端,再启动客户端进行测试:

服务端

客户端

如果不希望读取服务器数据的进程一直阻塞,可通过设置客户端Socket的超时时间

public static void main(String[] args) throws Exception {Socket socket = new Socket("127.0.0.1", 30000);//设置超时时间,如果在这个时间范围内,服务端的socket再不把数据写入到输出流中,将会超时socket.setSoTimeout(1000);InputStream inputStream = socket.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String s = bufferedReader.readLine();System.out.println(s);bufferedReader.close();
}

该超时会报以下异常:

如果不想让客户端Socket一直在连接服务器,可通过设置客户端Socket连接服务器的超时时间

public static void main(String[] args) throws Exception {Socket socket = new Socket("127.0.0.1", 30000);//设置连接服务器超时10秒socket.connect(new InetSocketAddress("127.0.0.1", 30000), 10000);//设置超时时间,如果在这个时间范围内,服务端的socket再不把数据写入到输出流中,将会超时socket.setSoTimeout(1000);InputStream inputStream = socket.getInputStream();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));String s = bufferedReader.readLine();System.out.println(s);bufferedReader.close();
}

加入多线程

实现C/S聊天室的功能,在任何一个客户端通过键盘输入一些内容,所有客户端都能收到刚刚输入的内容

创建服务端:

public class MyServer {//保存服务端接收到的所有Socket的public static List<Socket> socketList = Collections.synchronizedList(new ArrayList<>());public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(30000);while (true) {Socket socket = serverSocket.accept();System.out.println("收到客户端请求");socketList.add(socket);new Thread(new ServerThread(socket)).start();}}public static class ServerThread implements Runnable {//定义该线程读到的Socket的输入流private BufferedReader bufferedReader;public ServerThread(Socket socket) throws Exception {bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));}@Overridepublic void run() {String line;try {//该方法会阻塞,一直等待客户端传过来消息while ((line = bufferedReader.readLine()) != null) {System.out.println("收到客户端发来的信息:" + line);for (Socket s : socketList) {PrintStream printStream = new PrintStream(s.getOutputStream());printStream.println(line);}}} catch (IOException e) {e.printStackTrace();}}}
}

创建客户端:

public class MyClient {public static void main(String[] args) throws Exception {//连接服务端Socket socket = new Socket("127.0.0.1", 30000);//单独起一个线程去读取服务端返回的数据new Thread(new ClientThread(socket)).start();PrintStream printStream = new PrintStream(socket.getOutputStream());BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));String line;//该方法会阻塞,一直等待键盘输入消息while ((line = bufferedReader.readLine()) != null) {printStream.println(line);}}private static class ClientThread implements Runnable {private BufferedReader bufferedReader;public ClientThread(Socket socket) throws Exception {this.bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));}@Overridepublic void run() {String line;try {//该方法会阻塞,一直等待服务端响应消息while ((line = bufferedReader.readLine()) != null) {System.out.println(line);}} catch (IOException e) {e.printStackTrace();}}}
}

记录用户信息

可以实现客户端之间的私聊,但客户端之间是不会直接连接的,而是通过服务端来判断,要做到这个需要关注两点:

  • 服务端要识别到客户端发送的消息类型
  • 服务端要识别到客户端的信息

半关闭的Socket

Socket的输出流如何表示输出数据已经结束?不关闭Socket,只关闭流

  • shutdownInput():关闭该Socket的输入流,程序还可以通过该Socket的输出流输出数据
  • shutdownOutput():关闭该Socket的输出流,程序还可以通过该Socket的输入流输入数据

调用Socket的shutdownOutput()或shutdownInput()方法关闭了输出流或输入流之后,该Socket无法再次打开输出流或输入流,不适合保持持久通信状态的交互式应用,只适用于一站式的通信协议,如HTTP协议(属于应用层协议):客户端连接到服务器端之后,开始发送请求数据,发送完成后无须再次发送数据,只需要读取服务器端响应数据即可,当读取响应完成后,该Socket连接也被关闭了

使用NIO实现非阻塞Socket通信

阻塞式Socket通信(BIO),服务端必须为每个客户端都提供一个独立线程进行处理,当处理大量客户端时,导致性能下降,使用NIO可以让服务端使用一个或有限几个线程来同时处理连接到服务端的所有客户端。

关键概念

  • Selector:采用非阻塞方式进行通信的Channel都应该注册到Selector对象中,调用静态方法open()来创建默认的Selector对象
  • SelectableChannel:代表可以支持非阻塞IO操作的Channel对象,默认都是阻塞默认,需手动设置,SelectableChannel可通过validOps()获取到支持的操作(int类型),OP_READ(1),OP_WRITE(4),OP_CONNECT(8),OP_ACCEPT(16),如果validOps()返回的是5,那就是1+4,ServerSocketChannel:对应于ServerSocket,SocketChannel:对应于Socket

三种SelectionKey集合

  1. 注册在该Selector上的Channel,可通过keys()方法获得
  2. 通过select()方法获取到的需要进行IO处理的Channel,通过selectedKeys()方法获得
  3. 被取消注册关系的Channel,通常无须直接访问

实现NIO

创建服务端:

public class NServer {//用于检测所有Channel状态的Selectorprivate Selector selector = null;static final int PORT = 30000;//定义实现编码,解码的字符集对象private Charset charset = Charset.forName("UTF-8");public static void main(String[] args) throws Exception {new NServer().init();}public void init() throws Exception {selector = Selector.open();//通过open方法打开一个未绑定的ServerSocketChannel实例ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", PORT);//将该实例绑定到指定IP地址serverSocketChannel.bind(inetSocketAddress);//设置ServerSocketChannel实例以非阻塞方式工作serverSocketChannel.configureBlocking(false);//将ServerSocketChannel注册到指定的Selector对象serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//该方法select()会阻塞while (selector.select() > 0) {//依次处理selector上的每个已选择的SelectionKeyfor (SelectionKey selectionKey : selector.selectedKeys()) {//从selector上的已选择Key集中删除正在处理的SelectorKeyselector.selectedKeys().remove(selectionKey);//如果selectorKey对应的channel包含客户端的连接请求if (selectionKey.isAcceptable()) {//调用accept方法接受连接,产生服务端的SocketChannelSocketChannel socketChannel = serverSocketChannel.accept();//设置采用非阻塞模式socketChannel.configureBlocking(false);//将该SocketChannel也注册到selectorsocketChannel.register(selector, SelectionKey.OP_READ);//将selectorKey对应的channel设置成准备接受其他请求selectionKey.interestOps(SelectionKey.OP_ACCEPT);}//如果selectionKey对应的channel有数据需要读取if (selectionKey.isReadable()) {//获取该selectionKey对应的channel,该channel中有可读的数据SocketChannel socketChannel = (SocketChannel) selectionKey.channel();//定义准备执行读取数据的ByteBufferByteBuffer byteBuffer = ByteBuffer.allocate(1024);String content = "";//开始读取数据try {while (socketChannel.read(byteBuffer) > 0) {byteBuffer.flip();content += charset.decode(byteBuffer);}//打印读取到的数据System.out.println("读取的数据:" + content);//将selectionKey对应的channel设置成准备下一次读取selectionKey.interestOps(SelectionKey.OP_READ);} catch (IOException e) {//如果发生了异常,说明selectionKey对应的channel出现了异常//即表明该channel对应的client出现了问题,所以从selector中取消该selectionKey的注册selectionKey.cancel();if (selectionKey != null) {selectionKey.channel().close();}}//如果读取的数据长度大于0,即聊天信息不为空if (content.length() > 0) {//遍历所有的SelectionKeyfor (SelectionKey key : selector.keys()) {//获取对应的ChannelChannel channel = key.channel();if (channel instanceof SocketChannel) {//将读到的内容写入到该Channel中SocketChannel socketChannel1 = (SocketChannel) channel;socketChannel1.write(charset.encode(content));}}}}}}}
}

如上所示,程序启动时会创建一个可监听连接请求的ServerSocketChannel,并注册到Selector对象中,采用循环不断监控Selector对象的select()方法返回值,如果大于0,则处理该Selector对象上所有被选择的SelctionKey。在处理SelctionKey的时候,先从该Selector上被选择的SelectionKey集合中删除该SelectionKey。以上监听了两个动作:连接和读数据,监控到连接的时候,将产生的SocketChannel注册到指定的Selector对象上即可,当监控到读数据的时候,先从Socket中读出,在写入到对应的Channel中

创建客户端:

public class NClient {private Selector selector = null;static final int PORT = 30000;private Charset charset = Charset.forName("UTF-8");private SocketChannel socketChannel = null;public static void main(String[] args) throws Exception {new NClient().init();}public void init() throws Exception {selector = Selector.open();InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", PORT);socketChannel = SocketChannel.open(inetSocketAddress);socketChannel.configureBlocking(false);socketChannel.register(selector, SelectionKey.OP_READ);new ClientThread().start();Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String line = scanner.nextLine();socketChannel.write(charset.encode(line));}}private class ClientThread extends Thread {@Overridepublic void run() {try {while (selector.select() > 0) {for (SelectionKey selectionKey : selector.selectedKeys()) {selector.selectedKeys().remove(selectionKey);if (selectionKey.isReadable()) {SocketChannel socketChannel = (SocketChannel) selectionKey.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(1024);String content = "";while (socketChannel.read(byteBuffer) > 0) {byteBuffer.flip();content += charset.decode(byteBuffer);}System.out.println("聊天信息:" + content);selectionKey.interestOps(SelectionKey.OP_READ);}}}} catch (Exception e) {e.printStackTrace();}}}
}

如上所示,启动时将SocketChannel注册到指定的Selector,启动另外的线程进行监听该Selector,监听的是读数据,如果监听到,取出该Channel并将结果打印出来

使用Java7的AIO实现非阻塞通信

基于异步Channel的IO机制成为异步IO(AsynchronousIO)

如果按照POSIX的标准来划分IO,可以分为两类:同步IO和异步IO。对于IO的操作可以分成两步:1.程序发出IO请求;2.完成事件的IO操作。阻塞IO与非阻塞IO是根据第一步区分的:发出的IO请求会阻塞线程,就是阻塞IO;发出的IO请求不会阻塞线程就是非阻塞IO;同步IO与异步IO是根据第二步区分的:实际操作都需要程序本身去执行,就是同步;实际操作由操作系统去完成,就是异步

AIO核心概念

AsynchronousServerSocketChannel

  • 与ServerSocketChannel相似,可以通过open()创建默认的AsynchronousServerSocketChannel对象,也可以通过open(AsynchronousChannelGroup group)传入分组管理器创建对象,AsynchronousChannelGroup创建时可传入ExecutorService线程池,线程池负责两个任务:处理IO事件和触发CompletionHandler
  • accept():接受客户端请求,如果连接成功可通过Future对象的get()得到AsynchronousSocketChannel对象,但是get()会阻塞线程
  • <A> void accept(A attachment,CompletionHandler<AsynchronousSocketChannel,? super A> handler):接受客户端请求,连接成功或失败都会触发CompletionHandler中相应的方法,其中AsynchronousSocketChannel就是连接成功之后返回的对象
  • CompletionHandler:void completed(V result, A attachment)方法是IO操作完成时触发,第一个参数代表操作完成所有返回的对象,第二个参数是发起IO操作时传入的附加参数,void failed(Throwable exc, A attachment)方法是IO操作失败时触发,第一个参数代表失败引发的异常,第二个参数是发起IO操作时传入的附加参数

AsynchronousServerSocketChannel使用三步骤:

  1. 调用open()创建AsynchronousServerSocketChannel对象
  2. 调用bind()绑定IP地址端口
  3. 调用accept()方法接受连接请求

AsynchronousSocketChannel使用三步骤:

  1. 调用open()创建AsynchronousSocketChannel对象
  2. 调用connect()方法连接指定IP,端口的服务器
  3. 调用read(),write()方法进行读写

AsynchronousServerSocketChannel区分两个版本:返回Future对象版本 & 传入 CompletionHandler参数版本

返回Future对象版本

创建服务端:

public class SimpleAIOServer {static final int PORT = 30000;public static void main(String[] args) throws Exception {//创建AsynchronousServerSocketChannel对象try (AsynchronousServerSocketChannel asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open()) {//指定地址,端口监听asynchronousServerSocketChannel.bind(new InetSocketAddress(PORT));while (true) {//接受客户端请求Future<AsynchronousSocketChannel> future = asynchronousServerSocketChannel.accept();//获取返回的AsynchronousSocketChannel对象,get()方法会阻塞AsynchronousSocketChannel asynchronousSocketChannel = future.get();//执行输出asynchronousSocketChannel.write(ByteBuffer.wrap("欢迎来到AIO世界".getBytes("UTF-8"))).get();}}}
}

创建客户端:

public class SimpleAIOClient {static final int PORT = 30000;public static void main(String[] args) throws Exception {ByteBuffer byteBuffer = ByteBuffer.allocate(1024);Charset charset = Charset.forName("UTF-8");try (AsynchronousSocketChannel asynchronousSocketChannel = AsynchronousSocketChannel.open()) {asynchronousSocketChannel.connect(new InetSocketAddress("127.0.0.1", PORT)).get();byteBuffer.clear();asynchronousSocketChannel.read(byteBuffer).get();byteBuffer.flip();String content = charset.decode(byteBuffer).toString();System.out.println(content);}}
}

返回Future对象版本版本没能充分发挥Java AIO的优势,考虑使用线程池来管理异步Channel,并使用CompletionHandler来监听异步IO操作,例子(略)

基于UDP协议的网络编程

UDP协议(User Datagram Protocol):即用户数据报协议,面向非连接,在发送数据之前不必进行建立连接,所以也是一种不可靠协议,适用于一次传输少量数据(64KB以下),对可靠性要求不高的应用环境,但因为不需要建立连接,所以通倍效率很高,与TCP协议一样,属于传输层协议,Java主要使用DatagramSocket实现UDP通信(不需要严格区分服务端,客户端的说法)

  • DatagramSocket:代表码头,只用来发送和接收数据,不维护状态,不产生IO流
  • DatagramPackage:代表数据报,DatagramSocket通过DatagramPackage来完成数据的发送和接收

DatagramSocket常用方法介绍:

  • DatagramSocket(int port):创建DatagramSocket实例,并把该实例绑定到本机默认IP,指定端口上
  • receive(DatagramPacket p):从该DatagramSocket中接收数据报,该方法会阻塞,直到收到数据报为止
  • send(DatagramPacket p):从该DatagramSocket中向外发送数据报

DatagramPackage常用方法介绍:

  • DatagramPacket(byte buf[], int offset, int length):以一个空数组来创建用于接收数据的DatagramPackage对象,并指定接收到的数据放入buf数组中时从offset开始,最多存放length个字节
  • DatagramPacket(byte buf[], int offset, int length, InetAddress address, int port):创建一个用于发送数据的DatagramPackage对象,指定发送buf数组中从offset开始,总共length个字节
  • getAddress()/getPort():当程序准备发送数据时,返回该数据报的目标IP地址/端口,当程序准备接收数据时,返回该数据报的发送主机的IP地址/端口

实现UDP网络通信,每当读取到内容之后便向该数据报的发送者发送一条响应消息

创建UDP“服务端”:

public class UdpServer {//定义端口private final static int PORT = 30000;//定义每个数据报的大小为4KBprivate final static int DATA_LEN = 4096;//定义接收网络数据的字节数组byte[] inBuff = new byte[DATA_LEN];//定义接收数据的DatagramPacket对象private DatagramPacket inDatagramPacket = new DatagramPacket(inBuff, DATA_LEN);//定义发送数据的DatagramPacket对象private DatagramPacket outDatagramPacket;public static void main(String[] args) throws Exception {new UdpServer().init();}public void init() throws Exception {//定义DatagramSocket对象,用于操作DatagramPackettry (DatagramSocket datagramSocket = new DatagramSocket(PORT)) {int i = 0;while (true) {datagramSocket.receive(inDatagramPacket);System.out.println("接收到的消息:" + new String(inBuff, 0, inDatagramPacket.getLength()));byte[] bytes = ("服务端返回消息" + (++i)).getBytes();//将收到的信息返回给发送者outDatagramPacket = new DatagramPacket(bytes, 0, bytes.length, inDatagramPacket.getAddress(), inDatagramPacket.getPort());datagramSocket.send(outDatagramPacket);}}}
}

创建UDP“客户端”:

public class UdpClient {//定义端口private final static int PORT = 30001;//定义每个数据报的大小为4KBprivate final static int DATA_LEN = 4096;//定义接收网络数据的字节数组byte[] inBuff = new byte[DATA_LEN];//定义接收数据的DatagramPacket对象private DatagramPacket inDatagramPacket = new DatagramPacket(inBuff, DATA_LEN);//定义发送数据的DatagramPacket对象private DatagramPacket outDatagramPacket;public static void main(String[] args) throws Exception {new UdpClient().init();}public void init() throws Exception {//定义DatagramSocket对象,用于操作DatagramPackettry (DatagramSocket datagramSocket = new DatagramSocket(PORT)) {Scanner scanner = new Scanner(System.in);//起一个新线程来玄循环接收数据new Thread(() -> {while (true) {try {datagramSocket.receive(inDatagramPacket);System.out.println("接收到的消息:" + new String(inBuff, 0, inDatagramPacket.getLength()));} catch (IOException e) {e.printStackTrace();}}}).start();//从控制台发送信息给另外的服务端while (scanner.hasNextLine()) {byte[] bytes = scanner.nextLine().getBytes();outDatagramPacket = new DatagramPacket(bytes, 0, bytes.length, InetAddress.getByName("127.0.0.1"), 30000);datagramSocket.send(outDatagramPacket);}}}
}

进行测试:

服务端

客户端

多点广播(略)

MulticastSocket

使用代理服务器

代理服务器是介于浏览器与服务器之间的一台服务器,浏览器请求服务器的时候,浏览器先向代理服务器发请求,然后由代理服务器请求web服务器,web服务器响应给代理服务器,代理服务器再响应给浏览器

  • 突破自身IP限制,对外隐藏自身IP地址
  • 代理服务器提供的缓冲功能可以提高访问速度

Proxy:代表一个代理服务器,打开URLConnection,建立Socket连接的时候指定Proxy

ProxySelector:代理选择器,提供了对代理服务器的灵活控制,

Proxy的使用

Proxy(Type type, SocketAddress sa):创建代理服务器对象

sa:代理服务器的地址

type:代理服务器的类型

  • Proxy.Type.DIRECT:直接连接,不使用代理
  • Proxy.Type.HTTP:高级协议代理,如HTTP,FTP
  • Proxy.Type.SOCKS:表示SOCKS(V4,V5代理)

进行测试:

public class ProxyTest {final String PROXY_ADDR = "127.0.0.1";final int PROXY_PORT = 8888;public static void main(String[] args) throws Exception {new ProxyTest().init();}public void init() throws Exception {URL url = new URL("www.baidu.com");//创建代理服务器Proxy对象Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_ADDR, PROXY_PORT));URLConnection urlConnection = url.openConnection(proxy);//设置超时时间urlConnection.setConnectTimeout(3000);BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));String content;if ((content = bufferedReader.readLine()) != null) {System.out.println(content);}}
}

如上所示,程序不会直接去访问资源,而是通过代理服务器去请求访问

ProxySelector的使用

ProxySelector是抽象类,需要自己实现,重写方法

  • List<Proxy> select(URI uri):根据业务需要返回代理服务器列表,如果只有一个,默认使用该Proxy
  • connectFailed(URI uri, SocketAddress sa, IOException ioe):连接代理服务器失败的时候会调用该方法,系统默认的代理服务器选择器重写该方法的策略是:代理服务器连接失败,会采用直连方式进行连接

自定义ProxySelector

public class ProxySelectorTest {final String PROXY_ADDR = "127.0.0.1";final int PROXY_PORT = 8888;public static void main(String[] args) throws Exception {new ProxyTest().init();}public void init() throws Exception {ProxySelector.setDefault(new ProxySelector() {@Overridepublic List<Proxy> select(URI uri) {List<Proxy> proxies = new ArrayList<>();proxies.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress(PROXY_ADDR, PROXY_PORT)));return proxies;}@Overridepublic void connectFailed(URI uri, SocketAddress sa, IOException ioe) {System.out.println("无法连接到指定代理!");//实现其他业务}});URL url = new URL("www.baidu.com");URLConnection urlConnection = url.openConnection();//设置超时时间urlConnection.setConnectTimeout(3000);BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));String content;if ((content = bufferedReader.readLine()) != null) {System.out.println(content);}}
}

如上所示,修改了程序默认的代理服务器选择器,所以程序会使用自定义的代理服务器选择器,当连接不上该代理服务器的时候,程序会打印出"无法连接指定代理",且不会进行直连操作

SE:17.网络编程相关推荐

  1. Java SE day21_网络编程

    Java SE day21_网络编程 如果一个类没有构造方法: 案例(UDP的Socket) 案例(键盘输入接受) 案例(多线程聊天) TCP连接 案例(阻塞) 案例(键盘录入-输出控制台) 案例(多 ...

  2. Java se:网络编程

    概述 用Java语言实现计算机间数据的信息传递和资源共享 参考模型: OSI参考模型 TCP/IP参考模型 网络编程三要素 IP地址: 计算机的在网络中的标识 点分十进制标识法:4个字节,每个字节采用 ...

  3. JAVA.SE Socket网络编程

    文章目录 1.网络编程入门 1.1软件架构介绍 1.2互联网通信协议 1.3网络编程的三要素 2.TCP通信 2.1通信分为客户端与服务器 2.2 socket的介绍与使用 2.3servletSoc ...

  4. Java SE:浅析网络编程

    目录 一. 软件结构 二. 网络编程三大要素 2.1 IP地址和域名 2.1.1 IP地址 2.1.2 域名 2.2 端口号 2.3 网络协议 三. TCP与UDP协议 3.1 UDP协议 3.2 T ...

  5. Java SE 网络编程

    Java SE 网络编程 目录 Java SE 网络编程 1.计算机网络概念 2.网络通讯协议 2.2.通讯接口 3.网络分层 3.2.OSI七层协议|TCP/IP五层协议 3.3 . 封装 3.4. ...

  6. [零基础学JAVA]Java SE应用部分-32.Java网络编程

    C/S程序:表示的客户/服务器程序,每次需要编写两套程序 |- MSN.QQ:一般会有两套程序,一个是服务器端,另外一个是客户端 B/S程序:表示的浏览器/ 服务器,可以理解为动态WEB,论坛 本季目 ...

  7. 【java学习之路】(java SE篇)012.网络编程

    网络编程 网络的概念 网络:一组相互连接的计算机 由多台计算机组成 使用物理线路进行连接 网络编程三要素 IP地址:唯一标识网络上的每一台计算机,两台计算机之间通信的必备要素 端口号:计算机中应用的标 ...

  8. -1-7 java 网络编程基本知识点 计算机网络 TCP/IP协议栈 通信必备 tcp udp

    计算机网络 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来, 在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统. 网络编程 ...

  9. 21天学会Java之(Java SE第十三篇):网络编程、TCP/UDP通信

    如今,计算机已经成为人们学习.工作.生活必不可少的工具.人们利用计算机可以和亲朋好友在网上聊天,玩网游或发邮件等,这些功能的实现都离不开计算机网络.计算机网络实现了不同计算机之间的通信,而这些必须依靠 ...

最新文章

  1. BCH将拥有远超过BTC的“最佳货币”体验
  2. idea怎么调成黑色页面
  3. GetResponse() 基础连接已经关闭:服务器关闭了本应保持活动状态的连接
  4. sprinigboot(2.2.4)+mysql引入druid的性能监控StateFilter
  5. kafka 不同分区文件存储_大白话 + 13 张图解 Kafka
  6. Leetcode——连续子数组的最大和(剑指offer 42)
  7. python中lambda的用法
  8. 要想不被套,学会这八招
  9. 用大数据挑选出国外最值得看的前50条swift教程(v.2019)
  10. 八段锦是一种不错的养生运动
  11. 科技文献检索(七)——检索工具
  12. 开根号计算机在线应用,根号计算器(万能计算器在线计算)
  13. 音频开发_Microphone Array Beamforming_Delay Sum Beamforming
  14. 【SpringBoot】12.SpringBoot整合Dubbo+Zookeeper
  15. xss漏洞开源网站包
  16. el-tree实现类似windows文件列表,并支持折叠、展开和重命名
  17. 如果太阳突然爆炸 地球会发生什么?| 1分钟了解广义相对论
  18. 你知道这个提高 Java 单元测试效率的 IDEA 插件吗
  19. python scripts文件夹里面没有pip.exe怎么办?
  20. 反编译 jdk1.8 工具 [Procyon-Decompiler]

热门文章

  1. maters鸿蒙系统,华为MateRS保时捷设计4月12日国内发布
  2. DEAP:使用生理信号进行情绪分析的数据库(三、实验分析与结论)
  3. 疫情下的在线教育行业未来发展
  4. ajax 代码 查询股票联想,互联网金融爬虫怎么写-第三课 雪球网股票爬虫(ajax分析)...
  5. 大数据之------------数据中台
  6. 修改lib库对应的dll名称
  7. 经历≠经验,码农如何工作10年依然是菜鸟?
  8. Springboot配置Nacos出现异常
  9. app常见功能模块及其使用简单介绍
  10. 金蝶eas文件更新服务器端口,如何更改金蝶eas服务器地址