目录
一,网络基础
1.IP地址
为了准确地定位网络上的目标主机,网络中的每个设备都会有一个唯一的数字标识,即网络设备的IP地址,通过ip地址,可以精确地匹配目标主机,是网络中资源共享、数据传输的依据。
2.端口
一台主机上可能运行着许多应用程序,通过端口号可以精确地确定客户访问的是哪一个应用程序。在一台主机上,应用程序可以占用任何一个端口号;一旦应用程序占据这个端口号,其它应用将不能再占用该端口。在主机中,端口号1~1024是系统保留端口号,用来为常用的网络服务程序所占用。用户自定义应用程序,最好占用其它端口号。
3.TCP/UDP协议
网络传输过程中,数据的传递有两种最常见的形式:
- TCP协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议 需要首先在网络两端建立安全连接,再进行数据传递,确保网络双方完整无误地传输数据。
- UDP协议,是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务 无需建立网络双方连接,直接发送数据包(包含目的地址信息),可能会因为网络问题导致数据传输失败等问题,但是传输速度很快,常用于局域网中传输数据
4.网络编程开发模式
- 客户端/服务器端模式(C/S模式):对于不同的服务器端程序建立不同的客户端程序
- 浏览器/服务器段模式(B/S模式):对于不同的服务器端程序使用统一的“客户端”(即浏览器)即可
二,基于套接字的Java网络编程
1.Socket
客户端与服务器端建立连接后,连接两端将会建议一个虚拟“线缆”,称之为Socket(套接字);
通信的两端都要有Socket,是两台机器间通信的端口,网络通信其实就是Socket间的通信;
Socket借助IP地址和端口号,对应某一台主机中的某一个应用程序,Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输;
2.InetAddress
Java提供了InetAddress类,以便于获取域名和ip地址:
//1.获取本机 InetAddress 对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
//2.根据主机名获取指定的 InetAddress 对象
InetAddress host1 = InetAddress.getByName("LAPTOP-TE6QI6K6");
System.out.println(host1);
//3.根据域名获取 InetAddress 对象
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println(host2);
//4.根据 InetAddress 对象,获取ip地址
String hostAddress = host2.getHostAddress();
System.out.println(hostAddress);
//5.根据 InetAddress 对象,获取域名
String hostName = host2.getHostName();
System.out.println(hostName);
三.基于TCP的Socket网络编程
1.单服务器端与单Socket客户端一次通讯
对于客户端Socket应用程序来说,需要与服务器端建立连接(通过ip和端口号确定主机上的程序),并向服务器端发送数据,接收服务器端数据;
对于服务器端来说,服务器端需用SeverSocket类来监听特定端口,接收客户端连接,接收客户端请求,向客户端发送响应;
客户端示例:
public class MyClient1 {
public static void main(String[] args) throws IOException {
//1.通过IP地址和端口号连接到服务端
//2.成功连接后,生成Socket
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
//3.得到和Socket对象关联的输出流
InputStream inputStream = socket.getInputStream();
OutputStream outputStream =socket.getOutputStream();
//4.通过输出流,向数据通道中写入数据
outputStream.write("yuanshenqidong!".getBytes());
byte by[] = new byte[1024];
int len = inputStream.read(by);
System.out.println(new String(by, 0, len));
socket.close();
outputStream.flush();
outputStream.close();
}
}
服务器端示例:
public class MyServer1 {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
//1.在本机的9999端口进行监听,等待连接,(要求没有其他服务在监听9999)
//2.当没有客户端连接进来时,程序会阻塞,等待连接,有客户端连接时就会返回Socket对象,连接成功
Socket socket = serverSocket.accept();
//3.通过getOutputStream()获取客户写入数据通道的数据
InputStream inputStream = socket.getInputStream();
OutputStream outputStream =socket.getOutputStream();
byte by[] = new byte[1024];
int len = inputStream.read(by);
System.out.println(new String(by, 0, len));
outputStream.write("yuanshenqidong".getBytes());
socket.close();
serverSocket.close();
inputStream.close();
outputStream.flush();
outputStream.close();
}
}
2.单服务器端接收多次通讯
如果想要一个服务器与多个客户端建立连接的话,可以让服务器端持续监听特定端口,并将读写功能放入其他线程,便可实现客户端与服务端通信功能。
服务器端:
//服务器端
public class MyServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
while(true){
Socket socket = serverSocket.accept();
new Thread(new Read(socket)).start();
new Thread(new Write(socket)).start();
}
//socket.close();
}
}
客户端:
//客户端
public class MyClient {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
new Thread(new Read(socket)).start();
new Thread(new Write(socket)).start();
//socket.close();
}
}
读入数据:
public class Read implements Runnable{
Socket socket;
public Read(Socket socket) throws IOException {
this.socket = socket;
}
Scanner scanner = new Scanner(System.in);
@Override
public void run() {
InputStream inputStream = null;
try {
inputStream = socket.getInputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
while(true){
String strClient = null;
try {
strClient = bufferedReader.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
System.out.println(strClient);
}
}
}
写入数据:
public class Write implements Runnable{
Socket socket;
public Write(Socket socket) throws IOException {
this.socket = socket;
}
Scanner scanner = new Scanner(System.in);
@Override
public void run() {
OutputStream outputStream = null;
try {
outputStream = socket.getOutputStream();
} catch (IOException e) {
throw new RuntimeException(e);
}
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
while(true){
String str = scanner.next();
if(str.equals("退出!")){
break;
}
try {
bufferedWriter.write(str);
bufferedWriter.newLine(); //插入一个换行符,表示写入的内容结束,注意对方必须用readline接收;
bufferedWriter.flush(); //手动刷新
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
3.TCP网络通讯补充
当客户端连接到服务端后,实际上客户端也是通过一个端口和服务器端进行通讯的,这个端口是TCP/IP来分配的,是不确定的,是随机的。
例如,我们让服务器端在9999端口上监听,使用netstat指令可以看到9999端口正在监听:
启动客户端,可以看到这时客户端被分配了一个端口号:
四,基于UDP的网络编程
UDP传输协议通过数据包方式向服务器发送数据,在数据包中需要包含服务器的IP信息、端口信息等内容。因此,UDP网络编程必须提供以下对象来完成不同的任务:
- 网络两端接收消息或发送消息的对象(监听本机端口、发送消息、接收消息)
- 数据包对象(包含目的地IP和端口信息、数据报文信息)
1. DatagramSocket:收发消息对象
UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不确定何时抵达。
2. DatagramPacket:数据包对象
DatagramPacket对象封装了UDP数据包,在数据包中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。数据包大小不得超过64k(如果超过了64k,数据包会被拆分成若干个小于64k的数据包)。
可以看出:UDP协议中每个数据包都给出了完整的地址信息,因此无需建立发送方和接收方的连接。同时没有明确的服务器端和客户端,演变成了数据的发送端和接收端。
发送端:
public class Sender {
public static void main(String[] args) throws IOException {
//1.创建DatagramSocket对象,在8888端口号上接收数据
DatagramSocket datagramSocket = new DatagramSocket(8888);
//2.将数据打包,封装到DatagramPacket对象中,指明接收端的ip地址和端口号,类似发邮件时要指明收件人和地址
byte[] data = "原神启动".getBytes();
DatagramPacket datagramPacket = new DatagramPacket(data, data.length, InetAddress.getByName("127.0.0.1"), 9999);
//3.发送,关闭
datagramSocket.send(datagramPacket);
byte[] by = new byte[1024];
DatagramPacket datagramPacket1 = new DatagramPacket(by, by.length);
datagramSocket.receive(datagramPacket1);
int length = datagramPacket1.getLength();
byte[] data1 = datagramPacket1.getData();
String str = new String(data1, 0, length);
System.out.println(str);
datagramSocket.close();
}
}
接收端:
public class Receiver {
public static void main(String[] args) throws IOException {
//1.创建一个DatagramSocket对象,并在9999端口接收数据;
DatagramSocket datagramSocket = new DatagramSocket(9999);
//2.创建一个DatagramPacket对象,准备接收数据,接收数据包的大小最多为64k
byte by[] = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(by, by.length);//
//3.调用接收方法,将接收到的数据填充到datagramPacket对象中
datagramSocket.receive(datagramPacket);
//4.对数据进行拆包,获取数据字节长度和字节数组
int length = datagramPacket.getLength();
byte[] data = datagramPacket.getData();
//5.输出数据
String str = new String(data, 0, length).trim();
System.out.println(str);
byte[] data1 = "收到,原神启动".getBytes();
DatagramPacket datagramPacket1 = new DatagramPacket(data1, data1.length, InetAddress.getByName("127.0.0.1"), 8888);
datagramSocket.send(datagramPacket1);
//6.关闭资源
datagramSocket.close();
}
}