网络地址InetAddress
网络地址的表示
- 在计算机网络中,我们通过IP地址来标识、区分网络上的每一台设备。
- IPv4(Internet Protocol Version 4)使用4个字节(32位)来表示一个IP地址。我们通常将每个字节表示成一个十进制数,字节间用"."隔开。
- 在Java语言中,我们使用InetAddress来表示IP地址。
- InetAddress及其它Java网络编程中的常用工具类位于java.net包中,在只用这些类之前,我们需要先导入这个包。
获取本机地址
- 在Java语言中,我们使用InetAddress类的静态方法getLocalHost()来获取本机地址。
- 若IP地址获取失败,则抛出UnknownHostException异常。(用try-catch捕获)
例子:通过getLocalHost()获取本机地址
import java.net.*;
public class LocalAddressTest
{
public static void main(String[] args)
{
try{
InetAddress localAddress = InetAddress.getLocalHost();//重点
System.out.println(localAddress);
}
catch(UnknownHostException e){
System.out.println("获取不到本机地址");
}
}
}
获取互联网主机地址
获取互联网主机地址使用的是InetAddress类的静态方法getByName(String host),其中host可以是主机名(如"www.szu.edu.cn"),也可以是具体的IP地址(如"210.39.3.164")。
通过getByName(String host)获取互联网主机地址
import java.net.*;
public class RemoteAddressTest
{
public static void main(String[] args)
{
try
{
InetAddress remoteAddress = InetAddress.getByName("www.szu.edu.cn");
System.out.println(remoteAddress);
}
catch(UnknownHostException e)
{
System.out.println("获取不到主机地址");
}
}
}
UDP数据报
端口与数据报套接字
UDP(User Datagram Protocol)数据报协议:一种面向无连接的、不可靠的传输层协议。
-不需要在通信双方间建立连接,而采用”尽最大努力投递“的方式提供通信服务。
-由于其协议开销小、传输延时短、对传输环境要求高,通常用于局域网内不需要高可靠性传输的通信,例如局域网内的视频点播等应用。
- 端口号:UDP数据报协议提供16位的端口号(0-65535)来区分收发数据报的上层应用程序。【最好不使用0-1023的端口】
- 发送方(上层应用程序):除了指定IP地址外,还需要指定数据报的发送端口(源端口)和接收端口(目的端口)。
- 接收方(上层应用程序):监听相应的目的端口,当数据报送达时,上层应用程序就可收到具体的数据报内容。
- 套接字(Socket):UDP协议、IP地址和端口成为上层应用程序之间通信的窗口,即套接字。
- 对UDP协议来说,我们称这个套接字为数据报套接字(DatagramSocket)。
- 数据报套接字有两个常用的构造方法:DatagramSocket()和DatagramSocket(int prot)。通常,前者用于发送数据报,后者用于监听、接收数据报。
- 数据报包裹(DatagramPacket)构造方法
-发送包裹时:使用DatagramPacket(byte[] data, int length, InetAddress remoteAddr, int remotePort),其中remoteAddr和remotePort指明了接收方的地址。(数据、长度、IP地址、端口号)
-接收包裹时:通常只关注包裹的内容,此时常用DatagramPacket(byte[] data, int length)。
例子:发送UDP数据报
import java.net.*;
public class SendUDP {
public static void main(String[] args) throws Exception
{
DatagramSocket datagramsocket = null;//窗口
DatagramPacket datagrampacket = null;//包裹
datagramsocket = new DatagramSocket(2345);//把这个Socket的端口号设为2345
String str = "send UDP";
datagrampacket = new DatagramPacket(str.getBytes(),str.length(),InetAddress.getByName("127.0.0.1"),8000);//创建一个包裹
datagramsocket.send(datagrampacket);//发送
datagramsocket.close();
}
}
import java.net.*;
public class ReceiveUDP {
public static void main(String[] args) throws Exception
{
DatagramSocket datagramsocket = null;
DatagramPacket datagrampacket = null;
byte[] data = new byte[1024];
datagramsocket = new DatagramSocket(8000);//要指定端口也是8000(门牌号)
datagrampacket = new DatagramPacket(data,data.length);
System.out.println("正在等待客户端的数据");
datagramsocket.receive(datagrampacket);
System.out.println("收到客户端发来的:"+new String(data));
datagramsocket.close();
}
}
要先运行父亲,也就是接收方,再运行儿子,也就是发送方。
TCP连接(重点)
连接
- TCP(Transmission Control Protocol)是一种面向连接的传输控制协议,它提供可靠的数据流(data stream)传输服务,是互联网上使用最广泛的传输协议。
- TCP是面向连接的,在通信双方之间进行数据交换之前,双方要建立逻辑连接。连接建立之后,双方的通信就在该连接中进行。
套接字Socket
与数据报套接字类似,TCP也存在套接字的概念,而且,由于TCP比UDP更常见,通常说的套接字(Socket)是指TCP的套接字。
例子:Socket连接到服务器
(如何通过客户端,通过Socket来访问服务器,获得服务器上的信息。主要就是通过套接字来和服务器打交道。)
import java.io.*;
import java.net.*;
import java.util.*;
//作为客户端要创建一个套接字
//并且向这个时间服务器发送一个请求
//希望获得时间服务器上的信息
public class sttest {
public static void main(String[] args)
{
try {
Socket s = new Socket("time.nist.gov",13);
//第一部分是域名,第二部分是端口号
//有了这两个以后,客户端就可以连接到服务器端
//一旦创建了这个Socket对象,就意味着已经尝试连接到服务器那里去了
try {
InputStream is = s.getInputStream();
Scanner in = new Scanner(is);
while(in.hasNextLine()) {
String line = in.nextLine();
System.out.println(line);
}
}
finally {
s.close();
}
}
catch(IOException e) {
e.printStackTrace();
}
}
}
ServerSocket实现服务器
客户端:使用Socket类来创建socket并连接到服务器端。
服务器端:用来监听socket的类是ServerSocket类。
在Client/Server通信模式中,服务器端需要创建监听特定端口的ServerSocket对象来负责接收客户端的连接请求。
客户端:
import java.net.*;
import java.io.*;
//客户端
public class DaytimeClient {
public static void main(String[] args) {
String hostname;
if(args.length>0) {
hostname = args[0];
}
else
{
hostname = "localhost";
}
try {//关键
Socket theSocket = new Socket (hostname,13);//域名,端口号,创建Socket对象
InputStream timeStream = theSocket.getInputStream();//得到输入流
StringBuffer time = new StringBuffer();
int c;
while((c = timeStream.read())!=-1)//每次读一个字符,将其加到time里面
time.append((char)c);
String timeString = time.toString().trim();
System.out.println("It is "+timeString + " at "+ hostname);//读完后打印
}
catch(UnknownHostException e) {
System.err.println(e);
}
catch(IOException e) {
System.err.println(e);
}
}
}
//首先创建Stocket对象:域名、端口(向服务器端发送请求的过程)
//输入流
服务器端
import java.net.*;
import java.io.*;
//服务端
public class DaytimeServer {
public static final int SERVICE_PORT = 13;//端口号,与客户端保持一致
public static void main(String arg[]) {
try {
ServerSocket server = new ServerSocket(SERVICE_PORT);
System.out.println("Daytime service started");
for(;;)
{
Socket nextClient = server.accept();
//有客户端请求进来之前,这条语句会一直等待,阻塞在这里
//有客户端请求进来后才会接着往下读
System.out.println("Received request from "+ nextClient.getInetAddress()+":"+nextClient.getPort());
OutputStream out = nextClient.getOutputStream();//输出流
PrintStream pout = new PrintStream(out);//将输出流放入PrintStream,好处是可以把对象Date打印出去。
pout.print(new java.util.Date());
out.flush();//清空缓冲区
out.close();
nextClient.close();//关闭Client
}
}
catch(BindException be) {
System.err.println("Service already running on port "+SERVICE_PORT);
}
catch(IOException ioe) {
System.err.println("I/O error - "+ioe);
}
}
}
//首先创建了ServerSocket对象:
//端口号与客户端保持一致,才能连接,从而数据交流
//服务端等到客户端发来请求后才能继续往下走,
//从而把信息传给客户端
//一定要先运行服务器端
//一旦运行客户端的程序,客户端就会发送一个请求给服务器端
//服务器端做出反应,把时间信息传递过去
//客户端接收到这个时间信息就会把它打印出来
//服务端关闭当前客户端的连接
//服务器端重新进入等待(append)
//如果再运行客户端,服务器端会收到第二条请求,最后输出第二个结果
服务器多线程处理套接字连接
当有多个客户端同时发出请求时,一个服务器可以应对多个请求:
当服务器端收到一个请求时,它就产生一个线程来服务这个客户端。
不管有多少个客户端,服务器端总是能产生新的线程来服务。
例子:使用多线程来处理套接字连接:
import java.io.*;
import java.net.*;
public class EchoServer {
private int port = 8000;//端口号
private ServerSocket serverSocket;
public EchoServer() throws IOException
{
serverSocket = new ServerSocket(port);
System.out.println("服务器启动");
}
public static void main(String[] args) throws IOException
{
new EchoServer().service();
}
public void service() {//关键
while(true) {//无限等待
Socket socket = null;
try
{
socket = serverSocket.accept();
//一直在等待,只要有客户端请求进来它就会提供服务
Thread workThread = new Thread(new Handler(socket));
//收到请求后,创建一个新的线程
//一个实参传到Thread构造方法里
//里面是一个实现了Runnable接口的类的对象
workThread.start();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
}
import java.net.*;
import java.io.*;
public class Handler implements Runnable{
private Socket socket;
public Handler(Socket socket) {
this.socket = socket;
}
private PrintWriter getWriter(Socket socket) throws IOException
{
OutputStream socketOut = socket.getOutputStream();
return new PrintWriter(socketOut,true);
}
private BufferedReader getReader(Socket socket) throws IOException
{
InputStream socketIn = socket.getInputStream();
return new BufferedReader(new InputStreamReader(socketIn));
}
public String echo(String msg) {
return "echo:"+msg;
}//过来一个信息就会有回复
public void run() {
try
{
System.out.println("New connection accepted "+socket.getInetAddress()+":"+socket.getPort());
BufferedReader br = getReader(socket);
PrintWriter pw = getWriter(socket);
String msg = null;
while((msg = br.readLine())!=null)
{
System.out.println(msg);
pw.println(echo(msg));
if(msg.equals("bye")) break;
}
}
catch(IOException e)
{
e.printStackTrace();
}finally {
try {
if(socket != null)
socket.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}
}
Socket关闭与半关闭
- 当客户端与服务器端通信结束时,应及时关闭socket,以释放socket占用的资源。Socket的close()方法负责关闭socket。
- Socket的半关闭提供了如下功能:
套接字连接的一端可以终止其输出,但还可以接收来自另一端的数据。例如,向服务器传输数据时,可以关闭一个套接字的输出流来发送给服务器的数据结束,但还可以保持输入流在打开状态。
即服务器端不再输出,但客户端还可以接收。
程序片段:Socket半关闭
Socket socket = new Socket(host,port);//域名,端口号
Scanner in = new Scanner(socket.getInputStream());//输入流放入Scanner
PrintWriter write = new PrintWriter(socket.getOutputStream());//输出流
writer.print("finished");
writer.flush();//清空缓冲区
socket.shutdownOutput();
//意思是输出已经完成了,从此不再输出,但仍旧接收输入
while(in.hasNextLine() != null)00..
{
String linr = in.nextLine();
}
socket.close();