套接字地址
在Java中可以用主机名或主机地址来标识一个网络地址,例如,www.baidu.com和
115.239.210.26,但主机名必须解析为数字型地址才能进行通信(DNS),而这一过程将会耗费一定的时间,所以一般在程序中使用数字型地址
在Java中,InetAddress代表了一个网络目标地址,包括主机名和数字型的地址信息,它有两个子类Inet4Address和Inet6Address,分别代表IPv4和IPv6地址,以下是一个简单的示例程序,打印一台主机上所有的网络接口和相应的地址信息,还打印根据主机名取得主机地址,你会发现如果试图用主机名来定位一台主机,可能会耗费相当长的时间
public class InetAddressExample {
public static void main(String[] args) {
try {
Enumeration<NetworkInterface> interfaceList = NetworkInterface
. getNetworkInterfaces();
if (interfaceList == null) {
System. out.println("No Interface Found" );
} else {
while (interfaceList.hasMoreElements()) {
NetworkInterface iface = interfaceList.nextElement();
System. out.println("Interface " + iface.getName());
Enumeration<InetAddress> addressList = iface
.getInetAddresses();
if(!addressList.hasMoreElements()){
System. out.println("No Address Found" );
}
while (addressList.hasMoreElements()) {
InetAddress address = addressList.nextElement();
System. out.println("address " +(address instanceof Inet4Address)+" ? v4" );
System. out.println("address " +(address instanceof Inet6Address)+" ? v6" );
System. out.println("host address "+address.getHostAddress());;
}
System. out.println();
System. out.println("==========================" );
System. out.println();
}
}
} catch (SocketException e) {
e.printStackTrace();
}
String hosts[] = new String[]{"www.baidu.com" ,"blab.bl$ald" };
for (String host : hosts) {
try {
InetAddress address[] = InetAddress.getAllByName(host);
for (InetAddress inetAddress : address) {
System. out.println("host name:"+inetAddress.getHostName()+" host address:" + inetAddress.getHostAddress());
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
}
打印信息
Interface lo
address true ? v4
address false ? v6
host address 127.0.0.1
address false ? v4
address true ? v6
host address 0:0:0:0:0:0:0:1
==========================
Interface net0
No Address Found
==========================
Interface net1
No Address Found
==========================
Interface net2
No Address Found
==========================
Interface ppp0
No Address Found
==========================
Interface eth0
No Address Found
==========================
Interface eth1
No Address Found
==========================
Interface eth2
No Address Found
==========================
Interface ppp1
No Address Found
==========================
Interface net3
No Address Found
==========================
Interface net4
No Address Found
==========================
Interface eth3
address false ? v4
address true ? v6
host address fe80:0:0:0:8d48:2afb:e255:3d33%12
==========================
Interface net5
No Address Found
==========================
Interface net6
No Address Found
==========================
Interface eth4
address false ? v4
address true ? v6
host address fe80:0:0:0:2116:398c:e5a0:1392%15
==========================
Interface net7
address true ? v4
address false ? v6
host address 192.168.12.104
address false ? v4
address true ? v6
host address fe80:0:0:0:5415:3c6f:45cb:8596%16
==========================
Interface net8
No Address Found
==========================
Interface net9
No Address Found
==========================
Interface net10
address false ? v4
address true ? v6
host address 2001:0:4137:9e76:14bb:17de:c266:1e69
address false ? v4
address true ? v6
host address fe80:0:0:0:14bb:17de:c266:1e69%19
==========================
Interface net11
No Address Found
==========================
Interface eth5
No Address Found
==========================
Interface eth6
address true ? v4
address false ? v6
host address 192.168.111.1
address false ? v4
address true ? v6
host address fe80:0:0:0:f9f0:272d:850c:9f63%22
==========================
Interface net12
address false ? v4
address true ? v6
host address fe80:0:0:0:0:5efe:c0a8:c68%23
==========================
Interface net13
address false ? v4
address true ? v6
host address fe80:0:0:0:0:5efe:c0a8:6f01%24
==========================
Interface net14
No Address Found
==========================
Interface net15
No Address Found
==========================
Interface eth7
No Address Found
==========================
Interface eth8
No Address Found
==========================
Interface net16
No Address Found
==========================
Interface net17
No Address Found
==========================
Interface eth9
No Address Found
==========================
Interface eth10
No Address Found
==========================
Interface eth11
No Address Found
==========================
Interface net18
No Address Found
==========================
Interface net19
No Address Found
==========================
Interface net20
No Address Found
==========================
host name:www.baidu.com host address:115.239.210.26
host name:www.baidu.com host address:115.239.210.27
java.net.UnknownHostException : blab.bl$ald
at java.net.Inet6AddressImpl.lookupAllHostAddr( Native Method)
at java.net.InetAddress$1.lookupAllHostAddr(Unknown Source)
at java.net.InetAddress.getAddressesFromNameService(Unknown Source)
at java.net.InetAddress.getAllByName0(Unknown Source)
at java.net.InetAddress.getAllByName(Unknown Source)
at java.net.InetAddress.getAllByName(Unknown Source)
at tu.bingbing.basesocket.InetAddressExample.main(InetAddressExample.java:50)
我们可以看到回环地址(127.0.0.1)和本地地址(
192.168.12.104
),最后花费了一定的时间来解析主机名,但并没有找到与之对应的主机,InetAddress类可以获取关于网络地址的信息和一些网络地址属性的判断,NetworkInterface类可以获取一台主机网络接口的一些信息,这对我们编写程序来说都是很有帮助的
TCP套接字
以下是一个测试主机回馈服务程序的例子,回馈服务程序只是简单的把发送给服务器的数据报文简单的回发给客户端发送程序,有些主机可能并没有回馈服务程序,可以输入命令:telnet "server.example.com 7"来检测
public class TCPEchoClient {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
String serverAddress = "127.0.0.1";
int serverPort = 7;
Socket socket = new Socket(serverAddress,serverPort);
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte datas[] = "Hello World" .getBytes();
out.write(datas);
int totalBytesRecvd = 0;
int bytesRecvd = 0;
while(totalBytesRecvd<datas.length ){
if((bytesRecvd = in.read(datas, totalBytesRecvd, datas.length-totalBytesRecvd))==-1){
throw new Exception("Server Closed Prematurelly");
}
totalBytesRecvd += bytesRecvd;
}
System. out.println("Received :" + new String(datas));
socket.close();
}
}
你可能会感觉到奇怪,在接收回馈服务程序返回的数据时为什么要这样写,直接in.read(datas,0,datas.length)不就行了吗,这是因为TCP协议并不能确定在read()和write()中所发送信息的界限,虽然我们只用了一个write()来发送回馈字符串。
回馈服务器也可能从多个块中接受改信息。即使回馈字符串在回馈服务器中只存在于一个块中,在返回的时候也有可能被分割成多个块。但你是否会有疑问向我们程序中那样做,如果回馈信息块发送顺序被打乱的怎么办,我们不是就接收不到正确的信息了吗,这个你不用担心了,别忘了TCP协议能够修复和检测IP层提供的主机到主机间信道中可能发生的报文丢失,重复,顺序打乱和一些其他的错误。
服务器端的工作主要是建立一个通信终端,被动的等待客户端的连接,在创建ServerSocket实例时需要指定要监听的端口号,但不需要去指定一个主机地址,因为一台主机可能有很多个网络接口地址,我们并不指定哪个接口地址可以和其它的主机进行通信连接,不去指定的话,将会去自动的选取一个'可用'的地址来进行通信
ServerSocket调用accept()方法时将会阻塞,直到有客户端来连接时会获取一个和客户端已经建立连接的Socket实例,这样就可以和客户端进行通信了。以下是一个TCP服务器端的例子
public class TCPEchoServerExample {
/**
* @param args
*/
public static void main(String[] args) throws Exception{
int BUFSIZE = 32;
int listenerPort = 9999;
ServerSocket serverSocket = new ServerSocket(listenerPort);
int receiveMsgSize = 0;
byte[] receiveBuf = new byte[BUFSIZE];
while(true ){
Socket socket = serverSocket.accept();
//获取客户端主机地址信息
SocketAddress cltAddress = socket.getRemoteSocketAddress();
System. out.println("client address:" +cltAddress);
InputStream cltIn = socket.getInputStream();
OutputStream cltOut = socket.getOutputStream();
while((receiveMsgSize = cltIn.read(receiveBuf)) != -1){
cltOut.write(receiveBuf, 0, receiveMsgSize);
}
socket.close();
}
}
}
如果TCP连接的一端一直在往输出流中写入数据,而连接的另一端相关联的输入流没有调用read()方法时,write()方法可能会被阻塞,如果不进行处理的话会产生一些不可预知的后果,后面将会学习到应对这种情况的对策
UDP套接字
UDP协议提供了一种与TCP协议不同的端到端的服务,UDP协议主要做两件事:在IP协议的基础上添加了另一层地址(端口);对数据传输中可能产生的错误进行了检测,并抛弃已经损坏的数据。UDP套接字在使用前不需要连接,UDP协议就像我们平时发送邮件一样,你只需要指定你'邮向的地址',每条发送的信息都包含了目的地址信息,并与其它信息独立开来。UDP套接字一旦创建出来就可以向任何地址发送信息,也可以从任何地址接受信息
UDP会保存边界信息,这使得应用程序在接收信息时,从某些方面来说比TCP更简单。UDP在数据报文的传输过程中有可能会出现数据丢失,数据顺序错乱等情况,所以我们在使用UDP协议时,这些情况都需要进行处理的。
简单来说,UDP发送 数据的时候是不管数据有没有真正达到目的地的,所以传输起来速度就比较快了。但是同时也容易造成数据丢失。而TCP我们知道有三次握手建立,四次握手释放,所以传输更准确,但是速度可能会相对慢一些。
Java中通过使用DatagramSocket和DatagramPacket来使用UDP套接字。与TCP套接字使用字节流来传输信息不同的是,UDP套接字使用一种称为数据报文的自包含信息,用DatagramPacket来表示,DatagramPacket包含了信息要送达的目的地址和端口号,相反的是也包含了源目的地址和端口号。使用DatagramSocket.send()方法来发送DatagramPacket报文,DatagramSocket.receive()来接收。
下面是一个简单的UDP客户端例子
public class UDPEchoClientTimeoutExample {
public static void main(String[] args) throws Exception{
InetAddress serverAddress = InetAddress.getByName("www.baidu.com");
System. out.println(serverAddress.getCanonicalHostName());
int serverPort = 80;
DatagramSocket dSocket = new DatagramSocket();
dSocket.setSoTimeout(3000);
byte[] sendMsgBuf = "Hello World" .getBytes();
DatagramPacket sendDp = new DatagramPacket(sendMsgBuf, sendMsgBuf.length, serverAddress, serverPort);
DatagramPacket receiveDp = new DatagramPacket(new byte[sendMsgBuf.length ], sendMsgBuf.length);
int tries = 0;
boolean receiveResp = false;
do {
dSocket.send(sendDp);
try{
dSocket.receive(receiveDp);
if(!receiveDp.getAddress().equals(serverAddress)){
throw new Exception("receive an unknow address");
}
receiveResp = true;
} catch(Exception e){
tries++;
}
} while ((!receiveResp)&&(tries<3));
if(receiveResp){
System. out.println("received data:" +new String(receiveDp.getData() ));
} else{
System. out.println("no data received" );
}
dSocket.close();
}
}
需要注意的是DatagramSocket的receive()方法有可能接收不到服务器返回的数据报文,如果不指定接收超时时间的话,将会一直阻塞,这个显然不是我们想要的,上面的示例程序展示了如果3s没有receive到的话将会连续的尝试3次
DatagramSocket还可以设定和指定的目的地址进行连接connect(InetAddress address, int port),当设定了和指定的目的地址连接时,DatagramSocket将不能发送和接收到指定的目的地址以外的其他目的地址的数据报文信息,但要注意的是UDP并不会建立端到端的连接,connectconnect(InetAddress address, int port)建立的只是本地操作,可以使用disconnect()来清除远程目的地址和端口号
UDP回馈服务器套接字也是被动的等待客户端UDP客户端套接字的连接,UDP是没有连接的,服务器端和客户端并不会建立连接,只是通过指定客户端数据报文目的地址和端口号来与服务器端进行通信,下面是一个简单的例子
public class UDPEchoServerExample {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
int serverPort = 9997;
int maxBufferSize = 255;
DatagramSocket serverDs = new DatagramSocket(serverPort);
DatagramPacket dp = new DatagramPacket( new byte[maxBufferSize], maxBufferSize);
while (true ){
serverDs.receive(dp);
System. out .println("client address : " + dp.getSocketAddress()+ " receive data : "+ new String(dp.getData()));
serverDs.send(dp);
dp.setLength(maxBufferSize); // reset the packet buffer size
}
}
}
每次receive到客户端的数据报文后都会重置服务器端数据报文的buffer size,字节数有可能会减少,下次再接收客户端的数据报文时,信息有可能会被截断,所以每次都需要重新设置下
dp.setLength(maxBufferSize)
我们还需要注意,当客户端UDP套接字要发送数据包设置了缓存区的内部偏移量和长度时
sendDp.setData(
sendMsgBuf
, 6, 2)
,如果服务器端UDP套接字用dp.getData()来获取的话,那是得不到想要的结果的,而应该根据客户端套接字设置的缓存区内部偏移量和长度来获取,因为服务器端套接字接收到的数据缓存区改变的只是客户端设置的内部偏移量和长度来确定区域,我们可以这样来获取
byte
[] destBuf = Arrays .copyOfRange (dp.getData(), dp.getOffset(), dp.getOffset()+dp.getLength())
这样就和客户端设置的对应上了
还有一点,看看我们的UDP服务器程序,构造DatagramPacket时指定了从客户端接收到的数据存放到一个缓存区内,但我们事先并不会知道这个缓存区到底要设置多大,如果设置小了的话,那数据会被截掉并且不会给我们任何的提示,UDP协议规定了每个数据包最大的字节数为65507,所以服务器端可以使用65600字节左右大小的缓存区来接收是比较好的