28Java基础之网络编程
网络编程
什么是网络编程?
- 可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的)。
基本的通信架构
- 基本的通信架构有两种形式:CS架构(Client客户端/Server服务端)、BS架构(Brower浏览器/Server服务端)。
![image]()
![image]()
无论是CS架构,还是BS架构的软件都必须依赖网络编程!
网络通信的三要素

IP地址
- IP(Internet Protocol):全称"互联网协议地址",是分配给上网设备的唯一标志。
- IP地址的两种形式:IPv4、IPv6
IPv4

IPv6地址
- IPv6:共128位,号称可以为地球每一粒沙子编号。
- IPv6分成8段表示,每段每四位编码成一个16进制位表示,数之间用冒号(:)分开。
![image]()
IP域名
www.baidu.com
公网IP、内网IP
- 公网IP:是可以连接互联网的IP地址;内网IP:也叫局域网IP,只能组织机构内部使用。
- 192.168.开头的就是常见的局域网地址,范围即为192.168.0.0——192.168.255.255,专门为组织机构内部使用。
特殊IP地址
127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机。
IP常用的命令
- ipconfig:查看本机IP地址。
- ping IP地址:检查网络是否连通。
InetAddress
- 代表IP地址
InetAddress的常用方法如下:

案例
// 目标:InetAddress类代表IP地址对象,用来操作IP地址
public class InetAddressDemo01 {
public static void main(String[] args) throws Exception {
//1. 获取本机IP地址对象
InetAddress ip = InetAddress.getLocalHost();
System.out.println(ip.getHostAddress());
System.out.println(ip.getHostName());
System.out.println(ip.getHostName());
//2. 指定获取对方主机的IP地址对象
InetAddress ip1 = InetAddress.getByName("www.baidu.com");
System.out.println(ip1.getHostName());
System.out.println(ip1.getHostAddress());
//3. 判断本机与该主机是否能够联通:ping
System.out.println(ip1.isReachable(5000));
}
}
端口号
- 端口:标记着在计算机设备上运行的应用程序的,被规定为一个16位的二进制,范围0~65535。
分类
- 周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)
- 注册端口:1024~49151,分配给用户进程或某些应用程序。
- 动态端口:49152~65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配。
注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。
协议
- 通信协议:网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。
开放式网络互联标准:OSI网络参考模型
- OSI网络参考模型:全球网络互联标准。
- TCP/IP网络模型:事实上的国际标准。
![image]()
传输层的2连个通信协议
- UDP(User Datagram Protocol):用户数据包协议;TCP(Transmission Control Protocol):传输控制协议。
UDP协议
- 特点:无连接、不可靠通信、通信效率高。
- 不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口、目的IP、程序端口和数据(限制在64KB内)等。
- 发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠。
- 应用场景:语音通话,视频直播等等。
TCP协议
- 特点:面向连接、可靠通信、通信效率相对不高。
- TCP的最终目的:要保证在不可靠的信道上实现可靠的传输。
- TCP主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接。
- 场景:网页、文件下载、支付
TCP协议:三次握手建立可靠连接
- 可靠连接:确定通信双方,收发消息都是正常无问题的!(全双工)
![image]()
- 传输数据会进行确认,以保证数据传输的可靠性。
TCP协议:四次握手断开连接

UDP通信
- 特点:无连接、不可靠通信
- 不事先建立连接;发送端每次把药发送的数据(限制在64KB内)、接收端IP等信息封装成一个数据包,发出去就不管了。
- Java提供了一个Java.net.DatagramSocket类来实现UDP通信。
DatagramSocket:用于创建客户端、服务端

DatagramPacket:创建数据包

案例
客户端:
//目标:客户端实现(发送端)
public class Client {
public static void main(String[] args) throws Exception {
//1.创建发送端对象
DatagramSocket socket = new DatagramSocket(); // 默认会随机分配一个端口
//2. 创建一个数据包对象,负责封装要发送的数据
/*
* 参数1:要发送的数据,字节数组
* 参数2:要发送的数据的长度
* 参数3:接收端的IP地址对象
* 参数4:接收端的端口号
* */
byte[] buffer = "今晚一起啤酒、小龙虾、小烧烤,约吗??".getBytes();
DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getLocalHost(), 8888);
//3. 把这一包数据发送出去
socket.send(packet);
//4. 释放资源
socket.close();
System.out.println("客户端已发送完毕!");
}
}
服务端:
public class Server {
public static void main(String[] args) throws Exception {
//1. 创建接收端对象
DatagramSocket socket = new DatagramSocket(8888);
//2. 创建一个数据包对象
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 接收收据
socket.receive(packet);
//4. 把数据输出
int len = packet.getLength();
String msg = new String(buf, 0, len);
System.out.println("接收到的消息:" + msg);
//获取发送端的IP和端口
InetAddress ip = packet.getAddress();
System.out.println("对方IP:" + ip.getHostAddress());
int port = packet.getPort();
System.out.println("对方端口:" + port);
//5. 释放资源
socket.close();
}
}
TCP通信
- 特点:面向连接、可靠通信
- 通信双方事先会采用"三次握手"方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端。
- Java提供了一个java.net.Socket类来实现TCP通信。
![image]()
TCP通信之-客户端开发
- 客户端程序就是通过java.net包下的Socket类来实现的。
![image]()
![image]()
案例
//目标:完成TCP通信快速入门-客户端开发:实现1发1收。
public class Client {
public static void main(String[] args) throws Exception {
//1. 创建Socket对象,并同时请求与服务端程序的连接。
Socket skt = new Socket(InetAddress.getLocalHost(), 8888);
//2. 从Socket通信管道中得到一个字节输出流,用来发送数据给服务端程序。
OutputStream os = skt.getOutputStream();
//3. 把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
//4. 开始写数据出去了
dos.writeUTF("hello world!");
//5. 关闭资源
dos.close();
skt.close();
}
}
TCP通信-服务端程序的开发
- 服务端是通过java.net包下的ServerSocket类来实现的。
ServerSocket

案例:TCP单发通信
ServerReaderThread线程类:
public class ServerReaderThread extends Thread {
private Socket skt;
public ServerReaderThread(Socket skt) {
this.skt = skt;
}
@Override
public void run() {
try{
//3. 从Socket通信管道中得到一个字节输入流,用来读取数据。
InputStream is = skt.getInputStream();
//4. 把低级的字节输入流包装成缓冲字符输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
//5. 开始读数据了
String msg = dis.readUTF();
System.out.println("服务端接收到的消息:" + msg);
// 我们可以获取客户端的IP地址
System.out.println("客户端的IP地址:" + skt.getInetAddress().getHostAddress()+":"+skt.getPort());
} catch (Exception e) {
//6. 关闭资源
System.out.println(skt.getRemoteSocketAddress()+ "客户端已退出!");
dis.close();
skt.close();
break;
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
客户端:
//目标:完成TCP通信快速入门-客户端开发:实现1发1收。
public class Client {
public static void main(String[] args) throws Exception {
//1. 创建Socket对象,并同时请求与服务端程序的连接。
Socket skt = new Socket(InetAddress.getLocalHost(), 8888);
//2. 从Socket通信管道中得到一个字节输出流,用来发送数据给服务端程序。
OutputStream os = skt.getOutputStream();
//3. 把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
//4. 开始写数据出去了
dos.writeUTF("hello world!");
//5. 关闭资源
dos.close();
skt.close();
}
}
服务端:
//目标:完成TCP通信快速入门-服务端开发:实现1发1收。
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("----------服务端启动,等待客户端连接------------");
//1. 创建ServerSocket对象,指定服务端程序的端口号。
ServerSocket ss = new ServerSocket(8888);
//2. 调用ServerSocket对象的accept()方法,等待客户端程序的连接。
Socket skt = ss.accept();
//3. 从Socket通信管道中得到一个字节输入流,用来读取数据。
InputStream is = skt.getInputStream();
//4. 把低级的字节输入流包装成缓冲字符输入流
DataInputStream dis = new DataInputStream(is);
//5. 开始读数据了
String msg = dis.readUTF();
System.out.println("服务端接收到的消息:" + msg);
// 我们可以获取客户端的IP地址
System.out.println("客户端的IP地址:" + skt.getInetAddress().getHostAddress()+":"+skt.getPort());
//6. 关闭资源
dis.close();
skt.close();
}
}
案例:TCP的多发多收
ServerReaderThread线程类:
public class ServerReaderThread extends Thread {
private Socket skt;
public ServerReaderThread(Socket skt) {
this.skt = skt;
}
@Override
public void run() {
try{
//3. 从Socket通信管道中得到一个字节输入流,用来读取数据。
InputStream is = skt.getInputStream();
//4. 把低级的字节输入流包装成缓冲字符输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
//5. 开始读数据了
String msg = dis.readUTF();
System.out.println("服务端接收到的消息:" + msg);
// 我们可以获取客户端的IP地址
System.out.println("客户端的IP地址:" + skt.getInetAddress().getHostAddress()+":"+skt.getPort());
} catch (Exception e) {
//6. 关闭资源
System.out.println(skt.getRemoteSocketAddress()+ "客户端已退出!");
dis.close();
skt.close();
break;
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
客户端:
//目标:完成TCP通信快速入门-客户端开发:实现1发1收。
public class Client {
public static void main(String[] args) throws Exception {
//1. 创建Socket对象,并同时请求与服务端程序的连接。
Socket skt = new Socket(InetAddress.getLocalHost(), 8888);
//2. 从Socket通信管道中得到一个字节输出流,用来发送数据给服务端程序。
OutputStream os = skt.getOutputStream();
//3. 把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
//用户输入消息
Scanner sc = new Scanner(System.in);
System.out.println("请输入消息(exit为退出): ");
while (true) {
String msg = sc.nextLine();
if(msg.equals("exit")){
System.out.println("客户端已退出!");
//5. 关闭资源
dos.close();
skt.close();
break;
}
//4. 开始写数据出去了
dos.writeUTF(msg);
dos.flush();
}
}
}
服务端:
//目标:完成TCP通信快速入门-服务端开发:实现1发1收。
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("----------服务端启动,等待客户端连接------------");
//1. 创建ServerSocket对象,指定服务端程序的端口号。
ServerSocket ss = new ServerSocket(8888);
//2. 调用ServerSocket对象的accept()方法,等待客户端程序的连接。
Socket skt = ss.accept();
//3. 从Socket通信管道中得到一个字节输入流,用来读取数据。
InputStream is = skt.getInputStream();
//4. 把低级的字节输入流包装成缓冲字符输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
//5. 开始读数据了
String msg = dis.readUTF();
System.out.println("服务端接收到的消息:" + msg);
// 我们可以获取客户端的IP地址
System.out.println("客户端的IP地址:" + skt.getInetAddress().getHostAddress()+":"+skt.getPort());
} catch (Exception e) {
System.out.println(skt.getRemoteSocketAddress()+ "客户端已退出!");
//6. 关闭资源
dis.close();
skt.close();
break;
}
}
}
}

案例:即时通信-群聊
ServerReaderThread线程类:
public class ServerReaderThread extends Thread {
private Socket skt;
public ServerReaderThread(Socket skt) {
this.skt = skt;
}
@Override
public void run() {
try{
//3. 从Socket通信管道中得到一个字节输入流,用来读取数据。
InputStream is = skt.getInputStream();
//4. 把低级的字节输入流包装成缓冲字符输入流
DataInputStream dis = new DataInputStream(is);
while (true) {
try {
//5. 开始读数据了
String msg = dis.readUTF();
System.out.println("服务端接收到的消息:" + msg);
//把这个消息分发给全部客户端进行接收。
sendToAll(msg);
// 我们可以获取客户端的IP地址
System.out.println("客户端的IP地址:" + skt.getInetAddress().getHostAddress()+":"+skt.getPort());
} catch (Exception e) {
//6. 关闭资源
System.out.println(skt.getRemoteSocketAddress()+ "客户端已退出!");
Server.clients.remove(skt);
dis.close();
skt.close();
break;
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
private void sendToAll(String msg) throws Exception {
// 发送给全部在线的socket管道接收。
for(Socket skt: Server.clients){
DataOutputStream dos= new DataOutputStream(skt.getOutputStream());
dos.writeUTF(msg);
dos.flush();
}
}
}
服务端:
//目标:完成TCP通信快速入门-服务端开发:实现1发1收。
public class Server {
public static List<Socket> clients = new ArrayList<>();
public static void main(String[] args) throws Exception {
System.out.println("----------服务端启动,等待客户端连接------------");
//1. 创建ServerSocket对象,指定服务端程序的端口号。
ServerSocket ss = new ServerSocket(8888);
while (true) {
//2. 调用ServerSocket对象的accept()方法,等待客户端程序的连接。
Socket skt = ss.accept();
clients.add(skt);
System.out.println("一个客户端连接了,IP地址为:"+ skt.getRemoteSocketAddress().toString());
//3. 把这个客户端对应的socket对象,交给一个独立的线程负责处理
new ServerReaderThread(skt).start();
}
}
}
ClientReaderThread线程类:
public class ClientReaderThread extends Thread {
private Socket skt;
public ClientReaderThread(Socket skt) {
this.skt = skt;
}
@Override
public void run() {
try {
DataInputStream dis = new DataInputStream(skt.getInputStream());
while (true) {
try {
String Msg = dis.readUTF();
System.out.println(skt.getRemoteSocketAddress() + "说:" + Msg);
} catch (IOException e) {
System.out.println(skt.getRemoteSocketAddress() + "自己下线了!");
dis.close();
skt.close();
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端:
//目标:完成TCP通信快速入门-客户端开发:实现1发1收。
public class Client {
public static void main(String[] args) throws Exception {
//1. 创建Socket对象,并同时请求与服务端程序的连接。
Socket skt = new Socket(InetAddress.getLocalHost(), 8888);
//创建一个独立的线程,负责随机从socket中接收服务端发送过来的消息
new ClientReaderThread(skt).start();
//2. 从Socket通信管道中得到一个字节输出流,用来发送数据给服务端程序。
OutputStream os = skt.getOutputStream();
//3. 把低级的字节输出流包装成数据输出流
DataOutputStream dos = new DataOutputStream(os);
//用户输入消息
Scanner sc = new Scanner(System.in);
System.out.println("请输入消息(exit为退出): ");
while (true) {
String msg = sc.nextLine();
if(msg.equals("exit")){
System.out.println("客户端已退出!");
//5. 关闭资源
dos.close();
skt.close();
break;
}
//4. 开始写数据出去了
dos.writeUTF(msg);
dos.flush();
}
}
}
实现简易版BS架构
- BS架构的基本原理
![image]()
注意:服务器必须给浏览器响应http协议规定的数据格式,否则浏览器不识别返回的数据。
- HTTP协议规定:响应给浏览器的数据格式必须满足如下格式
![image]()
案例
ServerReaderThread线程类:
public class ServerReaderThread extends Thread {
private Socket skt;
public ServerReaderThread(Socket skt) {
this.skt = skt;
}
@Override
public void run() {
try {
//立即响应一个网页内容:你好呀baby
// DataOutputStream dos = new DataOutputStream(skt.getOutputStream());
// dos.writeUTF("你好呀,baby!");
PrintStream ps = new PrintStream(skt.getOutputStream());
String msg = "你好呀,baby!";
sendHtml(ps, msg);
ps.close();
skt.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void sendHtml(PrintStream ps, String msg){
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type: text/html;charset=UTF-8");
ps.println();//空行,标识响应头结束
ps.println("<div style='color:red;font-size:120px;text-align:center;'>"+ msg + "</div>");
}
}
服务端:
//目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通讯。
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("----------服务端启动,等待客户端连接------------");
//1. 创建ServerSocket对象,指定服务端程序的端口号。
ServerSocket ss = new ServerSocket(8080);
while (true) {
//2. 调用ServerSocket对象的accept()方法,等待客户端程序的连接。
Socket skt = ss.accept();
System.out.println("一有人上线了:"+ skt.getRemoteSocketAddress().toString());
//3. 把这个客户端对应的socket对象,交给一个独立的线程负责处理
new ServerReaderThread(skt).start();
}
}
}
每次请求都开一个新线程,到底好不好?
- 如果客户端比较少的情况下没问题,但是高并发时,容易宕机!
- 可以使用线程池来进行优化
![image]()
案例
服务端:
//目标:完成TCP通信快速入门-服务端开发:要求实现与多个客户端同时通讯。
public class Server {
public static void main(String[] args) throws Exception {
System.out.println("----------服务端启动,等待客户端连接------------");
//1. 创建ServerSocket对象,指定服务端程序的端口号。
ServerSocket ss = new ServerSocket(8080);
//创建出一个线程池
ThreadPoolExecutor pool = new ThreadPoolExecutor(16 * 2, 16 * 2, 0,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
while (true) {
//2. 调用ServerSocket对象的accept()方法,等待客户端程序的连接。
Socket skt = ss.accept();
System.out.println("一有人上线了:"+ skt.getRemoteSocketAddress().toString());
//3. 把这个客户端对应的socket对象,交给一个独立的线程负责处理
pool.execute(new ServerReaderRunnable(skt));
}
}
}
ServerReaderRunnable线程类:
public class ServerReaderRunnable implements Runnable {
private Socket skt;
public ServerReaderRunnable(Socket skt) {
this.skt = skt;
}
@Override
public void run() {
try {
//立即响应一个网页内容:你好呀baby
// DataOutputStream dos = new DataOutputStream(skt.getOutputStream());
// dos.writeUTF("你好呀,baby!");
PrintStream ps = new PrintStream(skt.getOutputStream());
String msg = "你好呀,baby!";
sendHtml(ps, msg);
ps.close();
skt.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public void sendHtml(PrintStream ps, String msg){
ps.println("HTTP/1.1 200 OK");
ps.println("Content-Type: text/html;charset=UTF-8");
ps.println();//空行,标识响应头结束
ps.println("<div style='color:red;font-size:120px;text-align:center;'>"+ msg + "</div>");
}
}












浙公网安备 33010602011771号