一.网络编程的基础知识
计算机网络:把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息,共享硬件,软件,数据信息等资源。
计算机网络是现代通信技术与计算机技术相结合的产物。
计算机网络分类
根据计算机网络规模大小和延伸范围分类:局域网(LAN)、城域网(MAN)、广域网(WAN)。
根据网络的拓扑结果分类:星型网络、总线网络、环线网络、树型网络、星型环线网络等。
根据网络的传输介质分类:双绞线网、同轴电缆网、光纤网、卫星网等。
局域网(LAN):指在一个较小地理范围内的各种计算机网络设备互连在一起的通信网络,可以包含一个或多个子网,通常局限在几千米的范围内。
城域网(MAN):主要是由城域范围内的局域网之间互连而构成的。
广域网(WAN):是由相距较远的局域网或城域网互连而成,通常是除了计算机设备之外,还要涉及一些电信通信方式。
通信协议
计算机网络中实现通信必须有一些约定,即通信协议。
计算机网络协议由三部分组成:一是语义部分,用于决定双方对话的类型;二是语法部分,用于决定双方对话的格式;三是变换规则,用于决定通信双方的应答关系。
开放系统互连参考模型:这是1978年国家标准化组织ISO提出的,该模型把计算机网络分为物理层、数据链路层、网络层、传输层、会话层、表示层、应用层等七层。
IP协议:互联网协议,是支持网间互联的数据报协议。它提供网间连接的完善功能,包括IP数据报规定互联网范围内的地址格式。
TCP协议:传输控制协议,它规定一种可靠的数据信息传递服务
虽然IP和TCP协议的功能不尽相同,也可以分开单独使用,但他们是在同一个时期作为一个协议来设计的,并且在功能上也是互补的。因此实际使用中常常把这两个协议统称为TCP/IP协议,TCP/IP协议是Internet中最常用的基础协议。
按TCP/IP协议模型,网络通常被分为四层,这四层和OSI七层模型有大致的对应关系。物理+数据链路(物理层、数据链路层)、网络层(网络层)、传输层(传输层)、应用层(会话层、表示层、应用层)。
IP地址和端口号
IP地址用于标志网络中的一个通信实体,这个通信实体可以是一台主机,也可以是一台打印机,或者路由器的某一个端口。
IP地址是数字型的,IP地址是一个32位整数,但通常为了方便记忆,通常把它分成4个8位的二进制数组成,每个8位之间用圆点隔开,每个8位整数可以转换成一个0到255的十进制整数。所以我经常看到的是:192.168.0.101等这种类型的。
NIC(Internet Network Information Center)统一负责全球Internet IP地址的规划、管理、而inter NIC、APNIC、RIPE三大网络信息中心具体负责美国及其他地区的IP地址分配。其中APNIC负责亚太地区的IP地址管理,我们国家申请IP地址也要通过APNIC,APNIC的总部设在日本东京大学。
IP地址分为A、B、C、D、E五类,每个类别的网络标识和主机标识各有规则:
A类:10.0.0.0~10.255.255.255
B类:172.16.0.0~172.31.255.255
C类:192.168.0.0~192.168.255.255
IP地址可以唯一地确定网络上的一个通信实体,但一个通信实体可以有多个通信程序同时提供网络服务,此时还需要使用端口。
端口是一个16位的整数,用于表示数据提交给那个通信程序处理。因此。端口就是应用程序与外界交流的出入口,它是一种抽象的软件结构,包括一些数据结构和I/O缓冲区。
不同的应用程序处理不同端口上的数据,同一台机器上不能有两个程序使用同一个端口,端口号可以从0~65535。
端口号通常分为如下三类:
公认端口(Well Known Ports):从0~1024,它们紧密绑定一些服务。
注册端口(Registered Ports):从1024~49151。它们松散的绑定一些服务。
动态/私有端口(Dynamic and/or Private Ports):从49152~65535,这些端口是应用程序使用的动态端口,应用程序一般不会主动使用这些端口。
为了更好的理解IP地址和端口,我们把IP地址理解为某个人所在地方的地址(包括街道和门牌号),但仅仅有地址还是找不到这个人,还需要知道他所在的房间号才可以找到这个人(房间号就类似端口)。因此如果我们认为应用程序是人,而计算机网络充当类似邮递员的角色,当一个程序需要发送数据时,需要指定目的地的IP地址和端口,如果指定了正确的IP地址和端口号,计算机网络就可以将数据送给该IP地址和端口所对应的应用程序。
二.Java的基本网络支持
Java为网络支持提供了java.net包,该包下的URL和URLConnection等类提供了以编程方式访问Web服务的功能,而URLDecoder和URLEncoder则提供普通字符串和application/x-www-form-urlencoded MIME字符串相互转换的静态方法。
InetAddress
InetAddress类代表IP地址,该类有两个子类:Inet4Address、Inet6Address,它们分别代表Internet Protocol version 4(IPv4)地址和Internet Protocol version 6(IPv6)地址。
创建InetAddress实例的方式:(InetAddress类没有提供构造函数)
(1)getByName(String host):根据主机获取对应的InetAddress对象。
(2)getByAddress(byte[] addr):根据原始IP地址来获取对应的InetAddress对象。
InetAddress提供如下三个方法获取InetAddress对应的IP地址和主机名:
(1)String getCannonicalHostName():获取此IP地址的全限定域名。
(2)String getHostAddress():返回该InetAddress实例对应的IP地址字符串。
(3)String getHostName():获取此IP地址的主机名。
经典实例
public class InetAddressTest {
public static void main(String[] args) throws Exception {
//根据主机名创建InetAddress实例
InetAddress ia = InetAddress.getByName("www.baidu.com");
System.out.println("百度是否可以达:" + ia.isReachable(3000));
System.out.println("InetAddress:" + ia.getHostAddress());
//根据原始IP地址创建InetAddress实例
InetAddress ia1 = InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 });
System.out.println("本机是否可达:" + ia1.isReachable(2000));
System.out.println(ia1.getCanonicalHostName());
}
}
URLDecoder和URLEncoder
URLDecoder和URLEncoder用于完成普通字符串和application/x-www-form-urlencoded MIME字符串之间的相互转换。
当URL地址里包含非西欧字符的字符串时,系统会将这些非西欧字符串转换成特殊的字符串。
URLDecoder类包含一个decode(String s ,String enc)静态方法,它可以将URL中的乱码(特殊字符串)转换成普通字符串。
URLEncoder类包含一个encode(String s,Setring enc)静态方法,它可以将普通字符串转换成application/x-www-form-urlencoded MIME字符串。
URL和URLConnection
URL(Uniform Resource Locator)对象代表统一资源定位器,它是指向互联网"资源"的指针。URL可以由协议名、主机、端口和资源组成。比如:protocol://host:port/resourceName。
注意:JDK中还提供了一个URI(Uniform Resource Identifiers)类,其实例代表一个统一资源标识符,Java的URI不能用于定位任何资源,它的唯一作用就是解析,与此对应的是,URL则包含一个可打开到达该资源的输入流,因此我们可以将URL理解成URI的一个特例。
URL提供如下方法访问对应的资源:
(1)String getFile():获取此URL的资源名。
(2)String getHost():获取此URL的主机名。
(3)String getPath():获取此URL的路径部分。
(4)int getPort():获取此URL的端口号。
(5)String getProtocol():获取此URL的协议名称。
(6)String getQuery():获取此URL的查询字符串部分。
(7)URLConnection openConnection():返回一个URLConnection对象,它表示到URL所引用的远程对象的连接。
(8)InputStream openStream():打开与此URL的连接,并返回一个用于读取该URL资源的InputStream。
URLConnection连接对象,该对象表示应用程序和URL之间的通信连接,程序可以通过URLConnection实例向该URL发送请求,读取URL引用的资源。
发送请求,读取此URL引用的资源步骤:
(1)通过调用URL对象openConnection方法来创建URLConnection对象。
(2)设置URLConnection的参数和普通请求属性。
(3)如果只是发送GET方式请求,使用connect方法建立和远程资源之间的实际连接即可;如果需要发送POST方式的请求,需要获取URLConnection实例对应的输出流来发送请求参数。
(4)远程资源变为可用,程序可以访问远程资源的头字段或通过输入流读取远程资源的数据。在建立和远程资源的实际连接之前,程序可以通过如下方法来设置请求字段:
1)setAllowUserInteraction:设置该URLConnection的allowUserInteraction请求头字段的值。
2)setDoInput:设置该URLConnection的doInput请求头字段的值。(POST请求必须设置该属性的值)
3)setDoOutput:设置该URLConnection的doOutput请求头字段的值。(POST请求必须设置该属性的值)
4)setIfModifiedSince:设置该URLConnection的ifModifiedSince请求头字段的值。
5)setUseCaches:设置该URLConnection的useCaches请求头字段的值。
6)setRequestProperty(String key,String value):设置该URLConnection的key请求头字段的值为value。比如:conn.setRequestProperty("accept","*/*");
7)addRequestProperty(String key,String value):为该URLConnection的可以请求字段的增加value值,该方法并不会覆盖原请求头字段的值,而是将新值追加到原请求头字段中。
8)Object getContent():获取该URLConnection的内容。
9)String getHeaderField(String name):获取指定响应头字段的值。
10)getInputStream():返回该URLConnection对应的输入流,用于获取URLConnection相应的内容。
11)getOutputStream():返回该URLConnection对应的输出流,用于向URLConnection发送请求参数。
12)getHeaderField:方法用于根响应头字段来返回对应的值。
13)getContentEncoding:获取content-encoding响应头字段的值。
14)getContentLength:获取content-length响应头字段的值。
15)getContentType:获取content-type响应头字段的值。
16)getDate():获取date响应头字段的值。
17)getExpiration():获取expires响应头字段的值。
18)getLastModified():获取last-modified响应头字段的值。
基于TCP协议的网络编程
ICP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路。一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信。Java对基于TCP协议的网络通信提供了良好的封装,Java使用Socket对象来代表两端的通信端口,并通过Socket参数IO流来进行网络通信。
TCP协议被称作一种端对端的协议。当一台计算机需要与另一台远程计算机连接时,TCP协议会让它们之间建立一个连接,用于发送和接受数据的虚拟链路。TCP协议负责收集这些信息包,并将其按适当的次序放好传送,在接收端收到后再将其正确地还原。TCP协议包装了数据包在传送过程中准确无误。TCP协议使用重发机制:当一个通信实体发送一个消息给另一个通信实体后,需要收到另一个通信实体确认信息,如果没有收到另一个通信实体的确认信息,则会再次重发刚才发送的信息。
ServerSocket创建TCP服务器端
在两个通信实体没有建立虚拟链路之前,必须有一个通信实体先做出"主动姿态",主动接收来自其他通信实体的连接请求,被称为服务器端。Java中能接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客服端的Socket连接,如果没有连接,它将处于等待状态。ServerSocket使用accept()方法来接受客户端的Socket请求。如果没有接受到客户端发送的Socket,该方法将一直处于等待状态,线程也被阻塞。
创建ServerSocket的几个构造函数
(1)ServerSocket(int port):用指定的端口port来创建一个ServerSocket。
(2)ServerSocket(int port,int backlog):增加一个用来改变连接队列长度的参数backlog。
(3)ServerSocket(int port,int backlog,InetAddress localAddr):在机器存在多个IP地址的情况下,允许通过localAddr这个参数来指定将ServerSocket绑定到指定的IP地址。
当ServerSocket使用完毕,应该使用close方法来关闭该ServerSocket。
Socket进行通信
创建Socket实例使用如下的构造函数:
(1)Socket(InetAddress/String remoteAddress,int port):创建连接到指定远程主机、远程端口的Socket。
(2)Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort):创建连接到指定远程主机、远程端口的Socket,并指定本地IP地址和本地端口号,适用于本地主机有多个IP地址的情况。
Socket提供如下两个方法来获取输入流和输出流:
(1)InputStream getInputStream():返回该Socket对象对应的输入流,让程序通过该输入流从Socket中取出数据。
(2)OutputStream getOutputStream():返回该Socket对象对应的输出流,让程序通过输出流向Socket中输出数据。
经典实例:只是为了测试功能,并未对程序做任何异常处理,也没有使用finally语句块来关闭资源。
public class ServerSocketTest {
public static void main(String[] args) throws Exception {
// 创建一个ServerSocket对象。
ServerSocket ss = new ServerSocket(3000);
while (true) {
// 每当接受到客户端Socket的请求,服务器断也对应产生一个Socket
Socket s = ss.accept();
// 将Socket对应的输出流包装成PrintStream
PrintStream ps = new PrintStream(s.getOutputStream());
ps.println("你好,我是ServerSocket!");
ps.close();
s.close();
}
}
}
public class SocketTest {
public static void main(String[] args) throws Exception {
Socket s = new Socket("127.0.0.1", 3000);
BufferedReader br = new BufferedReader(new InputStreamReader(s
.getInputStream()));
String content = br.readLine();
System.out.println("信息来自服务器:" + content);
br.close();
s.close();
}
}
将在客户端打印出:信息来自服务器:你好,我是ServerSocket!
Socket提供了如下两个半关闭的方法:
(1)shutdownInput():关闭该Socket的输入流,程序还可以通过该Socket的输出流输出数据。
(2)shutdownOutput():关闭该Socket的输出流,程序还可以通过该Socket的输出流读取数据。
当调用shutdownInput或shutdownOutput方法关闭Socket的输入流或输出流之后,该Socket处于“半关闭”状态。
注意:当调用Socket的shutdownInput或shutdownOutput方法关闭Socket的输入流或输出流之后,该Socket无法再次打开输入流或输出流。因此这种做法通常不适合保持持久通信状态的交互式应用,只适用与一站式的通信协议。例如HTTP协议,客户端连接到服务器后,开始发送请求数据,发送完成后无须再次发送数据,只需要读取服务器响应数据即可。当读取响应完成后,该Socket连接也被关闭。
基于UDP协议的网络编程
UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送,接受数据报的对象,Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据报。
UDP协议(User Datagram Protocol),即用户数据报协议,主要用来支持那些需要在计算机之间传输数据的网络连接。UDP协议是一种面向非连接的协议,就是说在正式通信前不必与对方先建立连接,不管对方状态就直接发送。至于对方是否可以接收到这些数据内容,UDP协议无法控制,所以说UDP协议是不可靠协议。
UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。通信效率高。UDP协议主要作用是完成网络数据流量和数据报之前的转换:在信息的发送端,UDP协议将网络数据流量封装数据报,然后将数据报发送出去;在信息接收端,UDP协议将数据报转换成实际数据内容。
为了更好的理解UDP,我们可以任务基于UDP协议的Socket类似于一个码头,该码头的作用就是负责发送、接收集装箱,一个数据报类似于一个集装箱。因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务端的概念,Java中的DatagramSocket的作用类似于码头,而DatagramPacket的作用类似与集装箱。
UDP和TCP协议简单对比:
TCP协议:可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大。
UDP协议:不可靠,差错控制开销小,传输大小限制在64K以下,不需要建立连接。
DatagramSocket发送、接受数据
DatagramSocket本事类似于码头,不维护状态,不能产生IO流,它的唯一作用就是接受和发送数据报。Java使用DatagramPacket来代表数据报,DatagramSocket接受和发送的数据都是通过DatagramPacket对象来完成。
DatagramSocket的构造器:
(1)DatagramSocket():创建一个DatagramSocket实例,并将该对象绑定到本地默认的IP地址,本机所有可用的端口中随机选择某一个端口。
(2)DatagramSocket(int port):创建一个DatagramSocket实例,并将该对象绑定到本地默认的IP地址,指定端口。
(3)DatagramSocket(int port ,InetAddress laddr):创建一个DatagramSocket实例,并将该对象绑定到指定的IP地址,指定端口。
DatagramSocket发送、接收数据:
(1)receive(DatagramPacket p):从该DatagramSocket中接收数据报。
(2)send(DatagramPacket p):从该DatagramSocket对象向外发送数据报。
使用DatagramSocket发送数据报时,DatagramSocket并不知道该数据报发送到那里,而是有DatagramPacket自身决定数据报的目的。就像码头并不知道每个集装箱的目的地,码头只是将这些集装箱发送出去,而集装箱本事包含了该集装箱的目的地。
DatagramPacket的构造器:
(1)DatagramPacket(byte buf[],int length):以一个空数组来创建DatagramPacket对象,该对象的作用是接收DatagramSocket中的数据。
(2)DatagramPacket(byte buf[],int length,InetAddress addr,int port):以一个包含数据的数组来创建DatagramPacket对象,创建该DatagramPacket时还指定了IP地址和端口(这就决定了该数据报的目的)。
(3)DatagramPacket(byte[] buf,int offset,int lenght):以一个空数组来创建DatagramPacket对象,并指定接收到的数据放入buf数组中时从offset开始,最多存放length个字节。
(4)DatagramPacket(byte[] buf,int offset,int lenght,InetAddress address,int port):创建一个用于发送的DatagramPacket对象,也多指定了一个offset参数。
在接收数据前,应该采用上面的第一个或迪桑构造器生成一个DatagramPacket对象,给出接收数据的自己数组极其长度。然后调用DatagramSocket的方法receive()等待数据报的到来,receive()将一直等待(阻塞调用该方法的线程),直到收到一个数据报为止。
在发送数据前,调用第二个或第四个构造函数创建DatagramPacket对象,此时的自己数组里存放了想发送的数据,除此之外,还要给出完整的目的地址,包括IP地址和端口号。发送数据是通过DatagramSocket的send()方法实现的,send方法更加数据报的目的地址来寻找以传递数据报。
DatagramPacket提供如下三个方法获取发送者的IP和端口:
(1)InetAddress getAddress():返回某台机器的IP地址,当程序准备发送此数据报时,该方法返回此数据报的目标机器的IP地址;当程序刚刚接收到一个数据报时,该方法返回该数据报的发送主机的IP地址。
(2)int getPort():返回某台机器的端口,当程序准备发送此数据报时,该方法返回此数据报的目标机器的端口;当程序刚刚接收到一个数据报时,该方法返回该数据报的发送主机的端口。
(3)SocketAddress getSocketAddress():返回完整SocketAddress,通常由IP地址和端口组成。当程序准备发送此数据报时,该方法返回此数据报的目标SocketAddress;当程序刚刚接收到一个数据报时,该方法返回该数据报是源SocketAddress。
经典实例
public class UdpServer {
public static final int PORT = 30003;
// 定义每个数据报的最大大小为4K
private static final int DATA_LEN = 4096;
// 定义该服务器使用的DatagramSocket
private DatagramSocket socket = null;
// 定义接收网络数据的字节数组。
byte[] inBuff = new byte[DATA_LEN];
// 以指定自己数组创建准备接收数据的DatagramPacket对象
private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);
private DatagramPacket outPacket;
// 定义一个字符串数组,服务器发送该数组的元素。
String[] strs = new String[] { "smarhit", "java", "android" };
public void init() {
try {
// 创建DatagramSocket对象
socket = new DatagramSocket(PORT);
// 循环接收数据
for (int i = 0; i < 1000; i++) {
// 读取Socket中的数据,读到的数据放在inPacket所封装的字节数组中。
socket.receive(inPacket);
System.out.println(inBuff == inPacket.getData());
System.out.println(new String(inBuff, 0, inPacket.getLength()));
// 从字符串数组中取出一个元素作为发送的数据
byte[] sendData = strs[i % 4].getBytes();
// 以指定字节数组作为发送数据,以刚刚接收的DatagramPacket的源SocketAddress作为
// 目标SocketAddress创建DatagramPacket对象。
outPacket = new DatagramPacket(sendData, sendData.length,
inPacket.getSocketAddress());
// 发送数据
socket.send(outPacket);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} finally {
if (socket != null) {
socket.close();
}
}
}
public static void main(String[] args) {
new UdpServer().init();
}
}
public class UdpClient {
//定义发送数据报的目的地
public static final int DEST_PORT = 30003;
public static final String DEST_IP = "127.0.0.1";
private static final int DATA_LEN = 4096;
//定义客户端使用的DatagramSocket
private DatagramSocket socket = null;
byte[] inBuff = new byte[DATA_LEN];
//创建一个接受数据的DatagramPacket对象
private DatagramPacket inPacket = new DatagramPacket(inBuff, inBuff.length);
//定义一个用于发送的DatagramPacket
private DatagramPacket outPacket = null;
public void init() {
try {
//创建一个客户端DatagramSocket对象
socket = new DatagramSocket();
//初始化发送用的DatagramSocket,它包含一个长度为的字节数组。
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.receive(inPacket);
System.out.println(new String(inBuff, 0, inPacket.getLength()));
}
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
} finally {
if (socket != null) {
socket.close();
}
}
}
public static void main(String[] args) {
new UdpClient().init();
}
}
MulticastSocket实现多点广播
DatagramSocket只允许数据报发送给指定的目标地址,而MulticastSocket可以将数据报以广播方式发送到数量不等的多个客户端。
若要使用多点广播时,则需要让一个数据报标有一组目标主机地址,当数据报发出后,整个组的所有主机都能收到该数据报。IP多点广播(或多点发送)实现了将单一信息发送到多个接收者的广播,其思想是设置一组特殊网络地址作为多点广播地址,每一个多点广播地址都被看做一组,当客户端需要发送、接收广播信息时,加入到该组即可。
MulticastSocket和DatagramSocket有点类似,MulticastSocket是DatagramSocket的一个子类,若要发送一个数据报时,可以使用随机端口创建MulticastSocket,也可以在指定端口来创建MulticastSocket。
MulticastSocket提供了如下三个构造函数:
(1)MulticastSocket():使用本地默认地址、随机端口来创建一个MulticastSocket对象。
(2)MulticastSocket(int portNumber):使用本机默认地址,指定端口来创建一个MulticastSocket对象。
(3)MulticastSocket(SocketAddress bindaddr):使用本机指定IP地址,指定端口来创建一个MulticastSocket对象。
MulticastSocket使用joinGroup方法来加入指定组,使用leaveGroup方法脱离一个组。
(1)joinGroup(InetAddress multicastAddr):将该MulticastSocket加入指定的多点广播地址。
(2)leaveGroup(InetAddress multicastAddr):将该MulticastSocket离开指定的多点广播地址。
MulticastSocket有一个setTimeToLive(int ttl)方法,该ttl参数设置数据报最多可以跨过多少个网络,当ttl为0时,指定数据报应停留在本地主机;当ttl的值为1时,指定数据报发送到本地局域网;当ttl的值为32时,只能发送到本站点的网络上;当ttl为64时,数据报应保留在本地区;当ttl的值为128时,数据报应该保留在本大州;当ttl为255时,数据报可以发送到所有地方;默认情况下ttl的值为1。
经典实例
public class MulticastSocketTest implements Runnable {
private static final String BROADCAST_IP = "230.0.0.1";
public static final int BROADCAST_PORT = 3000;
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);
private DatagramPacket outPacket = null;
public void init() {
try {
// 创建用于发送或接收的MulticastSocket对象
socket = new MulticastSocket(BROADCAST_PORT);
broadcastAddress = InetAddress.getByName(BROADCAST_IP);
// 将该socket加入到指定的多点广播地址
socket.joinGroup(broadcastAddress);
// 设置本MulticastSocket发送的数据报被会送到自身
socket.setLoopbackMode(false);
// 创建发送的DatagramPacket对象
outPacket = new DatagramPacket(new byte[0], 0, broadcastAddress,
BROADCAST_PORT);
// 启动线程
new Thread(this).start();
scan = new Scanner(System.in);
while (scan.hasNextLine()) {
byte[] buff = scan.nextLine().getBytes();
// 设置发送用的DatagramPacket里字节数据
outPacket.setData(buff);
// 发送数据报
socket.send(outPacket);
}
} catch (IOException e) {
// TODO: handle exception
e.printStackTrace();
} finally {
socket.close();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
while (true) {
// 获取socket中的数据,读到的数据放在inPacket所封装的字节数组里
socket.receive(inPacket);
System.out.println(new String(inBuff, 0, inPacket.getLength()));
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
try {
if (socket != null) {
socket.leaveGroup(broadcastAddress);
socket.close();
}
System.exit(1);
} catch (Exception e2) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new MulticastSocketTest().init();
}
}
代理服务器
从JDK1.5开始,Java在java.net包下提供了Proxy和ProxySelector两个类,其中Proxy代表一个代理服务器,可以在打开URLConnection连接时指定所用的Proxy实例,也可以在创建Socket连接时指定Proxy实例。而ProxySelector代表一个代理选择器,它提供了对代理服务器更加灵活的控制,它可以对HTTP,HTTPS,FTP,SOCKS等分别设置,而且还可以设置不需要通过代理服务器的主机和地址。通过使用ProxySelector可以达到像在Internet Explorer、FireFox等软件中设置代理服务器类似的效果。
Pxoxy有一个构造器:Proxy(Proxy.Type type,SocketAddress sa):创建表示嗲了服务器的Proxy对象。type是该代理服务器的类型。
代理服务有以下三种类型:
(1)Proxy.Type.DIRECT:表示直接连接或代理不存在。
(2)Proxy.Type.HTTP:表示高级协议的代理,如果HTTP或FTP。
(3)Proxy.Type.SOCKS:表示SOCKS(V4或V5)代理。
创建Proxy对象后,程序可以使用URLConnection打开连接时,或创建Socket连接时闯入Proxy对象,作为本次连接所使用的代理服务器。
其中URL包含了一个URLConnection openConnection(Proxy proxy)方法,该方法使用指定的代理服务器来打开连接;而Socket则提供了一个Socket(Proxy proxy)构造函数,该构造函数使用指定的代理服务器创建一个没有连接的Socket对象。
经典实例
public class ProxyTest {
Proxy proxy;
URL url;
URLConnection conn;
Scanner scan;
PrintStream ps;
String proxyAddress = "202.128.23.32";
int proxyPort;
String urlStr = "http://www.smarhit.com";
public void init() {
try {
url = new URL(urlStr);
//创建代理服务器对象
proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(
proxyAddress, proxyPort));
//使用指定的代理服务器打开连接
conn = url.openConnection(proxy);
//设置超长时间
conn.setConnectTimeout(5000);
scan = new Scanner(conn.getInputStream());
//初始化输出流
ps = new PrintStream("index.htm");
while (scan.hasNextLine()) {
String line = scan.nextLine();
System.out.println(line);
ps.println(line);
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} finally {
if (ps != null) {
ps.close();
}
}
}
public static void main(String[] args) {
new ProxyTest().init();
}
}