【16-网络编程】
网络编程 网络的优势
•所谓计算机网络,就是把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享硬件、软件、数据信息等资源。 •计算机网络是现代通信技术与计算机技术相结合的产物,计算机网络可以提供以下一些主要功能. –资源共享。 –信息传输与集中处理。 –均衡负荷与分布处理。 –综合信息服务。
按规模的三种分类
•局域网(LAN):指在一个较小地理范围内的各种计算机网络设备互连在一起的通信网络,可以包含一个或多个子网,通常局限在几千米的范围之内。 •城域网(MAN):主要是由城域范围内的各局域网之间互连而构成的,现在很少提起这个概念。 •广域网(WAN):是由相距较远的局域网或城域网互连而成,通常是除了计算机设备以外,还要涉及一些电信通讯方式。
TCP/IP分层和OSI分层
TCP/IP协议集
IP地址
•IP地址用于标识网络中的一个通信实体,这个通信实体可以是一台主机,也可以是一台打印机,或者 是路由器的某一个端口。而在基于IP协议网络中传输的数据包,都必须使用IP地址来进行标识。
•IP地址是数字型的,IP地址是一个32位(32bit)整数,但通常为了更加便于记忆,通常也把它分 成4个8位的二进制数组成,每8位之间用圆点隔开,每个8位整数可以转换成一个0~255的十进制整 数,因此我们看到的IP地址常常是如下形式:202.9.128.88。
IP与DNS
•IP 地址 –连接至网络的每台计算机都是唯一的 –32 位数字,四个用点号分隔的数字 –包括网络 ID 和主机 ID –网络的类包括 A、B、C和 D 类E –0~126 –128~191 –192~223 –127.0.0.1 –192.168.10.179 –10.11.0.1 •域名系统 –将特定 IP 地址映射至字符串 –映射由域名服务器系统维护
端口
•端口是一个16位的整数,用于表示数据交给哪个通信程序处理。因此,端口是应用程序与外界交流的 出入口,它是一种抽象的软件结构,包括一些数据结构和I/O(基本输入/输出)缓冲区。
•不同的应用程序处理不同端口上的数据,同一台机器上不能有两个程序使用同一个端口,端口号可以 从0到65535,通常将它分为三类: –公认端口(Well Known Ports):从0到1023,它们紧密绑定(Binding)一些服务。 –注册端口(Registered Ports):从1024到49151。它们松散地绑定一些服务。 –动态和/或私有端口(Dynamic and/or Private Ports):从49152到65535,这些端口是应用程序使用的动态端口,应用程序一般不会主动使用这些端口。
•用于实现程序间的通信
•常用的端口
InetAddress
•Java提供了InetAddress类来代表IP地址,InetAddress下还有2个子类:Inet4Address、 Inet6Address,它们分别代表Internet Protocol version 4(IPv4)地址和Internet Protocol version 6(IPv6)地址。
•InetAddress类没有提供构造器,而是提供了如下两个静态方法来获取InetAddress实例: –getByName(String host):根据主机获取对应的InetAddress对象。 –getByAddress(byte[] addr):根据原始IP地址来获取对应的InetAddress对象。
•InetAddress还提供了如下三个方法来获取InetAddress实例对应的IP地址和主机名: –String getCanonicalHostName():获取此 IP 地址的全限定域名。 –String getHostAddress():返回该InetAddress实例对应的IP地址字符串(以字符串形式)。 –String getHostName():获取此 IP 地址的主机名。
URLDecoder和URLEncoder
•URLDecoder类包含一个decode(String s,String enc)静态方法,它可以将看上去是乱码的特殊字符串转转成普通字符串。 •URLEncoder类包含一个encode(String s,String enc)静态方法,它可以将普通字符串转换成application/x-www-form-urlencoded MIME字符串。 URL
•URL(Uniform Resource Locator)对象代表统一资源定位器,它是指向互联网“资源”的指针。 资源可以是简单的文件或目录,也可以是对更复杂的对象引用,例如对数据库或搜索引擎的查询。通常 情况而言,URL可以由协议名、主机、端口和资源组成。即满足如下格式: •protocol://host:port/resourceName
URLConnection
•程序可以通过URLConnection实例向该URL发送请求、读取URL引用的资源。 •通常创建一个和 URL 的连接,并发送请求、读取此 URL 引用的资源需要如下几个步骤: –(1)通过调用URL对象openConnection()方法来创建URLConnection对象。 –(2)设置URLConnection的参数和普通请求属性。 –(3)如果只是发送GET方式请求,使用connect方法建立和远程资源之间的实际连接即可;如果需要发送POST方式的请求,需要获取URLConnection实例对应的输出流来发送请求参数。 –(4)远程资源变为可用,程序可以访问远程资源的头字段、或通过输入流读取远程资源的数据。
IP协议
•IP协议是Internet上使用的一个关键协议,它的全称是Internet Protocol,即Internet协议,通 常简称IP协议。通过使用IP协议,从而使Internet成为一个允许连接不同类型的计算机和不同操作系 统的网络。 •IP协议只保证计算机能发送和接收分组数据。IP协议负责将消息从一个主机传送到另一个主机,消 息在传送的过程中被分割成一个个的小包。
Java对TCP/IP协议的支持
•TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端 之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。 Java对基于TCP协议的网络通信提供了良好的封装,Java使用Socket对象来代表两端的通信接口, 并通过Socket产生IO流来进行网络通信。
TCP协议
•TCP协议被称作一种端对端协议。这是因为它为两台计算机之间的连接起了重要作用:当一台计算机 需要与另一台远程计算机连接时,TCP协议会让它们建立一个连接:用于发送和接收数据的虚拟链路。
•TCP协议负责收集这些信息包,并将其按适当的次序放好传送,在接收端收到后再将其正确地还原。 TCP协议保证了数据包在传送中准确无误。TCP协议使用重发机制:当一个通信实体发送一个消息给另 一个通信实体后,需要收到另一个通信实体确认信息,如果没有收到另一个通信实体的确认信息,则会 再次重发刚才发送的信息。
•通过这种重发机制,TCP协议向应用程序提供可靠的通信连接,使它能够自动适应网上的各种变化。 即使在 Internet 暂时出现堵塞的情况下,TCP也能够保证通信的可靠。
ServerSocket
•ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态。ServerSocket包含一个监听来自客户端连接请求的方法: •Socket accept():如果接收到一个客户端Socket的连接请求,该方法将返回一个与连客户端Socket对应的Socket(如图17.4所示每个TCP连接有两个Socket);否则该方法将一直处于等待状态,线程也被阻塞。 •为了创建ServerSocket对象,ServerSocket类提供了如下几个构造器: –ServerSocket(int port):用指定的端口port来创建一个ServerSocket。该端口应该是有一个有效的端口整数值:0~65535。 –ServerSocket(int port,int backlog):增加一个用来改变连接队列长度的参数backlog。 –ServerSocket(int port,int backlog,InetAddress localAddr):在机器存在多个 IP地址的情况下,允许通过localAddr这个参数来指定将ServerSocket绑定到指定的IP地址。
Socket
•客户端通常可使用Socket的构造器来连接到指定服务器,Socket通常可使用如下两个构造器: –Socket(InetAddress/String remoteAddress, int port):创建连接到指定远程主机、远 程端口的Socket,该构造器没有指定本地地址、本地端口,默认使用本地主机的默认IP地址,默 认使用系统动态指定的IP地址。
–Socket(InetAddress/String remoteAddress, int port, InetAddress localAddr, int localPort)::创建连接到指定远程主机、远程端口的Socket,并指定本地IP地址和本地端口 号,适用于本地主机有多个IP地址的情形。
网络通信
•当客户端、服务器端产生了对应的Socket之后,此时就到了如图17.4所示的通信示意图,程序无需再区分服务器、客户端,而是通过各自的Socket进行通信,Socket提供如下两个方法来获取输入流和输出流: –InputStream getInputStream():返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据。 –OutputStream getOutputStream():返回该Socket对象对应的输出流,让程序通过该输出流向Socket中输出数据。
客户端
•通过Socket建立对象并指定要连接的服务端主机以及端口。 Socket s = new Socket(“192.168.1.1”,9999); OutputStream out = s.getOutputStream(); out.write(“hello”.getBytes()); s.close(); 服务端
•建立服务端需要监听一个端口 ServerSocket ss = new ServerSocket(9999); Socket s = ss.accept (); InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int num = in.read(buf); String str = new String(buf,0,num); System.out.println(s.getInetAddress().toString()+”:”+str); s.close(); ss.close(); 加入多线程支持
•实际应用中的客户端则可能需要和服务器端保持长时间通信,即服务器需要不断地读取客户端数据, 并向客户端写入数据;客户端也需要不断地读取服务器数据,并向服务器写入数据。
•使用传统BufferedReader的readLine()方法读取数据时,当该方法成功返回之前,线程被阻塞, 程序无法继续执行。考虑到这个原因,因此服务器应该每个Socket单独启动一条线程,每条线程负责 与一个客户端进行通信。
•客户端读取服务器数据的线程同样会被阻塞,所以系统应该单独启动一条线程,该线程专门负责读取 服务器数据。
NIO实现非阻塞通信
•Java的NIO为非阻塞式的Socket通信提供了如下几个特殊类: –Selector:它是SelectableChannel对象的多路复用器,所有希望采用非阻塞方式进行通信的 Channel都应该注册到Selector对象。可通过调用此类的静态open()方法来创建Selector实 例,该方法将使用系统默认的Selector来返回新的Selector。
–SelectableChannel:它代表可以支持非阻塞IO操作的Channel对象,可以将其注册到 Selector上,这种注册的关系由SelectionKey实例表示。Selector对象提供了一个select()方 法,该方法允许应用程序同时监控多个IO Channel。
–SelectionKey:该对象代表SelectableChannel和Selector之间的注册关系。
–ServerSocketChannel:支持非阻塞操作,对应于java.net.ServerSocket这个类,提供了 TCP协议IO接口,只支持OP_ACCEPT操作。该类也提供了accept()方法,功能相当于 ServerSocket提供的accept()方法。
–SocketChannel:支持非阻塞操作,对应于java.net.Socket这个类,提供了TCP协议IO接 口,支持OP_CONNECT,OP_READ和OP_WRITE操作。这个类还实现了ByteChannel接 口、ScatteringByteChannel接口和GatheringByteChannel接口,所以可以直接通过 SocketChannel来读写ByteBuffer对象。
NIO的非阻塞通信
•服务器上所有Channel(包括ServerSocketChannel和SocketChannel)都需要向Selector注 册,而该Selector则负责监视这些Socket的IO状态,当其中任意一个或多个Channel具有可用的IO 操作时,该Selector的select()方法将会返回大于0的整数,该整数值就表示该Selector上有多少个 Channel具有可用的IO操作,并提供了selectedKeys()方法来返回这些Channel对应的 SelectionKey集合。正是通过Selector,使得服务器端只需要不断地调用Selector实例的select() 方法即可知道当前所有Channel是否有需要处理的IO操作。
UDP协议
•UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但这两个Socket之 间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象,Java提供了DatagramSocket对 象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据 报。
UDP和TCP的对比
•TCP协议:可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大。 •UDP协议:不可靠,差错控制开销较小,传输大小限制在64K以下,不需要建立连接。 发送数据报
•DatagramSocket本身只是码头,不维护状态,不能产生IO流,它的唯一作用就是接受和发送数据 报,Java使用DatagramPacket来代表数据报,DatagramSocket接收和发送的数据都是通过 DatagramPacket对象完成的。
•DatagramSocket的构造器: –DatagramSocket():创建一个DatagramSocket实例,并将该对象绑定到本机默认IP地址、本机所有可用端口中随机选择的某个端口。 –DatagramSocket(int prot):创建一个DatagramSocket实例,并将该对象绑定到本机默认IP地址、指定端口。 –DatagramSocket(int port, InetAddress laddr):创建一个DatagramSocket实例,并将该对象绑定到指定IP地址、指定端口。
DatagramSocket
•DatagramSocket实例,通常在创建服务器时,我们创建指定端口的DatagramSocket实例—— 这样保证其他客户端可以将数据发送到该服务器。一旦得到了DatagramSocket实例之后,就可以通 过如下两个方法来接收和发送数据: –receive(DatagramPacket p):从该DatagramSocket中接收数据报。 –send(DatagramPacket p):以该DatagramSocket对象向外发送数据报。
DatagramPacket
•DatagramPacket自身决定数据报的目的。 •DatagramPacket的构造器: –DatagramPacket(byte buf[],int length):以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据。 –DatagramPacket(byte buf[], int length, InetAddress addr, int port):以一个包含数据的数组来创建DatagramPacket对象,创建该DatagramPacket时还指定了IP地址和端口——这就决定了该数据报的目的。 –DatagramPacket(byte[] buf, int offset, int length):以一个空数组来创建DatagramPacket对象,并指定接收到的数据放入buf数组中时从offset开始,最多放length个字节。 –DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):创建一个用于发送的DatagramPacket对象,也多指定了一个offset参数。
通过DatagramPacket反馈
•获取DatagramPacket对象后,如果想向该数据报的发送者“反馈”一些信息,但由于UDP是面向非 连接的,所以接受者并不知道每个数据报由谁发送过来,但程序可以调用DatagramPacket的如下三 个方法来获取发送者的IP和端口:
–InetAddress getAddress():返回某台机器的 IP 地址。当程序准备发送此数据报时,该方 法返回此数据报的目标机器的IP地址;当程序刚刚接收到一个数据报时,该方法返回该数据报的发 送主机的IP地址。
–int getPort():返回某台机器的端口,当程序准备发送此数据报时,该方法返回此数据报的目标 机器的端口;当程序刚刚接收到一个数据报时,该方法返回该数据报的发送主机的端口。
–SocketAddress getSocketAddress():返回完整SocketAddress,通常由IP地址和端口 组成。当程序准备发送此数据报时,该方法返回此数据报的目标SocketAddress;当程序刚刚接 收到一个数据报时,该方法返回该数据报的源SocketAddress。
发送端
•在发送端,要在数据包对象中明确目的地IP及端口。 DatagramSocket ds = new DatagramSocket(); byte[] by = “hello,udp”.getBytes(); DatagramPacket dp = new DatagramPacket(by,0,by.length, InetAddress.getByName(“127.0.0.1”),10000); ds.send(dp); ds.close();
接收端
•在接收端,要指定监听的端口。 DatagramSocket ds = new DatagramSocket(10000); byte[] by = new byte[1024]; DatagramPacket dp = new DatagramPacket(by,by.length); ds.receive(dp); String str = new String(dp.getData() ); System.out.println(str+"--"+dp.getAddress()); ds.close(); MulticastSocket与多点广播
•DatagramSocket只允许数据报发送给指定的目标地址,而MulticastSocket可以将数据报以广播 方式发送到数量不等的多个客户端。
•若要使用多点广播时,则需要让一个数据报标有一组目标主机地址,当数据报发出后,整个组的所有 主机都能收到该数据报。IP多点广播(或多点发送)实现了将单一信息发送到多个接收者的广播,其 思想是设置一组特殊网络地址作为多点广播地址,每一个多点广播地址都被看作一个组,当客户端需要 发送、接收广播信息时,加入到该组即可。
MulticastSocket
•MulticastSocket把一个DatagramPacket发送到多点广播IP地址后,该数据报将被自动广播给 加入该地址的所有MulticastSocket。MulticastSocket类既可以将数据报发送到多点广播地址,也 可以接收其他主机的广播信息。
•MulticastSocket有点像DatagramSocket,事实上MulticastSocket是DatagramSocket的 一个子类,也就是说MulticastSocket是特殊的DatagramSocket。若要发送一个数据报时,可使 用随机端口创建MulticastSocket,也可以在指定端口来创建MulticastSocket。 MulticastSocket提供了如下三个构造器:
–public MulticastSocket():使用本机默认地址、随机端口来创建一个MulticastSocket对 象。
–public MulticastSocket(int portNumber):使用本机默认地址、指定端口来创建一个 MulticastSocket对象。
–public MulticastSocket(SocketAddress bindaddr):使用本机指定IP地址、指定端口来 创建一个MulticastSocket对象。
加入多点广播
•创建一个MulticastSocket对象后,还需要将该MulticastSocket加入到指定的多点广播地址, MulticastSocket使用joinGroup()方法来加入指定组;使用leaveGroup()方法脱离一个组。
–joinGroup(InetAddress multicastAddr):将该MulticastSocket加入指定的多点广播地址。
–leaveGroup(InetAddress multicastAddr):让该MulticastSocket离开指定的多点广播地 址。
代理服务器
•代理服务器的功能就是代理网络用户去取得网络信息。我们使用网络浏览器直接连接其他Internet 站点取得网络信息时,通常需要发送Request请求来等待响应。代理服务器是介于浏览器和Web服务 器之间的一台服务器,有了它之后,浏览器不是直接到Web服务器去取得网页数据,而是向代理服务 器发出请求,Request请求会先送到代理服务器,由代理服务器来取回浏览器所需要的信息并送回给 网络浏览器。
使用Proxy
•Proxy有如下一个构造器:Proxy(Proxy.Type type, SocketAddress sa):创建表示代理服务 器的Proxy对象。而sa参数指定代理服务器的地址,其中type是该代理服务器的类型,该服务器类型 有如下三种: –Proxy.Type.DIRECT:表示直接连接或代理不存在。 –Proxy.Type.HTTP:表示高级协议的代理,如 HTTP 或 FTP。 –Proxy.Type.SOCKS:表示 SOCKS(V4 或 V5)代理。
•一旦创建了Proxy对象之后,程序就可以在使用URLConnection打开连接时,或创建Socket连接 时传入一个Proxy对象,作为本次连接所使用的代理服务器。
•其中URL包含了一个URLConnection openConnection(Proxy proxy)方法,该方法使用指定 的代理服务器来打开连接;而Socket则提供了一个Socket(Proxy proxy)构造器,该构造器使用指 定的代理服务器创建一个没有连接的Socket对象。
使用ProxySelector
•ProxySelector可以它根据不同的连接使用不同的代理服务器。
•系统默认的ProxySelector会检测各种系统属性和URL协议,然后决定怎样连接不同的主机。当 然,程序也可以调用ProxySelector类的setDefault()静态方法来设置默认代理服务器,也可以调用 getDefault()方法获得系统当前默认的代理服务器。
•程序可以通过System类来设置系统的代理服务器属性,关于代理服务器常用的属性名有如下三个 : –http.proxyHost:设置HTTP访问所使用的代理服务器地址。该属性名的前缀可以改为https、ftp 等,分别用于设置HTTP访问、安全HTTP访问和FTP访问所用的代理服务器地址。
–http.proxyPort:设置HTTP访问所使用的代理服务器端口。该属性名的前缀可以改为https、ftp 等,分别用于设置HTTP访问、安全HTTP访问和FTP访问所用的代理服务器端口。
–http.nonProxyHosts:设置HTTP访问中不需要使用代理服务器的远程主机,可以使用*通配符, 如果有多个地址,多个地址用竖线(|)分隔。
自定义ProxySelector
•系统提供了默认的ProxySelector子类作为代理选择器,开发者可以实现自己的代理选择器,程序 可以通过继承ProxySelector来实现自己的代理选择器。继承ProxySelector需要重写两个方法:
–List<Proxy> select(URI uri):实现该方法让代理选择器根据不同的URI来使用不同的代理服务 器,该方法就是代理选择器管理网络连接使用代理服务器的关键。
–connectFailed(URI uri, SocketAddress sa, IOException ioe):当系统通过默认的代理服 务器建立连接失败后,代理选择器将会自动调用该方法。通过重写该方法可以对连接代理服务器失败的 情形进行处理。
<?xml version="1.0" encoding="ISO-8859-1"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0" metadata-complete="true"> </web-app>
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.threadNum = threadNum; // 初始化threads数组 threads = new DownThread[threadNum]; this.targetFile = targetFile; } public void download() throws Exception { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty( "Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); conn.setRequestProperty("Connection", "Keep-Alive"); // 得到文件大小 fileSize = conn.getContentLength(); conn.disconnect(); int currentPartSize = fileSize / threadNum + 1; RandomAccessFile file = new RandomAccessFile(targetFile, "rw"); // 设置本地文件的大小 file.setLength(fileSize); file.close(); for (int i = 0; i < threadNum; i++) { // 计算每条线程的下载的开始位置 int startPos = i * currentPartSize; // 每个线程使用一个RandomAccessFile进行下载 RandomAccessFile currentPart = new RandomAccessFile(targetFile, "rw"); // 定位该线程的下载位置 currentPart.seek(startPos); // 创建下载线程 threads[i] = new DownThread(startPos, currentPartSize, currentPart); // 启动下载线程 threads[i].start(); } } // 获取下载的完成百分比 public double getCompleteRate() { // 统计多条线程已经下载的总大小 int sumSize = 0; for (int i = 0; i < threadNum; i++) { sumSize += threads[i].length; } // 返回已经完成的百分比 return sumSize * 1.0 / fileSize; } private class DownThread extends Thread { // 当前线程的下载位置 private int startPos; // 定义当前线程负责下载的文件大小 private int currentPartSize; // 当前线程需要下载的文件块 private RandomAccessFile currentPart; // 定义已经该线程已下载的字节数 public int length; public DownThread(int startPos, int currentPartSize, RandomAccessFile currentPart) { this.startPos = startPos; this.currentPartSize = currentPartSize; this.currentPart = currentPart; } @Override public void run() { try { URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url .openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); conn.setRequestProperty( "Accept", "image/gif, image/jpeg, image/pjpeg, image/pjpeg, " + "application/x-shockwave-flash, application/xaml+xml, " + "application/vnd.ms-xpsdocument, application/x-ms-xbap, " + "application/x-ms-application, application/vnd.ms-excel, " + "application/vnd.ms-powerpoint, application/msword, */*"); conn.setRequestProperty("Accept-Language", "zh-CN"); conn.setRequestProperty("Charset", "UTF-8"); InputStream inStream = conn.getInputStream(); // 跳过startPos个字节,表明该线程只下载自己负责哪部分文件。 inStream.skip(this.startPos); byte[] buffer = new byte[1024]; int hasRead = 0; // 读取网络数据,并写入本地文件 while (length < currentPartSize && (hasRead = inStream.read(buffer)) != -1) { currentPart.write(buffer, 0, hasRead); // 累计该线程下载的总大小 length += hasRead; } currentPart.close(); inStream.close(); } catch (Exception e) { e.printStackTrace(); } } } } public class GetPostTest { /** * 向指定URL发送GET方法的请求 * * @param url * 发送请求的URL * @param param * 请求参数,格式满足name1=value1&name2=value2的形式。 * @return URL所代表远程资源的响应 */ public static String sendGet(String url, String param) { String result = ""; String urlName = url + "?" + param; try { URL realUrl = new URL(urlName); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 建立实际的连接 conn.connect(); // 获取所有响应头字段 Map<String, List<String>> map = conn.getHeaderFields(); // 遍历所有的响应头字段 for (String key : map.keySet()) { System.out.println(key + "--->" + map.get(key)); } try ( // 定义BufferedReader输入流来读取URL的响应 BufferedReader in = new BufferedReader(new InputStreamReader( conn.getInputStream(), "utf-8"))) { String line; while ((line = in.readLine()) != null) { result += "\n" + line; } } } catch (Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } return result; } /** * 向指定URL发送POST方法的请求 * * @param url * 发送请求的URL * @param param * 请求参数,格式应该满足name1=value1&name2=value2的形式。 * @return URL所代表远程资源的响应 */ public static String sendPost(String url, String param) { String result = ""; try { URL realUrl = new URL(url); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)"); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); try ( // 获取URLConnection对象对应的输出流 PrintWriter out = new PrintWriter(conn.getOutputStream())) { // 发送请求参数 out.print(param); // flush输出流的缓冲 out.flush(); } try ( // 定义BufferedReader输入流来读取URL的响应 BufferedReader in = new BufferedReader(new InputStreamReader( conn.getInputStream(), "utf-8"))) { String line; while ((line = in.readLine()) != null) { result += "\n" + line; } } } catch (Exception e) { System.out.println("发送POST请求出现异常!" + e); e.printStackTrace(); } return result; } // 提供主方法,测试发送GET请求和POST请求 public static void main(String args[]) { // 发送GET请求 String s = GetPostTest.sendGet("http://localhost:8888/abc/a.jsp", null); System.out.println(s); // 发送POST请求 String s1 = GetPostTest.sendPost("http://localhost:8888/abc/login.jsp", "name=crazyit.org&pass=leegang"); System.out.println(s1); } } public class InetAddressTest { public static void main(String[] args) throws Exception { // 根据主机名来获取对应的InetAddress实例 InetAddress ip = InetAddress.getByName("www.crazyit.org"); // 判断是否可达 System.out.println("crazyit是否可达:" + ip.isReachable(2000)); // 获取该InetAddress实例的IP字符串 System.out.println(ip.getHostAddress()); // 根据原始IP地址来获取对应的InetAddress实例 InetAddress local = InetAddress .getByAddress(new byte[] { 127, 0, 0, 1 }); System.out.println("本机是否可达:" + local.isReachable(5000)); // 获取该InetAddress实例对应的全限定域名 System.out.println(local.getCanonicalHostName()); } } public class MultiThreadDown { public static void main(String[] args) throws Exception { // 初始化DownUtil对象 final DownUtil downUtil = new DownUtil("http://www.crazyit.org/" + "attachments/month_1403/1403202355ff6cc9a4fbf6f14a.png" , "ios.png", 4); // 开始下载 downUtil.download(); new Thread(() -> { while(downUtil.getCompleteRate() < 1) { // 每隔0.1秒查询一次任务的完成进度, // GUI程序中可根据该进度来绘制进度条 System.out.println("已完成:" + downUtil.getCompleteRate()); try { Thread.sleep(1000); } catch (Exception ex){} } }).start(); } } public class URLDecoderTest { public static void main(String[] args) throws Exception { // 将application/x-www-form-urlencoded字符串 // 转换成普通字符串 // 其中的字符串直接从图17.3所示窗口复制过来 String keyWord = URLDecoder.decode("%E7%96%AF%E7%8B%82java", "utf-8"); System.out.println(keyWord); // 将普通字符串转换成 // application/x-www-form-urlencoded字符串 String urlStr = URLEncoder.encode("疯狂Android讲义", "GBK"); System.out.println(urlStr); } } <%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title> 测试页面 </title> <meta name="website" content="http://www.crazyit.org"/> </head> <body> 服务器时间为:<%=new java.util.Date()%> </body> </html> <%@ page contentType="text/html; charset=UTF-8" language="java" errorPage="" %> <% request.setCharacterEncoding("UTF-8"); String name = request.getParameter("name"); String pass = request.getParameter("pass"); if(name.equals("crazyit.org") && pass.equals("leegang")) { out.println("登录成功!"); } else { out.println("登录失败!"); } %> public class SimpleAIOClient { static final int PORT = 30000; public static void main(String[] args) throws Exception { // 用于读取数据的ByteBuffer。 ByteBuffer buff = ByteBuffer.allocate(1024); Charset utf = Charset.forName("utf-8"); try ( // ①创建AsynchronousSocketChannel对象 AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel .open()) { // ②连接远程服务器 clientChannel.connect(new InetSocketAddress("127.0.0.1", PORT)) .get(); // ④ buff.clear(); // ③从clientChannel中读取数据 clientChannel.read(buff).get(); // ⑤ buff.flip(); // 将buff中内容转换为字符串 String content = utf.decode(buff).toString(); System.out.println("服务器信息:" + content); } } } public class SimpleAIOServer { static final int PORT = 30000; public static void main(String[] args) throws Exception { try ( // ①创建AsynchronousServerSocketChannel对象。 AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel .open()) { // ②指定在指定地址、端口监听。 serverChannel.bind(new InetSocketAddress(PORT)); while (true) { // ③采用循环接受来自客户端的连接 Future<AsynchronousSocketChannel> future = serverChannel .accept(); // 获取连接完成后返回的AsynchronousSocketChannel AsynchronousSocketChannel socketChannel = future.get(); // 执行输出。 socketChannel.write( ByteBuffer.wrap("欢迎你来自AIO的世界!".getBytes("UTF-8"))) .get(); } } } }
client
public class Client { private static final int SERVER_PORT = 30000; private Socket socket; private PrintStream ps; private BufferedReader brServer; private BufferedReader keyIn; public void init() { try { // 初始化代表键盘的输入流 keyIn = new BufferedReader(new InputStreamReader(System.in)); // 连接到服务器 socket = new Socket("127.0.0.1", SERVER_PORT); // 获取该Socket对应的输入流和输出流 ps = new PrintStream(socket.getOutputStream()); brServer = new BufferedReader(new InputStreamReader( socket.getInputStream())); String tip = ""; // 采用循环不断地弹出对话框要求输入用户名 while (true) { String userName = JOptionPane.showInputDialog(tip + "输入用户名"); // ① // 将用户输入的用户名的前后增加协议字符串后发送 ps.println(CrazyitProtocol.USER_ROUND + userName + CrazyitProtocol.USER_ROUND); // 读取服务器的响应 String result = brServer.readLine(); // 如果用户重复,开始下次循环 if (result.equals(CrazyitProtocol.NAME_REP)) { tip = "用户名重复!请重新"; continue; } // 如果服务器返回登录成功,结束循环 if (result.equals(CrazyitProtocol.LOGIN_SUCCESS)) { break; } } } // 捕捉到异常,关闭网络资源,并退出该程序 catch (UnknownHostException ex) { System.out.println("找不到远程服务器,请确定服务器已经启动!"); closeRs(); System.exit(1); } catch (IOException ex) { System.out.println("网络异常!请重新登录!"); closeRs(); System.exit(1); } // 以该Socket对应的输入流启动ClientThread线程 new ClientThread(brServer).start(); } // 定义一个读取键盘输出,并向网络发送的方法 private void readAndSend() { try { // 不断读取键盘输入 String line = null; while ((line = keyIn.readLine()) != null) { // 如果发送的信息中有冒号,且以//开头,则认为想发送私聊信息 if (line.indexOf(":") > 0 && line.startsWith("//")) { line = line.substring(2); ps.println(CrazyitProtocol.PRIVATE_ROUND + line.split(":")[0] + CrazyitProtocol.SPLIT_SIGN + line.split(":")[1] + CrazyitProtocol.PRIVATE_ROUND); } else { ps.println(CrazyitProtocol.MSG_ROUND + line + CrazyitProtocol.MSG_ROUND); } } } // 捕捉到异常,关闭网络资源,并退出该程序 catch (IOException ex) { System.out.println("网络通信异常!请重新登录!"); closeRs(); System.exit(1); } } // 关闭Socket、输入流、输出流的方法 private void closeRs() { try { if (keyIn != null) { ps.close(); } if (brServer != null) { ps.close(); } if (ps != null) { ps.close(); } if (socket != null) { keyIn.close(); } } catch (IOException ex) { ex.printStackTrace(); } } public static void main(String[] args) { Client client = new Client(); client.init(); client.readAndSend(); } } public class ClientThread extends Thread { // 该客户端线程负责处理的输入流 BufferedReader br = null; // 使用一个网络输入流来创建客户端线程 public ClientThread(BufferedReader br) { this.br = br; } public void run() { try { String line = null; // 不断从输入流中读取数据,并将这些数据打印输出 while ((line = br.readLine()) != null) { System.out.println(line); /* * 本例仅打印了从服务器端读到的内容。实际上,此处的情况可以更复杂: 如果希望客户端能看到聊天室的用户列表,则可以让服务器在 * 每次有用户登录、用户退出时,将所有用户列表信息都向客户端发送一遍。 * 为了区分服务器发送的是聊天信息,还是用户列表,服务器也应该 * 在要发送的信息前、后都添加一定的协议字符串,客户端此处则根据协议 字符串的不同而进行不同的处理! 更复杂的情况: * 如果两端进行游戏,则还有可能发送游戏信息,例如两端进行五子棋游戏, * 则还需要发送下棋坐标信息等,服务器同样在这些下棋坐标信息前、后 * 添加协议字符串后再发送,客户端就可以根据该信息知道对手的下棋坐标。 */ } } catch (IOException ex) { ex.printStackTrace(); } // 使用finally块来关闭该线程对应的输入流 finally { try { if (br != null) { br.close(); } } catch (IOException ex) { ex.printStackTrace(); } } } } public interface CrazyitProtocol { // 定义协议字符串的长度 int PROTOCOL_LEN = 2; // 下面是一些协议字符串,服务器和客户端交换的信息 // 都应该在前、后添加这种特殊字符串。 String MSG_ROUND = "§γ"; String USER_ROUND = "∏∑"; String LOGIN_SUCCESS = "1"; String NAME_REP = "-1"; String PRIVATE_ROUND = "★【"; String SPLIT_SIGN = "※"; }
server
// 通过组合HashMap对象来实现CrazyitMap,CrazyitMap要求value也不可重复 public class CrazyitMap<K, V> { // 创建一个线程安全的HashMap public Map<K, V> map = Collections.synchronizedMap(new HashMap<K, V>()); // 根据value来删除指定项 public synchronized void removeByValue(Object value) { for (Object key : map.keySet()) { if (map.get(key) == value) { map.remove(key); break; } } } // 获取所有value组成的Set集合 public synchronized Set<V> valueSet() { Set<V> result = new HashSet<V>(); // 将map中所有value添加到result集合中 map.forEach((key , value) -> result.add(value)); return result; } // 根据value查找key。 public synchronized K getKeyByValue(V val) { // 遍历所有key组成的集合 for (K key : map.keySet()) { // 如果指定key对应的value与被搜索的value相同,则返回对应的key if (map.get(key) == val || map.get(key).equals(val)) { return key; } } return null; } // 实现put()方法,该方法不允许value重复 public synchronized V put(K key, V value) { // 遍历所有value组成的集合 for (V val : valueSet()) { // 如果某个value与试图放入集合的value相同 // 则抛出一个RuntimeException异常 if (val.equals(value) && val.hashCode() == value.hashCode()) { throw new RuntimeException("MyMap实例中不允许有重复value!"); } } return map.put(key, value); } } public interface CrazyitProtocol { // 定义协议字符串的长度 int PROTOCOL_LEN = 2; // 下面是一些协议字符串,服务器和客户端交换的信息都应该在前、后添加这种特殊字符串。 String MSG_ROUND = "§γ"; String USER_ROUND = "∏∑"; String LOGIN_SUCCESS = "1"; String NAME_REP = "-1"; String PRIVATE_ROUND = "★【"; String SPLIT_SIGN = "※"; } public class Server { private static final int SERVER_PORT = 30000; // 使用CrazyitMap对象来保存每个客户名字和对应输出流之间的对应关系。 public static CrazyitMap<String, PrintStream> clients = new CrazyitMap<>(); public void init() { try ( // 建立监听的ServerSocket ServerSocket ss = new ServerSocket(SERVER_PORT)) { // 采用死循环来不断接受来自客户端的请求 while (true) { Socket socket = ss.accept(); new ServerThread(socket).start(); } } // 如果抛出异常 catch (IOException ex) { System.out.println("服务器启动失败,是否端口" + SERVER_PORT + "已被占用?"); } } public static void main(String[] args) { Server server = new Server(); server.init(); } } public class ServerThread extends Thread { private Socket socket; BufferedReader br = null; PrintStream ps = null; // 定义一个构造器,用于接收一个Socket来创建ServerThread线程 public ServerThread(Socket socket) { this.socket = socket; } public void run() { try { // 获取该Socket对应的输入流 br = new BufferedReader(new InputStreamReader( socket.getInputStream())); // 获取该Socket对应的输出流 ps = new PrintStream(socket.getOutputStream()); String line = null; while ((line = br.readLine()) != null) { // 如果读到的行以CrazyitProtocol.USER_ROUND开始,并以其结束, // 可以确定读到的是用户登录的用户名 if (line.startsWith(CrazyitProtocol.USER_ROUND) && line.endsWith(CrazyitProtocol.USER_ROUND)) { // 得到真实消息 String userName = getRealMsg(line); // 如果用户名重复 if (Server.clients.map.containsKey(userName)) { System.out.println("重复"); ps.println(CrazyitProtocol.NAME_REP); } else { System.out.println("成功"); ps.println(CrazyitProtocol.LOGIN_SUCCESS); Server.clients.put(userName, ps); } } // 如果读到的行以CrazyitProtocol.PRIVATE_ROUND开始,并以其结束, // 可以确定是私聊信息,私聊信息只向特定的输出流发送 else if (line.startsWith(CrazyitProtocol.PRIVATE_ROUND) && line.endsWith(CrazyitProtocol.PRIVATE_ROUND)) { // 得到真实消息 String userAndMsg = getRealMsg(line); // 以SPLIT_SIGN分割字符串,前半是私聊用户,后半是聊天信息 String user = userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[0]; String msg = userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[1]; // 获取私聊用户对应的输出流,并发送私聊信息 Server.clients.map.get(user).println( Server.clients.getKeyByValue(ps) + "悄悄地对你说:" + msg); } // 公聊要向每个Socket发送 else { // 得到真实消息 String msg = getRealMsg(line); // 遍历clients中的每个输出流 for (PrintStream clientPs : Server.clients.valueSet()) { clientPs.println(Server.clients.getKeyByValue(ps) + "说:" + msg); } } } } // 捕捉到异常后,表明该Socket对应的客户端已经出现了问题 // 所以程序将其对应的输出流从Map中删除 catch (IOException e) { Server.clients.removeByValue(ps); System.out.println(Server.clients.map.size()); // 关闭网络、IO资源 try { if (br != null) { br.close(); } if (ps != null) { ps.close(); } if (socket != null) { socket.close(); } } catch (IOException ex) { ex.printStackTrace(); } } } // 将读到的内容去掉前后的协议字符,恢复成真实数据 private String getRealMsg(String line) { return line.substring(CrazyitProtocol.PROTOCOL_LEN, line.length() - CrazyitProtocol.PROTOCOL_LEN); } } public class NClient { // 定义检测SocketChannel的Selector对象 private Selector selector = null; static final int PORT = 30000; // 定义处理编码和解码的字符集 private Charset charset = Charset.forName("UTF-8"); // 客户端SocketChannel private SocketChannel sc = null; public void init() throws IOException { selector = Selector.open(); InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT); // 调用open静态方法创建连接到指定主机的SocketChannel sc = SocketChannel.open(isa); // 设置该sc以非阻塞方式工作 sc.configureBlocking(false); // 将SocketChannel对象注册到指定Selector sc.register(selector, SelectionKey.OP_READ); // 启动读取服务器端数据的线程 new ClientThread().start(); // 创建键盘输入流 Scanner scan = new Scanner(System.in); while (scan.hasNextLine()) { // 读取键盘输入 String line = scan.nextLine(); // 将键盘输入的内容输出到SocketChannel中 sc.write(charset.encode(line)); } } // 定义读取服务器数据的线程 private class ClientThread extends Thread { public void run() { try { while (selector.select() > 0) // ① { // 遍历每个有可用IO操作Channel对应的SelectionKey for (SelectionKey sk : selector.selectedKeys()) { // 删除正在处理的SelectionKey selector.selectedKeys().remove(sk); // 如果该SelectionKey对应的Channel中有可读的数据 if (sk.isReadable()) { // 使用NIO读取Channel中的数据 SocketChannel sc = (SocketChannel) sk.channel(); ByteBuffer buff = ByteBuffer.allocate(1024); String content = ""; while (sc.read(buff) > 0) { sc.read(buff); buff.flip(); content += charset.decode(buff); } // 打印输出读取的内容 System.out.println("聊天信息:" + content); // 为下一次读取作准备 sk.interestOps(SelectionKey.OP_READ); } } } } catch (IOException ex) { ex.printStackTrace(); } } } public static void main(String[] args) throws IOException { new NClient().init(); } } public class NServer { // 用于检测所有Channel状态的Selector private Selector selector = null; static final int PORT = 30000; // 定义实现编码、解码的字符集对象 private Charset charset = Charset.forName("UTF-8"); public void init() throws IOException { selector = Selector.open(); // 通过open方法来打开一个未绑定的ServerSocketChannel实例 ServerSocketChannel server = ServerSocketChannel.open(); InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT); // 将该ServerSocketChannel绑定到指定IP地址 server.bind(isa); // 设置ServerSocket以非阻塞方式工作 server.configureBlocking(false); // 将server注册到指定Selector对象 server.register(selector, SelectionKey.OP_ACCEPT); while (selector.select() > 0) { // 依次处理selector上的每个已选择的SelectionKey for (SelectionKey sk : selector.selectedKeys()) { // 从selector上的已选择Key集中删除正在处理的SelectionKey selector.selectedKeys().remove(sk); // ① // 如果sk对应的Channel包含客户端的连接请求 if (sk.isAcceptable()) // ② { // 调用accept方法接受连接,产生服务器端的SocketChannel SocketChannel sc = server.accept(); // 设置采用非阻塞模式 sc.configureBlocking(false); // 将该SocketChannel也注册到selector sc.register(selector, SelectionKey.OP_READ); // 将sk对应的Channel设置成准备接受其他请求 sk.interestOps(SelectionKey.OP_ACCEPT); } // 如果sk对应的Channel有数据需要读取 if (sk.isReadable()) // ③ { // 获取该SelectionKey对应的Channel,该Channel中有可读的数据 SocketChannel sc = (SocketChannel) sk.channel(); // 定义准备执行读取数据的ByteBuffer ByteBuffer buff = ByteBuffer.allocate(1024); String content = ""; // 开始读取数据 try { while (sc.read(buff) > 0) { buff.flip(); content += charset.decode(buff); } // 打印从该sk对应的Channel里读取到的数据 System.out.println("读取的数据:" + content); // 将sk对应的Channel设置成准备下一次读取 sk.interestOps(SelectionKey.OP_READ); } // 如果捕捉到该sk对应的Channel出现了异常,即表明该Channel // 对应的Client出现了问题,所以从Selector中取消sk的注册 catch (IOException ex) { // 从Selector中删除指定的SelectionKey sk.cancel(); if (sk.channel() != null) { sk.channel().close(); } } // 如果content的长度大于0,即聊天信息不为空 if (content.length() > 0) { // 遍历该selector里注册的所有SelectionKey for (SelectionKey key : selector.keys()) { // 获取该key对应的Channel Channel targetChannel = key.channel(); // 如果该channel是SocketChannel对象 if (targetChannel instanceof SocketChannel) { // 将读到的内容写入该Channel中 SocketChannel dest = (SocketChannel) targetChannel; dest.write(charset.encode(content)); } } } } } } } public static void main(String[] args) throws IOException { new NServer().init(); } }
client
public class ClientThread implements Runnable { // 该线程负责处理的Socket private Socket s; // 该线程所处理的Socket所对应的输入流 BufferedReader br = null; public ClientThread(Socket s) throws IOException { this.s = s; br = new BufferedReader(new InputStreamReader(s.getInputStream())); } public void run() { try { String content = null; // 不断读取Socket输入流中的内容,并将这些内容打印输出 while ((content = br.readLine()) != null) { System.out.println(content); } } catch (Exception e) { e.printStackTrace(); } } } public class MyClient { public static void main(String[] args) throws Exception { Socket s = new Socket("127.0.0.1", 30000); // 客户端启动ClientThread线程不断读取来自服务器的数据 new Thread(new ClientThread(s)).start(); // ① // 获取该Socket对应的输出流 PrintStream ps = new PrintStream(s.getOutputStream()); String line = null; // 不断读取键盘输入 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); while ((line = br.readLine()) != null) { // 将用户的键盘输入内容写入Socket对应的输出流 ps.println(line); } } }
server
public class MyServer { // 定义保存所有Socket的ArrayList,并将其包装为线程安全的 public static List<Socket> socketList = Collections .synchronizedList(new ArrayList<>()); public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(30000); while (true) { // 此行代码会阻塞,将一直等待别人的连接 Socket s = ss.accept(); socketList.add(s); // 每当客户端连接后启动一条ServerThread线程为该客户端服务 new Thread(new ServerThread(s)).start(); } } } // 负责处理每个线程通信的线程类 public class ServerThread implements Runnable { // 定义当前线程所处理的Socket Socket s = null; // 该线程所处理的Socket所对应的输入流 BufferedReader br = null; public ServerThread(Socket s) throws IOException { this.s = s; // 初始化该Socket对应的输入流 br = new BufferedReader(new InputStreamReader(s.getInputStream())); } public void run() { try { String content = null; // 采用循环不断从Socket中读取客户端发送过来的数据 while ((content = readFromClient()) != null) { // 遍历socketList中的每个Socket, // 将读到的内容向每个Socket发送一次 for (Socket s : MyServer.socketList) { PrintStream ps = new PrintStream(s.getOutputStream()); ps.println(content); } } } catch (IOException e) { e.printStackTrace(); } } // 定义读取客户端数据的方法 private String readFromClient() { try { return br.readLine(); } // 如果捕捉到异常,表明该Socket对应的客户端已经关闭 catch (IOException e) { // 删除该Socket。 MyServer.socketList.remove(s); // ① } return null; } } public class Client { public static void main(String[] args) throws Exception { Socket s = new Socket("localhost", 30000); Scanner scan = new Scanner(s.getInputStream()); while (scan.hasNextLine()) { System.out.println(scan.nextLine()); } PrintStream ps = new PrintStream(s.getOutputStream()); ps.println("客户端的第一行数据"); ps.println("客户端的第二行数据"); ps.close(); scan.close(); s.close(); } } public class Server { public static void main(String[] args) throws Exception { ServerSocket ss = new ServerSocket(30000); Socket socket = ss.accept(); PrintStream ps = new PrintStream(socket.getOutputStream()); ps.println("服务器的第一行数据"); ps.println("服务器的第二行数据"); // 关闭socket的输出流,表明输出数据已经结束 socket.shutdownOutput(); // 下面语句将输出false,表明socket还未关闭。 System.out.println(socket.isClosed()); Scanner scan = new Scanner(socket.getInputStream()); while (scan.hasNextLine()) { System.out.println(scan.nextLine()); } scan.close(); socket.close(); ss.close(); } } public class AIOClient { final static String UTF_8 = "utf-8"; final static int PORT = 30000; // 与服务器端通信的异步Channel AsynchronousSocketChannel clientChannel; JFrame mainWin = new JFrame("多人聊天"); JTextArea jta = new JTextArea(16, 48); JTextField jtf = new JTextField(40); JButton sendBn = new JButton("发送"); public void init() { mainWin.setLayout(new BorderLayout()); jta.setEditable(false); mainWin.add(new JScrollPane(jta), BorderLayout.CENTER); JPanel jp = new JPanel(); jp.add(jtf); jp.add(sendBn); // 发送消息的Action,Action是ActionListener的子接口 Action sendAction = new AbstractAction() { public void actionPerformed(ActionEvent e) { String content = jtf.getText(); if (content.trim().length() > 0) { try { // 将content内容写入Channel中 clientChannel .write(ByteBuffer.wrap(content.trim().getBytes( UTF_8))).get(); // ① } catch (Exception ex) { ex.printStackTrace(); } } // 清空输入框 jtf.setText(""); } }; sendBn.addActionListener(sendAction); // 将Ctrl+Enter键和"send"关联 jtf.getInputMap().put( KeyStroke.getKeyStroke('\n', java.awt.event.InputEvent.CTRL_MASK), "send"); // 将"send"和sendAction关联 jtf.getActionMap().put("send", sendAction); mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWin.add(jp, BorderLayout.SOUTH); mainWin.pack(); mainWin.setVisible(true); } public void connect() throws Exception { // 定义一个ByteBuffer准备读取数据 final ByteBuffer buff = ByteBuffer.allocate(1024); // 创建一个线程池 ExecutorService executor = Executors.newFixedThreadPool(80); // 以指定线程池来创建一个AsynchronousChannelGroup AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup .withThreadPool(executor); // 以channelGroup作为组管理器来创建AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open(channelGroup); // 让AsynchronousSocketChannel连接到指定IP、指定端口 clientChannel.connect(new InetSocketAddress("127.0.0.1", PORT)).get(); jta.append("---与服务器连接成功---\n"); buff.clear(); clientChannel.read(buff, null, new CompletionHandler<Integer, Object>() // ② { @Override public void completed(Integer result, Object attachment) { buff.flip(); // 将buff中内容转换为字符串 String content = StandardCharsets.UTF_8.decode(buff) .toString(); // 显示从服务器端读取的数据 jta.append("某人说:" + content + "\n"); buff.clear(); clientChannel.read(buff, null, this); } @Override public void failed(Throwable ex, Object attachment) { System.out.println("读取数据失败: " + ex); } }); } public static void main(String[] args) throws Exception { AIOClient client = new AIOClient(); client.init(); client.connect(); } } public class AIOServer { static final int PORT = 30000; final static String UTF_8 = "utf-8"; static List<AsynchronousSocketChannel> channelList = new ArrayList<>(); public void startListen() throws InterruptedException, Exception { // 创建一个线程池 ExecutorService executor = Executors.newFixedThreadPool(20); // 以指定线程池来创建一个AsynchronousChannelGroup AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup .withThreadPool(executor); // 以指定线程池来创建一个AsynchronousServerSocketChannel AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel .open(channelGroup) // 指定监听本机的PORT端口 .bind(new InetSocketAddress(PORT)); // 使用CompletionHandler接受来自客户端的连接请求 serverChannel.accept(null, new AcceptHandler(serverChannel)); // ① Thread.sleep(5000); } public static void main(String[] args) throws Exception { AIOServer server = new AIOServer(); server.startListen(); } } // 实现自己的CompletionHandler类 class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> { private AsynchronousServerSocketChannel serverChannel; public AcceptHandler(AsynchronousServerSocketChannel sc) { this.serverChannel = sc; } // 定义一个ByteBuffer准备读取数据 ByteBuffer buff = ByteBuffer.allocate(1024); // 当实际IO操作完成时候触发该方法 @Override public void completed(final AsynchronousSocketChannel sc, Object attachment) { // 记录新连接的进来的Channel AIOServer.channelList.add(sc); // 准备接受客户端的下一次连接 serverChannel.accept(null, this); sc.read(buff, null, new CompletionHandler<Integer, Object>() // ② { @Override public void completed(Integer result, Object attachment) { buff.flip(); // 将buff中内容转换为字符串 String content = StandardCharsets.UTF_8.decode(buff) .toString(); // 遍历每个Channel,将收到的信息写入各Channel中 for (AsynchronousSocketChannel c : AIOServer.channelList) { try { c.write(ByteBuffer.wrap(content .getBytes(AIOServer.UTF_8))).get(); } catch (Exception ex) { ex.printStackTrace(); } } buff.clear(); // 读取下一次数据 sc.read(buff, null, this); } @Override public void failed(Throwable ex, Object attachment) { System.out.println("读取数据失败: " + ex); // 从该Channel读取数据失败,就将该Channel删除 AIOServer.channelList.remove(sc); } }); } @Override public void failed(Throwable ex, Object attachment) { System.out.println("连接失败: " + ex); } } public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 30000); // ① // 将Socket对应的输入流包装成BufferedReader BufferedReader br = new BufferedReader(new InputStreamReader( socket.getInputStream())); // 进行普通IO操作 String line = br.readLine(); System.out.println("来自服务器的数据:" + line); // 关闭输入流、socket br.close(); socket.close(); } } public class Server { public static void main(String[] args) throws IOException { // 创建一个ServerSocket,用于监听客户端Socket的连接请求 ServerSocket ss = new ServerSocket(30000); // 采用循环不断接受来自客户端的请求 while (true) { // 每当接受到客户端Socket的请求,服务器端也对应产生一个Socket Socket s = ss.accept(); // 将Socket对应的输出流包装成PrintStream PrintStream ps = new PrintStream(s.getOutputStream()); // 进行普通IO操作 ps.println("您好,您收到了服务器的新年祝福!"); // 关闭输出流,关闭Socket ps.close(); s.close(); } } } // 定义交谈的对话框 public class ChatFrame extends JDialog { // 聊天信息区 JTextArea msgArea = new JTextArea(12, 45); // 聊天输入区 JTextField chatField = new JTextField(30); // 发送聊天信息的按钮 JButton sendBn = new JButton("发送"); // 该交谈窗口对应的用户 UserInfo user; // 构造器,用于初始化交谈对话框的界面 public ChatFrame(LanTalk parent, final UserInfo user) { super(parent, "和" + user.getName() + "聊天中", false); this.user = user; msgArea.setEditable(false); add(new JScrollPane(msgArea)); JPanel buttom = new JPanel(); buttom.add(new JLabel("输入信息:")); buttom.add(chatField); buttom.add(sendBn); add(buttom, BorderLayout.SOUTH); // 发送消息的Action,Action是ActionListener的子接口 Action sendAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent evt) { InetSocketAddress dest = (InetSocketAddress) user.getAddress(); // 在聊友列表中,所有人项的SocketAddress是null // 这表明是向所有人发送消息 if (dest == null) { LoginFrame.comUtil.broadCast(chatField.getText()); msgArea.setText("您对大家说:" + chatField.getText() + "\n" + msgArea.getText()); } // 向私人发送信息 else { // 获取发送消息的目的 dest = new InetSocketAddress(dest.getHostName(), dest.getPort() + 1); LoginFrame.comUtil.sendSingle(chatField.getText(), dest); msgArea.setText("您对" + user.getName() + "说:" + chatField.getText() + "\n" + msgArea.getText()); } chatField.setText(""); } }; sendBn.addActionListener(sendAction); // 将Ctrl+Enter键和"send"关联 chatField.getInputMap().put( KeyStroke.getKeyStroke('\n', java.awt.event.InputEvent.CTRL_MASK), "send"); // 将"send"与sendAction关联 chatField.getActionMap().put("send", sendAction); pack(); } // 定义向聊天区域添加消息的方法 public void addString(String msg) { msgArea.setText(msg + "\n" + msgArea.getText()); } } // 聊天交换信息的工具类 public class ComUtil { // 定义本程序通信所使用的字符集 public static final String CHARSET = "utf-8"; // 使用常量作为本程序的多点广播IP地址 private static final String BROADCAST_IP = "230.0.0.1"; // 使用常量作为本程序的多点广播目的的端口 // DatagramSocket所用的的端口为该端口+1。 public static final int BROADCAST_PORT = 30000; // 定义每个数据报的最大大小为4K private static final int DATA_LEN = 4096; // 定义本程序的MulticastSocket实例 private MulticastSocket socket = null; // 定义本程序私聊的Socket实例 private DatagramSocket singleSocket = null; // 定义广播的IP地址 private InetAddress broadcastAddress = null; // 定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; // 以指定字节数组创建准备接受数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); // 定义一个用于发送的DatagramPacket对象 private DatagramPacket outPacket = null; // 聊天的主界面程序 private LanTalk lanTalk; // 构造器,初始化资源 public ComUtil(LanTalk lanTalk) throws Exception { this.lanTalk = lanTalk; // 创建用于发送、接收数据的MulticastSocket对象 // 因为该MulticastSocket对象需要接收,所以有指定端口 socket = new MulticastSocket(BROADCAST_PORT); // 创建私聊用的DatagramSocket对象 singleSocket = new DatagramSocket(BROADCAST_PORT + 1); broadcastAddress = InetAddress.getByName(BROADCAST_IP); // 将该socket加入指定的多点广播地址 socket.joinGroup(broadcastAddress); // 设置本MulticastSocket发送的数据报被回送到自身 socket.setLoopbackMode(false); // 初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0], 0, broadcastAddress, BROADCAST_PORT); // 启动两个读取网络数据的线程 new ReadBroad().start(); Thread.sleep(1); new ReadSingle().start(); } // 广播消息的工具方法 public void broadCast(String msg) { try { // 将msg字符串转换字节数组 byte[] buff = msg.getBytes(CHARSET); // 设置发送用的DatagramPacket里的字节数据 outPacket.setData(buff); // 发送数据报 socket.send(outPacket); } // 捕捉异常 catch (IOException ex) { ex.printStackTrace(); if (socket != null) { // 关闭该Socket对象 socket.close(); } JOptionPane.showMessageDialog(null, "发送信息异常,请确认30000端口空闲,且网络连接正常!", "网络异常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } // 定义向单独用户发送消息的方法 public void sendSingle(String msg, SocketAddress dest) { try { // 将msg字符串转换字节数组 byte[] buff = msg.getBytes(CHARSET); DatagramPacket packet = new DatagramPacket(buff, buff.length, dest); singleSocket.send(packet); } // 捕捉异常 catch (IOException ex) { ex.printStackTrace(); if (singleSocket != null) { // 关闭该Socket对象 singleSocket.close(); } JOptionPane.showMessageDialog(null, "发送信息异常,请确认30001端口空闲,且网络连接正常!", "网络异常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } // 不断从DatagramSocket中读取数据的线程 class ReadSingle extends Thread { // 定义接收网络数据的字节数组 byte[] singleBuff = new byte[DATA_LEN]; private DatagramPacket singlePacket = new DatagramPacket(singleBuff, singleBuff.length); public void run() { while (true) { try { // 读取Socket中的数据。 singleSocket.receive(singlePacket); // 处理读到的信息 lanTalk.processMsg(singlePacket, true); } // 捕捉异常 catch (IOException ex) { ex.printStackTrace(); if (singleSocket != null) { // 关闭该Socket对象 singleSocket.close(); } JOptionPane.showMessageDialog(null, "接收信息异常,请确认30001端口空闲,且网络连接正常!", "网络异常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } } } // 持续读取MulticastSocket的线程 class ReadBroad extends Thread { public void run() { while (true) { try { // 读取Socket中的数据。 socket.receive(inPacket); // 打印输出从socket中读取的内容 String msg = new String(inBuff, 0, inPacket.getLength(), CHARSET); // 读到的内容是在线信息 if (msg.startsWith(YeekuProtocol.PRESENCE) && msg.endsWith(YeekuProtocol.PRESENCE)) { String userMsg = msg.substring(2, msg.length() - 2); String[] userInfo = userMsg .split(YeekuProtocol.SPLITTER); UserInfo user = new UserInfo(userInfo[1], userInfo[0], inPacket.getSocketAddress(), 0); // 控制是否需要添加该用户的旗标 boolean addFlag = true; ArrayList<Integer> delList = new ArrayList<>(); // 遍历系统中已有的所有用户,该循环必须循环完成 for (int i = 1; i < lanTalk.getUserNum(); i++) { UserInfo current = lanTalk.getUser(i); // 将所有用户失去联系的次数加1 current.setLost(current.getLost() + 1); // 如果该信息由指定用户发送过来 if (current.equals(user)) { current.setLost(0); // 设置该用户无须添加 addFlag = false; } if (current.getLost() > 2) { delList.add(i); } } // 删除delList中的所有索引对应的用户 for (int i = 0; i < delList.size(); i++) { lanTalk.removeUser(delList.get(i)); } if (addFlag) { // 添加新用户 lanTalk.addUser(user); } } // 读到的内容是公聊信息 else { // 处理读到的信息 lanTalk.processMsg(inPacket, false); } } // 捕捉异常 catch (IOException ex) { ex.printStackTrace(); if (socket != null) { // 关闭该Socket对象 socket.close(); } JOptionPane.showMessageDialog(null, "接收信息异常,请确认30000端口空闲,且网络连接正常!", "网络异常", JOptionPane.ERROR_MESSAGE); System.exit(1); } } } } } public class LanTalk extends JFrame { private DefaultListModel<UserInfo> listModel = new DefaultListModel<>(); // 定义一个JList对象 private JList<UserInfo> friendsList = new JList<>(listModel); // 定义一个用于格式化日期的格式器 private DateFormat formatter = DateFormat.getDateTimeInstance(); public LanTalk() { super("局域网聊天"); // 设置该JList使用ImageCellRenderer作为单元格绘制器 friendsList.setCellRenderer(new ImageCellRenderer()); listModel.addElement(new UserInfo("all", "所有人", null, -2000)); friendsList.addMouseListener(new ChangeMusicListener()); add(new JScrollPane(friendsList)); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(2, 2, 160, 600); } // 根据地址来查询用户 public UserInfo getUserBySocketAddress(SocketAddress address) { for (int i = 1; i < getUserNum(); i++) { UserInfo user = getUser(i); if (user.getAddress() != null && user.getAddress().equals(address)) { return user; } } return null; } // ------下面四个方法是对ListModel的包装------ // 向用户列表中添加用户 public void addUser(UserInfo user) { listModel.addElement(user); } // 从用户列表中删除用户 public void removeUser(int pos) { listModel.removeElementAt(pos); } // 获取该聊天窗口的用户数量 public int getUserNum() { return listModel.size(); } // 获取指定位置的用户 public UserInfo getUser(int pos) { return listModel.elementAt(pos); } // 实现JList上的鼠标双击事件的监听器 class ChangeMusicListener extends MouseAdapter { public void mouseClicked(MouseEvent e) { // 如果鼠标的击键次数大于2 if (e.getClickCount() >= 2) { // 取出鼠标双击时选中的列表项 UserInfo user = (UserInfo) friendsList.getSelectedValue(); // 如果该列表项对应用户的交谈窗口为null if (user.getChatFrame() == null) { // 为该用户创建一个交谈窗口,并让该用户引用该窗口 user.setChatFrame(new ChatFrame(null, user)); } // 如果该用户的窗口没有显示,则让该用户的窗口显示出来 if (!user.getChatFrame().isShowing()) { user.getChatFrame().setVisible(true); } } } } /** * 处理网络数据报,该方法将根据聊天信息得到聊天者, 并将信息显示在聊天对话框中。 * * @param packet * 需要处理的数据报 * @param single * 该信息是否为私聊信息 */ public void processMsg(DatagramPacket packet, boolean single) { // 获取该发送该数据报的SocketAddress InetSocketAddress srcAddress = (InetSocketAddress) packet .getSocketAddress(); // 如果是私聊信息,则该Packet获取的是DatagramSocket的地址, // 将端口减1才是对应的MulticastSocket的地址 if (single) { srcAddress = new InetSocketAddress(srcAddress.getHostName(), srcAddress.getPort() - 1); } UserInfo srcUser = getUserBySocketAddress(srcAddress); if (srcUser != null) { // 确定消息将要显示到哪个用户对应窗口上。 UserInfo alertUser = single ? srcUser : getUser(0); // 如果该用户对应的窗口为空,显示该窗口 if (alertUser.getChatFrame() == null) { alertUser.setChatFrame(new ChatFrame(null, alertUser)); } // 定义添加的提示信息 String tipMsg = single ? "对您说:" : "对大家说:"; try { // 显示提示信息 alertUser.getChatFrame().addString( srcUser.getName() + tipMsg + "......................(" + formatter.format(new Date()) + ")\n" + new String(packet.getData(), 0, packet .getLength(), ComUtil.CHARSET) + "\n"); } catch (Exception ex) { ex.printStackTrace(); } if (!alertUser.getChatFrame().isShowing()) { alertUser.getChatFrame().setVisible(true); } } } // 主方法,程序的入口 public static void main(String[] args) { LanTalk lanTalk = new LanTalk(); new LoginFrame(lanTalk, "请输入用户名、头像后登录"); } } // 定义用于改变JList列表项外观的类 class ImageCellRenderer extends JPanel implements ListCellRenderer<UserInfo> { private ImageIcon icon; private String name; // 定义绘制单元格时的背景色 private Color background; // 定义绘制单元格时的前景色 private Color foreground; @Override public Component getListCellRendererComponent(JList list, UserInfo userInfo, int index, boolean isSelected, boolean cellHasFocus) { // 设置图标 icon = new ImageIcon("ico/" + userInfo.getIcon() + ".gif"); name = userInfo.getName(); // 设置背景色、前景色 background = isSelected ? list.getSelectionBackground() : list .getBackground(); foreground = isSelected ? list.getSelectionForeground() : list .getForeground(); // 返回该JPanel对象作为单元格绘制器 return this; } // 重写paintComponent方法,改变JPanel的外观 public void paintComponent(Graphics g) { int imageWidth = icon.getImage().getWidth(null); int imageHeight = icon.getImage().getHeight(null); g.setColor(background); g.fillRect(0, 0, getWidth(), getHeight()); g.setColor(foreground); // 绘制好友图标 g.drawImage(icon.getImage(), getWidth() / 2 - imageWidth / 2, 10, null); g.setFont(new Font("SansSerif", Font.BOLD, 18)); // 绘制好友用户名 g.drawString(name, getWidth() / 2 - name.length() * 10, imageHeight + 30); } // 通过该方法来设置该ImageCellRenderer的最佳大小 public Dimension getPreferredSize() { return new Dimension(60, 80); } } // 登录用的对话框 public class LoginFrame extends JDialog { public JLabel tip; public JTextField userField = new JTextField("李刚", 20); public JComboBox<Integer> iconList = new JComboBox<>(new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); private JButton loginBn = new JButton("登录"); // 聊天的主界面 private LanTalk chatFrame; // 聊天通信的工具实例 public static ComUtil comUtil; // 构造器,用于初始化的登录对话框 public LoginFrame(LanTalk parent, String msg) { super(parent, "输入名字后登录", true); this.chatFrame = parent; setLayout(new GridLayout(5, 1)); JPanel jp = new JPanel(); tip = new JLabel(msg); tip.setFont(new Font("Serif", Font.BOLD, 16)); jp.add(tip); add(jp); add(getPanel("用户名", userField)); iconList.setPreferredSize(new Dimension(224, 20)); add(getPanel("图 标", iconList)); JPanel bp = new JPanel(); loginBn.addActionListener(new MyActionListener(this)); bp.add(loginBn); add(bp); pack(); setVisible(true); } // 工具方法,该方法将一个字符串和组件组合成JPanel对象 private JPanel getPanel(String name, JComponent jf) { JPanel jp = new JPanel(); jp.add(new JLabel(name + ":")); jp.add(jf); return jp; } // 该方法用于改变登录窗口最上面的提示信息 public void setTipMsg(String tip) { this.tip.setText(tip); } // 定义一个事件监听器 class MyActionListener implements ActionListener { private LoginFrame loginFrame; public MyActionListener(LoginFrame loginFrame) { this.loginFrame = loginFrame; } // 当鼠标单击事件发生时 public void actionPerformed(ActionEvent evt) { try { // 初始化聊天通信类 comUtil = new ComUtil(chatFrame); final String loginMsg = YeekuProtocol.PRESENCE + userField.getText() + YeekuProtocol.SPLITTER + iconList.getSelectedObjects()[0] + YeekuProtocol.PRESENCE; comUtil.broadCast(loginMsg); // 启动定时器每20秒广播一次在线信息 javax.swing.Timer timer = new javax.swing.Timer(1000 * 10 , event-> comUtil.broadCast(loginMsg)); timer.start(); loginFrame.setVisible(false); chatFrame.setVisible(true); } catch (Exception ex) { loginFrame.setTipMsg("确认30001端口空闲,且网络正常!"); } } } } public class UserInfo { // 该用户的图标 private String icon; // 该用户的名字 private String name; // 该用户的MulitcastSocket所在的IP和端口 private SocketAddress address; // 该用户失去联系的次数 private int lost; // 该用户对应的交谈窗口 private ChatFrame chatFrame; public UserInfo() { } // 有参数的构造器 public UserInfo(String icon, String name, SocketAddress address, int lost) { this.icon = icon; this.name = name; this.address = address; this.lost = lost; } // 省略所有成员变量的setter和getter方法 // icon的setter和getter方法 public void setIcon(String icon) { this.icon = icon; } public String getIcon() { return this.icon; } // name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } // address的setter和getter方法 public void setAddress(SocketAddress address) { this.address = address; } public SocketAddress getAddress() { return this.address; } // lost的setter和getter方法 public void setLost(int lost) { this.lost = lost; } public int getLost() { return this.lost; } // chatFrame的setter和getter方法 public void setChatFrame(ChatFrame chatFrame) { this.chatFrame = chatFrame; } public ChatFrame getChatFrame() { return this.chatFrame; } // 使用address作为该用户的标识,所以根据address作为 // 重写hashCode()和equals方法的标准 public int hashCode() { return address.hashCode(); } public boolean equals(Object obj) { if (obj != null && obj.getClass() == UserInfo.class) { UserInfo target = (UserInfo) obj; if (address != null) { return address.equals(target.getAddress()); } } return false; } } public interface YeekuProtocol { String PRESENCE = "⊿⊿"; String SPLITTER = "▓"; } // 让该类实现Runnable接口,该类的实例可作为线程的target public class MulticastSocketTest implements Runnable { // 使用常量作为本程序的多点广播IP地址 private static final String BROADCAST_IP = "230.0.0.1"; // 使用常量作为本程序的多点广播目的的端口 public static final int BROADCAST_PORT = 30000; // 定义每个数据报的最大大小为4K private static final int DATA_LEN = 4096; // 定义本程序的MulticastSocket实例 private MulticastSocket socket = null; private InetAddress broadcastAddress = null; private Scanner scan = null; // 定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; // 以指定字节数组创建准备接受数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); // 定义一个用于发送的DatagramPacket对象 private DatagramPacket outPacket = null; public void init() throws IOException { try ( // 创建键盘输入流 Scanner scan = new Scanner(System.in)) { // 创建用于发送、接收数据的MulticastSocket对象 // 由于该MulticastSocket对象需要接收数据,所以有指定端口 socket = new MulticastSocket(BROADCAST_PORT); broadcastAddress = InetAddress.getByName(BROADCAST_IP); // 将该socket加入指定的多点广播地址 socket.joinGroup(broadcastAddress); // 设置本MulticastSocket发送的数据报会被回送到自身 socket.setLoopbackMode(false); // 初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0], 0, broadcastAddress, BROADCAST_PORT); // 启动以本实例的run()方法作为线程体的线程 new Thread(this).start(); // 不断读取键盘输入 while (scan.hasNextLine()) { // 将键盘输入的一行字符串转换字节数组 byte[] buff = scan.nextLine().getBytes(); // 设置发送用的DatagramPacket里的字节数据 outPacket.setData(buff); // 发送数据报 socket.send(outPacket); } } finally { socket.close(); } } public void run() { try { while (true) { // 读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。 socket.receive(inPacket); // 打印输出从socket中读取的内容 System.out.println("聊天信息:" + new String(inBuff, 0, inPacket.getLength())); } } // 捕捉异常 catch (IOException ex) { ex.printStackTrace(); try { if (socket != null) { // 让该Socket离开该多点IP广播地址 socket.leaveGroup(broadcastAddress); // 关闭该Socket对象 socket.close(); } System.exit(1); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) throws IOException { new MulticastSocketTest().init(); } } public class UdpClient { // 定义发送数据报的目的地 public static final int DEST_PORT = 30000; public static final String DEST_IP = "127.0.0.1"; // 定义每个数据报的最大大小为4K private static final int DATA_LEN = 4096; // 定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; // 以指定字节数组创建准备接受数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); // 定义一个用于发送的DatagramPacket对象 private DatagramPacket outPacket = null; public void init() throws IOException { try ( // 创建一个客户端DatagramSocket,使用随机端口 DatagramSocket socket = new DatagramSocket()) { // 初始化发送用的DatagramSocket,它包含一个长度为0的字节数组 outPacket = new DatagramPacket(new byte[0], 0, InetAddress.getByName(DEST_IP), DEST_PORT); // 创建键盘输入流 Scanner scan = new Scanner(System.in); // 不断读取键盘输入 while (scan.hasNextLine()) { // 将键盘输入的一行字符串转换字节数组 byte[] buff = scan.nextLine().getBytes(); // 设置发送用的DatagramPacket里的字节数据 outPacket.setData(buff); // 发送数据报 socket.send(outPacket); // 读取Socket中的数据,读到的数据放在inPacket所封装的字节数组里。 socket.receive(inPacket); System.out.println(new String(inBuff, 0, inPacket.getLength())); } } } public static void main(String[] args) throws IOException { new UdpClient().init(); } } public class UdpServer { public static final int PORT = 30000; // 定义每个数据报的最大大小为4K private static final int DATA_LEN = 4096; // 定义接收网络数据的字节数组 byte[] inBuff = new byte[DATA_LEN]; // 以指定字节数组创建准备接受数据的DatagramPacket对象 private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length); // 定义一个用于发送的DatagramPacket对象 private DatagramPacket outPacket; // 定义一个字符串数组,服务器发送该数组的的元素 String[] books = new String[] { "疯狂Java讲义", "轻量级Java EE企业应用实战", "疯狂Android讲义", "疯狂Ajax讲义" }; public void init() throws IOException { try ( // 创建DatagramSocket对象 DatagramSocket socket = new DatagramSocket(PORT)) { // 采用循环接受数据 for (int i = 0; i < 1000; i++) { // 读取Socket中的数据,读到的数据放入inPacket封装的数组里。 socket.receive(inPacket); // 判断inPacket.getData()和inBuff是否是同一个数组 System.out.println(inBuff == inPacket.getData()); // 将接收到的内容转成字符串后输出 System.out.println(new String(inBuff, 0, inPacket.getLength())); // 从字符串数组中取出一个元素作为发送的数据 byte[] sendData = books[i % 4].getBytes(); // 以指定字节数组作为发送数据、以刚接受到的DatagramPacket的 // 源SocketAddress作为目标SocketAddress创建DatagramPacket。 outPacket = new DatagramPacket(sendData, sendData.length, inPacket.getSocketAddress()); // 发送数据 socket.send(outPacket); } } } public static void main(String[] args) throws IOException { new UdpServer().init(); } } public class DefaultProxySelectorTest { // 定义需要访问的网站地址 static String urlStr = "http://www.crazyit.org"; public static void main(String[] args) throws Exception { // 获取系统的默认属性 Properties props = System.getProperties(); // 通过系统属性设置HTTP访问所用的代理服务器的主机地址、端口 props.setProperty("http.proxyHost", "192.168.10.96"); props.setProperty("http.proxyPort", "8080"); // 通过系统属性设置HTTP访问无需使用代理服务器的主机 // 可以使用*通配符,多个地址用|分隔 props.setProperty("http.nonProxyHosts", "localhost|192.168.10.*"); // 通过系统属性设置HTTPS访问所用的代理服务器的主机地址、端口 props.setProperty("https.proxyHost", "192.168.10.96"); props.setProperty("https.proxyPort", "443"); /* * DefaultProxySelector不支持https.nonProxyHosts属性, * DefaultProxySelector直接按http.nonProxyHosts的设置规则处理 */ // 通过系统属性设置FTP访问所用的代理服务器的主机地址、端口 props.setProperty("ftp.proxyHost", "192.168.10.96"); props.setProperty("ftp.proxyPort", "2121"); // 通过系统属性设置FTP访问无需使用代理服务器的主机 props.setProperty("ftp.nonProxyHosts", "localhost|192.168.10.*"); // 通过系统属性设置设置SOCKS代理服务器的主机地址、端口 props.setProperty("socks.ProxyHost", "192.168.10.96"); props.setProperty("socks.ProxyPort", "1080"); // 获取系统默认的代理选择器 ProxySelector selector = ProxySelector.getDefault(); // ① System.out.println("系统默认的代理选择器:" + selector); // 根据URI动态决定所使用的代理服务器 System.out.println("系统为ftp://www.crazyit.org选择的代理服务器为:" + ProxySelector.getDefault().select( new URI("ftp://www.crazyit.org"))); // ② URL url = new URL(urlStr); // 直接打开连接,默认的代理选择器会使用http.proxyHost、 // http.proxyPort系统属性设置的代理服务器, // 如果无法连接代理服务器,默认的代理选择器会尝试直接连接 URLConnection conn = url.openConnection(); // ③ // 设置超时时长。 conn.setConnectTimeout(3000); try (Scanner scan = new Scanner(conn.getInputStream(), "utf-8")) { // 读取远程主机的内容 while (scan.hasNextLine()) { System.out.println(scan.nextLine()); } } } } public class ProxySelectorTest { // 下面是代理服务器的地址和端口, // 随便一个代理服务器的地址和端口 final String PROXY_ADDR = "139.82.12.188"; final int PROXY_PORT = 3124; // 定义需要访问的网站地址 String urlStr = "http://www.crazyit.org"; public void init() throws IOException, MalformedURLException { // 注册默认的代理选择器 ProxySelector.setDefault(new ProxySelector() { @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { System.out.println("无法连接到指定代理服务器!"); } // 根据"业务需要"返回特定的对应的代理服务器 @Override public List<Proxy> select(URI uri) { // 本程序总是返回某个固定的代理服务器。 List<Proxy> result = new ArrayList<>(); result.add(new Proxy(Proxy.Type.HTTP, new InetSocketAddress( PROXY_ADDR, PROXY_PORT))); return result; } }); URL url = new URL(urlStr); // 没有指定代理服务器、直接打开连接 URLConnection conn = url.openConnection(); // ① // 设置超时时长。 conn.setConnectTimeout(3000); try ( // 通过代理服务器读取数据的Scanner Scanner scan = new Scanner(conn.getInputStream()); PrintStream ps = new PrintStream("index.htm")) { while (scan.hasNextLine()) { String line = scan.nextLine(); // 在控制台输出网页资源内容 System.out.println(line); // 将网页资源内容输出到指定输出流 ps.println(line); } } } public static void main(String[] args) throws IOException, MalformedURLException { new ProxySelectorTest().init(); } } public class ProxyTest { // 下面是代理服务器的地址和端口, // 换成实际有效的代理服务器的地址和端口 final String PROXY_ADDR = "129.82.12.188"; final int PROXY_PORT = 3124; // 定义需要访问的网站地址 String urlStr = "http://www.crazyit.org"; public void init() throws IOException, MalformedURLException { URL url = new URL(urlStr); // 创建一个代理服务器对象 Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress( PROXY_ADDR, PROXY_PORT)); // 使用指定的代理服务器打开连接 URLConnection conn = url.openConnection(proxy); // 设置超时时长。 conn.setConnectTimeout(5000); try ( // 通过代理服务器读取数据的Scanner Scanner scan = new Scanner(conn.getInputStream(), "utf-8"); PrintStream ps = new PrintStream("index.htm")) { while (scan.hasNextLine()) { String line = scan.nextLine(); // 在控制台输出网页资源内容 System.out.println(line); // 将网页资源内容输出到指定输出流 ps.println(line); } } } public static void main(String[] args) throws IOException, MalformedURLException { new ProxyTest().init(); } }
|