2529 2018-04-14 2020-06-25
前言:Java是一门活跃在服务后端的语言,而Java的网络编程提供客户端/服务端连接通信的底层实现。
一、IP地址
1、InetAddress
在互联网上通信,IP地址是必不可少的。Java提供一个与IP地址相关的类,如下
public class InetAddress implements Serializable {
static final int IPv4 = 1;
static final int IPv6 = 2;
// 默认构造方法,即只能在本包内被访问到
InetAddress() {
holder = new InetAddressHolder();
}
// 值得注意的是里面一些静态方法
public static InetAddress getByName(String host)
throws UnknownHostException {
return InetAddress.getAllByName(host)[0];
}
public static InetAddress[] getAllByName(String host)
throws UnknownHostException {
return getAllByName(host, null);
}
private static InetAddress[] getAllByName(String host, InetAddress reqAddr)
throws UnknownHostException {
if (host == null || host.length() == 0) {
InetAddress[] ret = new InetAddress[1];
ret[0] = impl.loopbackAddress();
return ret;
}
boolean ipv6Expected = false;
if (host.charAt(0) == '[') {
// This is supposed to be an IPv6 literal
if (host.length() > 2 && host.charAt(host.length()-1) == ']') {
host = host.substring(1, host.length() -1);
ipv6Expected = true;
} else {
// This was supposed to be a IPv6 address, but it's not!
throw new UnknownHostException(host + ": invalid IPv6 address");
}
}
// if host is an IP address, we won't do further lookup
if (Character.digit(host.charAt(0), 16) != -1
|| (host.charAt(0) == ':')) {
byte[] addr = null;
int numericZone = -1;
String ifname = null;
// see if it is IPv4 address
addr = IPAddressUtil.textToNumericFormatV4(host);
if (addr == null) {
// This is supposed to be an IPv6 literal
// Check if a numeric or string zone id is present
int pos;
if ((pos=host.indexOf ("%")) != -1) {
numericZone = checkNumericZone (host);
if (numericZone == -1) { /* remainder of string must be an ifname */
ifname = host.substring (pos+1);
}
}
if ((addr = IPAddressUtil.textToNumericFormatV6(host)) == null && host.contains(":")) {
throw new UnknownHostException(host + ": invalid IPv6 address");
}
} else if (ipv6Expected) {
// Means an IPv4 litteral between brackets!
throw new UnknownHostException("["+host+"]");
}
InetAddress[] ret = new InetAddress[1];
if(addr != null) {
if (addr.length == Inet4Address.INADDRSZ) {
ret[0] = new Inet4Address(null, addr);
} else {
if (ifname != null) {
ret[0] = new Inet6Address(null, addr, ifname);
} else {
ret[0] = new Inet6Address(null, addr, numericZone);
}
}
return ret;
}
} else if (ipv6Expected) {
// We were expecting an IPv6 Litteral, but got something else
throw new UnknownHostException("["+host+"]");
}
return getAllByName0(host, reqAddr, true);
}
public static InetAddress getLocalHost() throws UnknownHostException {
SecurityManager security = System.getSecurityManager();
try {
String local = impl.getLocalHostName();
if (security != null) {
security.checkConnect(local, -1);
}
if (local.equals("localhost")) {
return impl.loopbackAddress();
}
InetAddress ret = null;
synchronized (cacheLock) {
long now = System.currentTimeMillis();
if (cachedLocalHost != null) {
if ((now - cacheTime) < maxCacheTime) // Less than 5s old?
ret = cachedLocalHost;
else
cachedLocalHost = null;
}
// we are calling getAddressesFromNameService directly
// to avoid getting localHost from cache
if (ret == null) {
InetAddress[] localAddrs;
try {
localAddrs =
InetAddress.getAddressesFromNameService(local, null);
} catch (UnknownHostException uhe) {
// Rethrow with a more informative error message.
UnknownHostException uhe2 =
new UnknownHostException(local + ": " +
uhe.getMessage());
uhe2.initCause(uhe);
throw uhe2;
}
cachedLocalHost = localAddrs[0];
cacheTime = now;
ret = localAddrs[0];
}
}
return ret;
} catch (java.lang.SecurityException e) {
return impl.loopbackAddress();
}
}
}
2、InetAddress的使用
我们来看下InetAddress的一些常用方法。
InetAddress inetAddress = InetAddress.getLocalHost();
System.out.println(inetAddress.getHostAddress());
System.out.println(inetAddress.getHostName());
InetAddress inetAddress1 = InetAddress.getByName("www.baidu.com");
System.out.println(inetAddress1.getHostAddress());
System.out.println(inetAddress1.getHostName());
InetAddress[] inetAddress2 = InetAddress.getAllByName("www.baidu.com");
for (InetAddress temp : inetAddress2) {
System.out.println(temp.getHostAddress());
System.out.println(temp.getHostName());
}
// 输出结果如下
192.168.187.1
HK
183.232.231.172
www.baidu.com
183.232.231.172
www.baidu.com
183.232.231.173
www.baidu.com
二、TCP通信
TCP协议是面向连接的通信协议,在两台计算机之间提供可靠无差错的数据传输。每次连接的创建都需要经历“三次握手”。
Java提供了两个类用于实现TCP通信,一个是用于服务器端的ServerSocket类,另一个是用于客户端的Socket类。
1、ServerSocket
public class ServerSocket implements java.io.Closeable {
/**
* Various states of this socket.
*/
private boolean created = false;
private boolean bound = false;
private boolean closed = false;
private Object closeLock = new Object();
/**
* The implementation of this Socket.
*/
private SocketImpl impl;
/**
* Are we using an older SocketImpl?
*/
private boolean oldImpl = false;
// 包内访问
ServerSocket(SocketImpl impl) {
this.impl = impl;
impl.setServerSocket(this);
}
public ServerSocket() throws IOException {
setImpl();
}
// 常用
public ServerSocket(int port) throws IOException {
this(port, 50, null);
}
public ServerSocket(int port, int backlog) throws IOException {
this(port, backlog, null);
}
public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException {
setImpl();
if (port < 0 || port > 0xFFFF)
throw new IllegalArgumentException(
"Port value out of range: " + port);
if (backlog < 1)
backlog = 50;
try {
bind(new InetSocketAddress(bindAddr, port), backlog);
} catch(SecurityException e) {
close();
throw e;
} catch(IOException e) {
close();
throw e;
}
}
private void setImpl() {
if (factory != null) {
impl = factory.createSocketImpl();
checkOldImpl();
} else {
// No need to do a checkOldImpl() here, we know it's an up to date
// SocketImpl!
impl = new SocksSocketImpl();
}
if (impl != null)
impl.setServerSocket(this);
}
public InetAddress getInetAddress() {
if (!isBound())
return null;
try {
InetAddress in = getImpl().getInetAddress();
SecurityManager sm = System.getSecurityManager();
if (sm != null)
sm.checkConnect(in.getHostAddress(), -1);
return in;
} catch (SecurityException e) {
return InetAddress.getLoopbackAddress();
} catch (SocketException e) {
// nothing
// If we're bound, the impl has been created
// so we shouldn't get here
}
return null;
}
public int getLocalPort() {
if (!isBound())
return -1;
try {
return getImpl().getLocalPort();
} catch (SocketException e) {
// nothing
// If we're bound, the impl has been created
// so we shouldn't get here
}
return -1;
}
public Socket accept() throws IOException {
if (isClosed())
throw new SocketException("Socket is closed");
if (!isBound())
throw new SocketException("Socket is not bound yet");
Socket s = new Socket((SocketImpl) null);
implAccept(s);
return s;
}
public void close() throws IOException {
synchronized(closeLock) {
if (isClosed())
return;
if (created)
impl.close();
closed = true;
}
}
}
Socket源代码形式大致同ServerSocket,读者可自行查看。
2、模拟TCP对话
代码如下
public class Server {
public static void main(String[] args) throws UnknownHostException, IOException {
ServerSocket serverSocket = new ServerSocket(2222);
serverSocket.getInetAddress();
System.out.println("服务端等待连接....");
Socket client = serverSocket.accept();
System.out.println("与客户端 " + client.getInetAddress().toString() + " 建立连接");
BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter pw = new PrintWriter(client.getOutputStream());
pw.println("欢迎连接到服务器^_^");
pw.flush();
Scanner scanner;
String resp;
String clientMessage = br.readLine();
while (!clientMessage.equals("bye")) {
System.out.println("客户端说:" + clientMessage);
scanner = new Scanner(System.in);
System.out.print("服务端说:");
resp = scanner.nextLine();
pw.println(resp);
pw.flush();
if (resp.equals("bye")) {
break;
}
clientMessage = br.readLine();
}
br.close();
pw.close();
System.out.println("服务端:连接结束");
}
}
public class Client {
public static void main(String[] args) throws UnknownHostException, IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 2222);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream());
Scanner scanner;
String req;
String serverMessage = br.readLine();
while (!serverMessage.equals("bye")) {
System.out.println("服务端说:" + serverMessage);
scanner = new Scanner(System.in);
System.out.print("客户端说:");
req = scanner.nextLine();
pw.println(req);
pw.flush();
if (req.equals("bye")) {
break;
}
serverMessage = br.readLine();
}
br.close();
pw.close();
System.out.println("客户端:连接结束");
}
}
// 服务端输入输出如下
服务端等待连接....
与客户端 /192.168.187.1 建立连接
客户端说:hello
服务端说:hello too
客户端说:我叫一般般帅气
服务端说:我叫一般般可爱
客户端说:奥
服务端说:哦
服务端:连接结束
// 客户端输入输出如下
服务端说:欢迎连接到服务器^_^
客户端说:hello
服务端说:hello too
客户端说:我叫一般般帅气
服务端说:我叫一般般可爱
客户端说:奥
服务端说:哦
客户端说:bye
客户端:连接结束
三、UDP连接
UDP是一种面向无连接的协议(面向报文的),与TCP不同,它在通信时客户端和服务器端不用建立连接,因此不能保证通信的安全性。Java提供了两个类用于支持UDP,其中DatagramPacket用于封装要发送的数据包,而DatagramSocket类用于发送或接受数据包(DatagramPacket)的套接字。
1、DatagramPacket
在发送或接受数据时,必须先创建DatagramPacket对象用于封装数据,然后通过相应的方法可以得到发送或接受到的数据包的信息。在发送数据包中需绑定接收端的IP地址和端口号,系统自动添加发送端的IP地址和端口号。下面是常用的一些方法
方法声明 | 功能描述 |
---|---|
DatagramPacket(byte[] buf, int length) | 构造DatagramPacket,用来接收长度为length的数据包buf |
DatagramPacket(byte[] buf, int length, InetAddress address, int port) | 构造数据包,用来将长度为length的数据包发送到指定主机上的指定端口 |
DatagramPacket(byte[] buf, int offset, int length) | 构造DatagramPacket,用来接收长度为length的数据包,在缓存区指定了偏移量 |
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port) | 构造数据包,用来将长度为length、偏移量为offset的数据包发送到指定主机上的指定端口 |
InetAddress getAddress() | 返回某台机器的IP地址(他方IP地址),此数据包要发往该机器或者从该机器接受到的 |
int getPort() | 返回某台主机的端口号 |
void setData(byte[] buf, int offset, int length) | 为此包设置数据缓冲区。此方法设置数据包的数据、长度和偏移量 |
byte[] getData() | 返回数据缓冲区 |
int getLength() | 返回将要发送或接受到的数据长度 |
2、DatagramSocket
DatagramSocket类表示用来发送和接受数据包的套接字,创建DatagramSocket类对象,就可以发送或接受DatagramPacket数据包。
数据包套接字是包投递的发送和接受点,每个在数据包套接字上发送或接受的数据包都是单独编址和路由的。从一台机器发送到另一台机器的多个数据包可能选择不同的路由,也可能按不同的顺序到达。
由于发送和接受的方式不同,所以在创建发送端和接收端DatagramSocket对象时,使用的构造方法也有所不同。如下
方法声明 | 功能描述 |
---|---|
DatagramSocket() | 构造数据包套接字并将其绑定到本地主机上任何可用的端口 |
DatagramSocket(int port) | 创建数据包套接字并将其绑定到本地主机上指定端口 |
DatagramSocket(int port, InetAddress address) | 创建数据包套接字,将其绑定到指定的本地地址。本方法一般用于多网卡多IP的主机 |
void send(DatagramPacket packet) | 从此套接字发送数据包。数据包包含的信息为:将要发送的数据、其长度、远程主机的IP地址及端口号 |
void receive(DatagramPacket packet) | 当此方法返回时,DatagramPacket的缓冲区填充了接受的数据。数据包也包含发送方的IP地址及端口号,此方法在接受到数据包前一直阻塞。数据包对象的length字段包含所接受信息的长度,如果接受信息比包的长度长,该信息将被截短 |
void close() | 关闭此数据包套接字 |
3、模拟UDP
public class UServer extends Thread {
byte[] buf;
private DatagramSocket socket;
private DatagramPacket packet;
public UServer() throws Exception {
socket = new DatagramSocket(2222);
System.out.println("服务器在端口" + socket.getLocalPort() + "进行监听发送过来的数据包....");
}
@Override
public void run() {
while (true) {
try {
buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);// 会阻塞,直到接受到数据
InetAddress hostAddress = packet.getAddress();
int port = packet.getPort();
String data = new String(packet.getData());
System.out.println("收到" + hostAddress.getHostAddress() + " 端口号:" + port + ",发送的信息:" + data);
String sendData = InetAddress.getLocalHost().getHostName() + " 已收到你发送的消息";
buf = sendData.getBytes();
packet = new DatagramPacket(buf, buf.length, hostAddress, port);
socket.send(packet);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
new UServer().start();
}
}
public class UClient {
public static void main(String[] args) throws Exception {
byte[] buf = "这是测试UDP的信息".getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 2222);
DatagramSocket socket = new DatagramSocket(2221);
socket.send(packet);
buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String receiveData = new String(packet.getData());
System.out.println("从服务器" + packet.getAddress().getHostName() + "发送的内容为:\n" + receiveData);
socket.close();
}
}
// 输入输出结果如下
服务器在端口2222进行监听发送过来的数据包....
收到192.168.187.1 端口号:2221,发送的信息:这是测试UDP的信息
从服务器HK发送的内容为:
HK 已收到你发送的消息
更多关于网络编程的特性,需要在实践中慢慢摸索。
总访问次数: 242次, 一般般帅 创建于 2018-04-14, 最后更新于 2020-06-25
欢迎关注微信公众号,第一时间掌握最新动态!