网络编程是指通过计算机网络实现数据传输和通信的编程技术。
网络编程三要素
IP地址:设备在网络中的地址,是设备在网络中的唯一标识,可以用ipconfig在命令窗口查看自己的ip地址,也能用java获取。
java
InetAddress ip1 = InetAddress.getLocalHost();
上述代码用InetAddress类中的getLocalHost()方法获取了本机的ip对象。然后可用对象名.
getHostAddress()获取本机ip地址。
目前,被广泛采用的ip地址的格式有IPv4和IPv6两种。
IPv4地址由32位二进制数组成,通常以点分十进制表示,例如192.168.1.1。
IPv4的主要特点包括:
- 地址长度:32位
- 地址表示法:点分十进制(如192.168.1.1)
- 地址空间:约43亿个地址
- 协议支持:广泛支持,几乎所有网络设备都兼容IPv4
Pv6地址由128位二进制数组成,通常以冒号分隔的十六进制表示,例如2001:0db8:85a3:0000:0000:8a2e:0370:7334。
IPv6的主要特点包括:
- 地址长度:128位
- 地址表示法:冒号分隔的十六进制(如2001:0db8:85a3::8a2e:0370:7334)
- 地址空间:约3.4×10^38个地址
- 协议支持:逐渐普及,越来越多的网络设备开始支持IPv6
IP域名:将域名转化为对应IP地址的分布式命名系统。例如:www.baidu.com
可以在命令窗口使用ping IP地址 来检查网络是否连通。
端口:应用程序在设备中的唯一标识。
每个端口都有一个唯一的数字标识,范围从0到65535。端口号帮助操作系统将接收到的数据包正确地传递给相应的应用程序或服务。一台电脑不能出现两个相同的端口,否则报错。
端口通常分为三类:
◦ 知名端口(Well-Known Ports):范围从0到1023,通常用于系统级服务,如HTTP(80端口)、HTTPS(443端口)、FTP(21端口)等。
◦ 注册端口(Registered Ports):范围从1024到49151,用于用户级应用程序或服务,如MySQL(3306端口)、Redis(6379端口)等。
◦ 动态或私有端口(Dynamic or Private Ports):范围从49152到65535,通常用于临时或动态分配的端口,如客户端应用程序的临时连接。
java
package InetAddressDemo;
import java.net.InetAddress;
public class InetAdressDemo1 {
public static void main(String[] args) {
//目标:认识InetAddress获取本机ip对象和对方ip对象
try {
//获取本机ip对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1);
System.out.println(ip1.getHostName());//获取本机主机名
System.out.println(ip1.getHostAddress());//获取本机ip地址
System.out.println("=======================");
//获取对方ip对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");//获取百度的ip对象
System.out.println(ip2);
System.out.println(ip2.getHostName());//获取对方主机名
System.out.println(ip2.getHostAddress());//获取对方ip地址
System.out.println("=======================");
//判断本机与对方主机是否互通
System.out.println(ip2.isReachable(5000));//5秒内,是否可以访问对方主机(命令窗口的ping)
}catch (Exception e){
e.printStackTrace();
}
}
}
协议:连接和数据在网络中传输的规则。
传输层的两个通信协议:
UDP:用户数据报协议
TCP:传输控制协议
UDP协议的特点
UDP(User Datagram Protocol,用户数据报协议)是一种无连接的传输层协议,具有以下特点:
无连接:UDP在发送数据之前不需要建立连接,直接发送数据包。
不可靠:UDP不保证数据包的顺序、可靠性或完整性,数据包可能会丢失、重复或乱序。
高效:由于没有连接建立和维护的开销,UDP的传输效率较高,适合对实时性要求高的应用。
简单:UDP协议头部较小,只有8个字节,处理速度快。
支持广播和多播:UDP支持将数据包发送到多个接收者,适合广播和多播应用。
TCP协议的特点
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的传输层协议,具有以下特点:
面向连接:TCP在数据传输之前需要建立连接,数据传输结束后需要释放连接。
可靠:TCP通过确认机制、重传机制、流量控制和拥塞控制等手段,确保数据包的顺序、可靠性和完整性。
效率较低:由于需要建立连接、维护连接和处理确认、重传等机制,TCP的传输效率相对较低。
复杂:TCP协议头部较大,至少20个字节,处理速度相对较慢。
不支持广播和多播:TCP只支持点对点的通信,不支持广播和多播。
应用场景
UDP:适用于对实时性要求高、允许少量数据丢失的应用,如视频流、在线游戏、DNS查询等。
TCP:适用于对数据可靠性要求高的应用,如文件传输、电子邮件、网页浏览等。
TCP通过三次握手建立连接,四次挥手断开连接
三次握手确保双方收发消息都没问题。
客户端发出连接请求->服务端返回响应->客户端确认收到了响应,连接成立。
四次挥手确保收发信息都已经完成。
客户端发送断开连接请求->服务端返回一个响应:稍等->服务端返回一个响应:消息处理完毕,确认断开->发出确认断开信息,连接断开。
UDP通信:一发一收
DatagramSocket用于UDP通信创建客户端,服务端。
用DatagramSocket创建的客户端对象可以通过对象名.send(数据包对象)发送数据包,服务端对象可以通过对象名.receive(数据包对象)接收数据包。
创建客户端时,用DatagramSocket(),系统会随机一个端口号
服务端:DatagramSocket(端口号) ,指定端口号
DatagramSocket:创建数据包
创建发出的数据包对象时,具有四个参数需要填写:
参数一:发送的数据
参数二:数据长度
参数三:目标ip地址
参数四:服务端程序的端口号
客户端
java
package UDPDemo;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPClientDemo1 {
public static void main(String[] args) throws Exception{
//目标:完成UDP通信一发一收:客户端开发
System.out.println("客户端启动了");
//1、创建发送端对象
DatagramSocket socket = new DatagramSocket();
//2、创建数据包对象封装要发送的数据(64kb以内)
byte[] data = "hello,UDP".getBytes();
/**
* 参数一:发送的数据
* 参数二:数据长度
* 参数三:目标ip地址
* 参数四:服务端程序的端口号
* 创建数据包对象
*/
DatagramPacket packet = new DatagramPacket(data,data.length, InetAddress.getLocalHost(), 8080);
//3、发送端对象发送数据包的数据
socket.send(packet);
}
}
服务端
java
package UDPDemo;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServerDemo2 {
public static void main(String[] args) throws Exception{
//目标:完成UDP通信一发一收,服务端开发
System.out.println("服务端启动了");
//1、创建接收端对象
DatagramSocket socket = new DatagramSocket(8080);
//2、创建数据包对象,用于接收数据
byte[] buf = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buf,buf.length);
//3、调用方法接收数据,将数据封装到数据包对象的字节数组中
socket.receive(packet);
//4、解析数据包对象,获取数据,获取当前收到的数据长度
int length = packet.getLength();
String data = new String(buf,0,length);
System.out.println("服务端收到了"+data);
//获取对方ip对象和程序端口
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println("对方ip:"+ip+" 对方端口:"+port);
}
}
UDP通信:多发多收
客户端
java
package UDP2Demo;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UDPClientDemo1 {
public static void main(String[] args) throws Exception{
//目标:完成UDP通信多发多收:客户端开发
System.out.println("客户端启动了");
//1、创建发送端对象
DatagramSocket socket = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true) {
//2、创建数据包对象封装要发送的数据(64kb以内)
System.out.println("请输入你要传输的内容");
String msg = sc.nextLine();
//如果输入:exit,则结束发送
if ("exit".equals(msg)) {
socket.close();
break;
}
byte[] data = msg.getBytes();
DatagramPacket packet = new DatagramPacket(data,data.length, InetAddress.getLocalHost(), 8080);
//3、发送端对象发送数据包的数据
socket.send(packet);
}
}
}
服务端
java
package UDP2Demo;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServerDemo2 {
public static void main(String[] args) throws Exception{
//目标:完成UDP通信多发多收,服务端开发
System.out.println("服务端启动了");
//1、创建接收端对象
DatagramSocket socket = new DatagramSocket(8080);
//2、创建数据包对象,用于接收数据
byte[] buf = new byte[1024*64];
DatagramPacket packet = new DatagramPacket(buf,buf.length);
while (true) {
//3、调用方法接收数据,将数据封装到数据包对象的字节数组中
socket.receive(packet);//等待式接收数据
//4、解析数据包对象,获取数据,获取当前收到的数据长度
int length = packet.getLength();
String data = new String(buf,0,length);
System.out.println("服务端收到了:"+data);
//获取对方ip对象和程序端口
String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();
System.out.println("对方ip:"+ip+" 对方端口:"+port);
}
}
}
可以启动多个客户端端口与服务端端口发消息,证明其可以接收多个端口的消息,上述通过while循环实现多发多收,直到输出exit才停止程序。
TCP通信:一发一收
java提供了一个Socket类来实现TCP通信。
客户端字节输出流传输消息->服务端字节输入流接入->服务端字节输出流输出->客户端字节输入流输入
客户端
java
package TCP1Demo;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
public class ClientDemo1 {
public static void main(String[] args) throws Exception{
//目标:实现TCP通信下一发一收:客户端开发
System.out.println("客户端启动....");
//1、常见Scoket管道对象,请求与服务端的Scoket链接,可靠链接
//与指定的服务器id进行连接 端口
Socket socket = new Socket("电脑ip地址", 9999);
//2、从socket通信管道中获取一个字节输出流
OutputStream os = socket.getOutputStream();
//3、特殊数据流
DataOutputStream dos = new DataOutputStream(os);
dos.writeInt(1);
dos.writeUTF("在吗");
socket.close(); //关闭socket,释放资源
}
}
因为TCP通信要通过指定IP来制定Scoket管道对象,因此可以用ipconfig指令获取本机ip,用本机ip进行测试。
服务端
java
package TCP1Demo;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo2 {
public static void main(String[] args) throws Exception{
//目标:实现TCP通信下一发一收:服务端开发
System.out.println("服务端启动了");
//1、创建一个服务端Socket对象,绑定端口号,监听客户端连接
ServerSocket ss = new ServerSocket(9999);
//2、调用accept方法,阻塞式等待客户端连接,一旦有客户端连接会返回一个Socket对象
Socket socket = ss.accept();
//3、从Socket管道中得到一个字节输入流,读取客户端发送的数据
InputStream is = socket.getInputStream();
//4、把字节输入流包装成特殊数据输入流
DataInputStream dis = new DataInputStream(is);
int id = dis.readInt();
String msg = dis.readUTF();
System.out.println("id:"+id+" 收到的msg:"+msg);
//客户端的ip和端口
System.out.println("客户端的ip:"+socket.getInetAddress().getHostAddress());
System.out.println("客户端的端口:"+socket.getPort());
}
}
TCP通信:多发多收
客户端
java
package TCP2Demo;
import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo1 {
public static void main(String[] args) throws Exception{
//目标:实现TCP通信下多发多收:客户端开发
System.out.println("客户端启动....");
//1、常见Socket管道对象,请求与服务端的Socket链接,可靠链接
//与指定的服务器id进行连接 端口
Socket socket = new Socket("本机ip", 9999);
//2、从socket通信管道中获取一个字节输出流
OutputStream os = socket.getOutputStream();
//3、特殊数据流
DataOutputStream dos = new DataOutputStream(os);
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请输入:");
String msg = sc.nextLine();
if("exit".equals(msg))
{
System.out.println("退出成功");
socket.close();
break;
}
dos.writeUTF(msg);
dos.flush();
}
}
}
服务端
java
package TCP2Demo;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo2 {
public static void main(String[] args) throws Exception{
//目标:实现TCP通信下多发多收:服务端开发
System.out.println("服务端启动了");
//1、创建一个服务端Socket对象,绑定端口号,监听客户端连接
ServerSocket ss = new ServerSocket(9999);
//2、调用accept方法,阻塞式等待客户端连接,一旦有客户端连接会返回一个Socket对象
Socket socket = ss.accept();
//3、从Socket管道中得到一个字节输入流,读取客户端发送的数据
InputStream is = socket.getInputStream();
//4、把字节输入流包装成特殊数据输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
String msg = dis.readUTF(); //等待读取客户端发送的数据
System.out.println("收到的信息:"+msg);
//客户端的ip和端口
System.out.println("客户端的ip:"+socket.getInetAddress().getHostAddress());
System.out.println("客户端的端口:"+socket.getPort());
}
}
}
但因为TCP通信的特殊性(用accept返回Socket对象),所以上述代码的服务端只能够接收一台电脑客户端的消息,我们可以用多线程实现接收多台电脑的消息。
服务端的线程类
java
package TCP3Demo;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class ServerReader extends Thread{
private Socket socket;
public ServerReader(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
while (true){
String msg = dis.readUTF();
System.out.println("收到信息:"+msg);
//对方客户端的ip和端口
System.out.println("客户端的ip:"+socket.getInetAddress().getHostAddress());
System.out.println("客户端的端口:"+socket.getPort());
System.out.println("-----------------------------------------------");
}
} catch (IOException e) {
System.out.println("客户端已退出"+socket.getInetAddress().getHostAddress());
}
}
}
服务端
java
package TCP3Demo;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo2 {
public static void main(String[] args) throws Exception{
//目标:实现TCP通信下多发多收:服务端开发,支持多个客户端开发
System.out.println("服务端启动了");
//1、创建一个服务端Socket对象,绑定端口号,监听客户端连接
ServerSocket ss = new ServerSocket(9999);
while (true) {
//2、调用accept方法,阻塞式等待客户端连接,一旦有客户端连接会返回一个Socket对象
Socket socket = ss.accept();
System.out.println("一个客户端连接了");
System.out.println("客户端的ip:"+socket.getInetAddress().getHostAddress());
//3、把这个客户端管道交给一个独立的子线程专门负责接收这个管道的消息
new ServerReader(socket).start();
}
}
}
这样,每收到一个客户端的消息服务端就开一个线程进行处理,实现了TCP通信的多发多收。
BS架构的理解
需求:从浏览器中访问服务器,并让服务器响应一个简单的网页给浏览器展示,内容就是CodeBlossom的频道。
思路:因为是从浏览器中访问,会有多个访问数据,因此不能再使用直接创建线程来实现多个客户端访问,要使用线程池,防止资源耗尽。因为是从浏览器中访问,因此不需要实现客户端,只需要实现服务端即可。
java
package TCP4Demo;
import java.io.*;
import java.net.Socket;
public class ServerReader extends Thread{
private Socket socket;
public ServerReader(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
//给当前对应的浏览器管道响应一个网页数据
OutputStream os = socket.getOutputStream();
//1、从socket通信管道中获取一个字节输入流
//通过字节输出流包装数据出去给浏览器
//服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回数据
//把字节输出流包装成打印流
PrintWriter pw = new PrintWriter(os);
//写响应数据
pw.println("HTTP/1.1 200 OK");
pw.println("Content-Type:text/html;charset=utf-8");
pw.println();//必须换一行
pw.println("<html>");
pw.println("<head>");
pw.println("<meta charset='utf-8'>");
pw.println("<title>");
pw.println("CodeBlossom的频道");
pw.println("</title>");
pw.println("</head>");
pw.println("<body>");
pw.println("<h1 style='color:red;front-size=20px'>CodeBlossom的频道</h1>");
pw.println("</body>");
pw.println("</html>");
pw.close();
socket.close();
} catch (IOException e) {
System.out.println("客户端下线了"+socket.getInetAddress().getHostAddress());
}
}
}
java
package TCP3Demo;
import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo2 {
public static void main(String[] args) throws Exception{
//目标:实现TCP通信下多发多收:服务端开发,支持多个客户端开发
System.out.println("服务端启动了");
//1、创建一个服务端Socket对象,绑定端口号,监听客户端连接
ServerSocket ss = new ServerSocket(9999);
while (true) {
//2、调用accept方法,阻塞式等待客户端连接,一旦有客户端连接会返回一个Socket对象
Socket socket = ss.accept();
System.out.println("一个客户端连接了");
System.out.println("客户端的ip:"+socket.getInetAddress().getHostAddress());
//3、把这个客户端管道交给一个独立的子线程专门负责接收这个管道的消息
new ServerReader(socket).start();
}
}
}
做完这一切后,启动程序,就可以在浏览器中用ip地址:端口访问自己的网页了,就是因为没有注册域名,只有处于相同局域网的电脑才能访问。