一个客户端要发起一次通信,首先要知道运行服务器端程序的主机IP地址。然后由网络基础设施利用目标地址将客户端发送的信息传递到正确主机上。在Java中,地址可以由一个字符串定义,可以是数字型的地址(192.168.1.10(IPv4地址),fe20:12a0::0abc:1234(IPv6地址)),也可以是主机名(www.baidu.com)。在后面的例子中,主机名必须被解析出数字型地址才能用来进行通信。
InetAddress类代表了一个网络目标地址,包括主机名和数字型的地址信息。该类有两个子类Inet4Address和Inet6Address分别对应了目前IP地址的两个版本。
为了获取本地主机地址,这里用了NetworkInterface类的功能,该类提供了访问主机所有信息的功能。
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.UnknownHostException;
import java.util.Enumeration;
public class InetAddressExample {
public static void main(String[] args)
{
// Get the network interfaces and associated for this host
try
{
NetworkInterface networkInterface = null;
// 获得该主机每一个接口的信息
Enumeration interfaceList = NetworkInterface.getNetworkInterfaces();
if(interfaceList == null)
{
System.out.println("--没有发现接口--");
}//if
else
{
while(interfaceList.hasMoreElements())
{
networkInterface = interfaceList.nextElement();
// 接口名称
System.out.println("接口名称->"+networkInterface.getName());
// 获取与接口相关联的地址 根据主机的不同配置 可能包含IPV4或IPV6地址
Enumeration inetAddressList = networkInterface.getInetAddresses();
if(inetAddressList == null)
{
System.out.println("--此网络接口没有地址--");
}//if
else
{
InetAddress address = null;
while(inetAddressList.hasMoreElements())
{
address = inetAddressList.nextElement();
// 对每个地址进行检测判断属于哪个IP地址子类
System.out.print(address instanceof Inet4Address ? "(v4)" : (address instanceof Inet6Address ? "(v6)" : "(?)"));
// 打印IP地址
System.out.println(":"+address.getHostAddress());
}//while
}
}//while
}
}
catch (Exception e)
{
}
System.out.println("-------------------------------------------------------------------------");
String host = "www.baidu.com";
try
{
// 一个名字可能关联了多个数字地址 该方法返回一组与给定主机名相关联的所有地址的实例
InetAddress[] addressesList = InetAddress.getAllByName(host);
for(InetAddress address : addressesList)
{
System.out.println(address.getHostName()+" "+address.getHostAddress());
}//for
// 给定主机名,确定主机的IP地址。
InetAddress address2 = InetAddress.getByName(host);
System.out.println(address2.getHostName()+" "+address2.getHostAddress());
// 返回主机名
InetAddress address3 = InetAddress.getLocalHost();
System.out.println(address3.getHostName()+" "+address3.getHostAddress());
}
catch (UnknownHostException e)
{
e.printStackTrace();
}
}
}
1.获取主机的网络接口列表:
Enumeration interfaceList = NetworkInterface.getNetworkInterfaces();
getNetworkInterfaces()方法返回了一个列表,其中包括了该主机的每个接口对应的NetWorkInterface类的实例。
2.空列表检测
if(interfaceList == null)
{
System.out.println("--没有发现接口--");
}
3.获取并打印列表中的每个接口地址:
while(interfaceList.hasMoreElements())
{
networkInterface = interfaceList.nextElement();
// 接口名称
System.out.println("接口名称->"+networkInterface.getName());
// 获取与接口相关联的地址 根据主机的不同配置 可能包含IPV4或IPV6地址
Enumeration inetAddressList = networkInterface.getInetAddresses();
if(inetAddressList == null)
{
System.out.println("--此网络接口没有地址--");
}//if
else
{
InetAddress address = null;
while(inetAddressList.hasMoreElements())
{
address = inetAddressList.nextElement();
// 对每个地址进行检测判断属于哪个IP地址子类
System.out.print(address instanceof Inet4Address ? "(v4)" : (address instanceof Inet6Address ? "(v6)" : "(?)"));
// 打印IP地址
System.out.println(":"+address.getHostAddress());
}//while
}
}//while
这里通过getName()方法为接口返回了一个本地名称。接口的本地名称通常由字母和数字联合组成,如:lo,net0
然后通过getInetAddresses()方法返回Enumeration
然后判断该列表是否为空:
if(inetAddressList == null)
{
System.out.println("--此网络接口没有地址--");
}
最后通过instanceof判断其真实的实例,然后通过getHostAddress()方法返回字符串来代表主机的数字型地址。
在下划线以下的部分:
String host = "www.baidu.com";
try
{
// 一个名字可能关联了多个数字地址 该方法返回一组与给定主机名相关联的所有地址的实例
InetAddress[] addressesList = InetAddress.getAllByName(host);
for(InetAddress address : addressesList)
{
System.out.println(address.getHostName()+" "+address.getHostAddress());
}//for
// 给定主机名,确定主机的IP地址。
InetAddress address2 = InetAddress.getByName(host);
System.out.println(address2.getHostName()+" "+address2.getHostAddress());
// 返回主机名
InetAddress address3 = InetAddress.getLocalHost();
System.out.println(address3.getHostName()+" "+address3.getHostAddress());
}
(其他方法请看api文档)
Java为Tcp协议提供了两个类:Socket类和ServerSocket类。Socket实例代表TCP连接的一段,TCP连接是一条抽象的双向通道,两端分别由IP地址和端口号确定。在开始通信前需要建立一个TCP连接,这需要TCP向服务器端TCP发送连接请求。ServerSocket实例监听TCP连接请求,并为每个请求创建Socket实例。(服务器端要同时处理ServerSocket实例和Socket实例,客户端只需要使用Socket实例。)
package com.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class Client {
public static final int port = 8080;
public static final String host = "localhost";
public static void main(String[] args) {
System.out.println("Client启动...");
while (true) {
Socket socket = null;
try {
//创建一个流套接字并将其连接到指定主机上的指定端口号
socket = new Socket(host,port);
//读取服务器端数据
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//向服务器端发送数据
PrintStream out = new PrintStream(socket.getOutputStream());
System.out.print("请输入: \t");
String str = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.println(str);
String ret =null;
boolean flag=false;
do {
ret=input.readLine();
if(null==ret)
break;
System.out.println("服务器端返回过来的是: " + ret);
// 如接收到 "OK" 则断开连接
if ("OK".equals(ret)) {
flag=true;
System.out.println("客户端将关闭连接");
Thread.sleep(500);
break;
}
}while(ret!=null);
if(flag)
break;
out.close();
input.close();
} catch (Exception e) {
System.out.println("客户端异常:" + e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
socket = null;
System.out.println("客户端 finally 异常:" + e.getMessage());
}
}
}
}
}
}
package com.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static final int port = 8080;// 监听的端口号
public static void main(String[] args) throws IOException {
System.out.println("Server启动...");
Server server = new Server();
// 创建一个ServerSocket,这里可以指定连接请求的队列长度
// new ServerSocket(port,3);意味着当队列中有3个连接请求是,如果Client再请求连接,就会被Server拒绝
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
// 从请求队列中取出一个连接
Socket socket = serverSocket.accept();
// System.out.println(socket.getInetAddress());
// 处理这次连接
try {
// 读取客户端数据
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String clientInputStr = input.readLine();// 这里要注意和客户端输出流的写方法对应,否则会抛 EOFException
// 处理客户端数据
System.out.println("客户端发过来的内容:" + clientInputStr);
// 向客户端回复信息
PrintStream out = new PrintStream(socket.getOutputStream());
System.out.print("请输入:\t");
// 发送键盘输入的一行
String s = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.println(s);
out.write(s.getBytes());
out.close();
input.close();
} catch (Exception e) {
System.out.println("服务器 run 异常: " + e.getMessage());
} finally {
if (socket != null) {
try {
socket.close();
} catch (Exception e) {
socket = null;
System.out.println("服务端 finally 异常:" + e.getMessage());
}
}
}
}
}
}
服务端:
1.首先通过ServerSocket serverSocket = new ServerSocket(port);使其侦听指定的端口。
2.通过死循环迭代新的连接请求:
当SocketServer实例通过accept()方法侦听到有客户端连接,它会返回一个Socket实例,该实例会连接到远程的客户端并能进行读写数据等操作。
这里通过:
// 读取客户端数据
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String clientInputStr = input.readLine();// 这里要注意和客户端输出流的写方法对应,否则会抛 EOFException
// 处理客户端数据
System.out.println("客户端发过来的内容:" + clientInputStr);
这里通过soket.getInputStream()获取客户端输入流,然后通过InputStreamReader()和BufferedReader()获取到Reader流对象,通过readLine()获取客户端输入的一行数据,最后打印。
然后通过:
// 向客户端回复信息
PrintStream out = new PrintStream(socket.getOutputStream());
System.out.print("请输入:\t");
// 发送键盘输入的一行
String s = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.println(s);
这里通过soket.getOutputStream()获取客户端的输出流对象,并将其封装入PrintStream中,然后通过String s = new BufferedReader(new InputStreamReader(System.in)).readLine();获取一行输入,最后out.println(s)将数据发送至客户端。
3.最后通过close方法关闭各个流对象。
客户端:
1.通过socket = new Socket(host,port); 获取一个套接字对象并绑定到指定的端口号。
2.向指定的服务端发送数据:
//向服务器端发送数据
PrintStream out = new PrintStream(socket.getOutputStream());
System.out.print("请输入: \t");
String str = new BufferedReader(new InputStreamReader(System.in)).readLine();
out.println(str);
3.从服务端接收数据:
BufferedReader input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String ret = input.readLine();
System.out.println("服务器端返回过来的是: " + ret);
4.最后检测是否是OK,如果是就跳出循环否则就再绑定服务端发送数据。
UDP协议提供了一种不同于TCP协议的端对端服务。实际上UDP协议只实现了两个功能:1.在IP协议的基础上添加了另一层地址(端口)2.对数据传输过程中可能产生的数据错误进行了检测,并抛弃以及损坏的数据。
Java中通过DatagramPacket类和DatagramSocket类使用UDP套接字。客户端和服务端都使用DatagramSocket来发送数据,使用DatagramPacket来接受数据。
与TCP协议发送和接受字节流不同,UDP终端交换的是一种称为数据报文的自包含信息。这种信息在Java中表示为DatagramPacket类的实例。发送信息时,java创建一个待发送信息的DatagramPacket实例,然后将其作为参数传递给DatagramSocket类的send()方法。接受消息时,java首先创建一个DatagramSocket实例,该实例预先分配了一些空间。然后将接收到的信息存放在该空间,然后把该实例作为参数传递给DatagramSocket的receive()方法。
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPClient {
public static void main(String[] args) {
try {
DatagramSocket socket = new DatagramSocket();
String text = "客户端发送信息";
byte[] buf = text.getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 9002);
socket.send(packet);
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServer {
public static void main(String[] args) {
try {
System.out.println("UDP服务端启动……");
DatagramSocket socket = new DatagramSocket(9002);
while(true){
byte[] buf = new byte[2048];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
byte[] data = packet.getData();
String msg = new String(data, 0, packet.getLength());
System.out.println(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端:
这里首先创建一个DatagramPacket实例封装数据,然后通过DatagramSocket实例的send()方法发送数据。最后通过服务端的DatagramSocket实例的receive方法将数据保存到byte[]数组中。需要注意的是当buf数组设置过小是,当数据大于该数组时那么receive()方法只返回这条消息的前n个字节。超出部分会自动被丢弃,而且对接程序不会有任何丢失信息的提示。(UDP数据报文所能负载的最多数据为65507字节。)
另一个需要注意的是,DatagramPacket的getData()方法,该方法返回的是缓冲区的原始大小。
OK~简单的TCP,UDP的Demo就完成了。