网络编程
A.网络编程
1)网络模型概述
计算机网络之间以何种规则进行通信,就是网络模型研究问题。
网络模型一般是指
OSI(Open System Interconnection开放系统互连)七层参考模型
TCP/IP四层参考模型
2)网络模型7层概述
1.物理层:主要定义物理设备标准
如网线的接口类型、光纤的接口类型、各种传输介质的传输速率等
它的主要作用是传输比特流
就是由1、0转化为电流强弱来进行传输,到达目的地后在转化为1、0
这一层的数据叫做比特
2.数据链路层:主要将从物理层接收的数据进行MAC地址(网卡的地址)的封装与解封装
常把这一层的数据叫做帧。在这一层工作的设备是交换机,数据通过交换机来传输
3.网络层:主要将从下层接收到的数据进行IP地址(例192.168.0.1)的封装与解封装
在这一层工作的设备是路由器,常把这一层的数据叫做数据包
4.传输层:定义了一些传输数据的协议和端口号(WWW端口80等)
主要是将从下层接收的数据进行分段和传输,到达目的地址后再进行重组。
常常把这一层数据叫做段
5.会话层:通过传输层(端口号:传输端口与接收端口)建立数据传输的通路。
主要在你的系统之间发起会话或者接受会话请求
6.表示层:主要是进行对接收的数据进行解释、加密与解密、压缩与解压缩等
7.应用层:主要是一些终端的应用
3)网络编程的三要素
1.IP地址
a.作用:用来在互联网上标示一个计算机的唯一性
b.由来:我们计算机只能识别二进制数据,因此我们的IP地址应该也是一个二进制数据.32位
比如: 00000100. 00000010 .00001000 .00000001
如果我们使用上面的标示一个IP地址,那么我们记忆起来就比较麻烦.
于是我们就把上边的而二进制数据使用"."进行隔开
每一个字节隔开.在把每一个字节转换成十进制数据
于是上边的IP地址就可以转换为: 4.2.8.1
于是上边的IP地址就可以转换为: 4.2.8.1
c.IP地址的组成:IP地址 = 网络地址 + 主机地址
A类IP地址:第一段号码为网络地址,剩下的三段号码为本地计算机的号码
能配 256 * 256 * 256 = 16777216 台电脑
B类IP地址:前二段号码为网络地址,剩下的二段号码为本地计算机的号码
能配 256 * 256 = 65536 台电脑
C类IP地址:前三段号码为网络地址,剩下的一段号码为本地计算机的号码
256 公司用的比较多
d.IP地址的分类:
A类 1.0.0.1---127.255.255.254
B类 128.0.0.1---191.255.255.254172.16.0.0---172.31.255.255是私有地址。
C类 192.0.0.1---223.255.255.254192.168.X.X是私有地址 只能用于局域网
D类 224.0.0.1---239.255.255.254
E类 240.0.0.1---247.255.255.254
e.特殊的IP地址:
127.0.0.1 本地回环地址用来做一些本地测试不走交换机
ping IP地址; 用来检测本机是否可以和指定的IP地址的计算机可以进行正常通讯
ipconfig /all 用来查看IP地址
getmac 用来获取mac地址
2. 端口号
物理端口:物理设备对应的端口, 网卡口
逻辑端口:用来标示我们的计算机上的进程(正在运行的程序)
端口号的有效范围应该是 0-65535,其中0-1024被系统占用或者保留
3.协议:UDP/TCP
面试题:两者区别
UDP:
把数据打成一个数据包 , 不需要建立连接
数据包的大小有限制不能超过64k
因为无连接,所以属于不可靠协议(可能丢失数据)
因为无连接 ,所以效率高
TCP:
需要建立连接,形成连接通道
数据可以使用连接通道直接进行传输,无大小限制
因为有链接,所以属于可靠协议
因为有链接,所以效率低
4)InerAddress
1.概述
为了方便我们对IP地址的获取和操作,java提供了一个类InetAddress 供我们使用
此类表示互联网协议 (IP) 地址
2.常见功能
public static InetAddress getByName(String host)
封装一个主机:可以是主机名,也可以是IP地址的字符串表现形式
查看主机名:hostname
public String getHostAddress()://获取IP
public String getHostName()://获取主机名
import java.io.IOException;
import java.net.InetAddress;
public class IPDemo {
public static void main(String[] args) throws IOException {
// 创建InerAddress类对象
// 通过hostname命令查找主机名
InetAddress address = InetAddress.getByName("WPF-PC");
// 获取ip
String ip = address.getHostAddress();
System.out.println(ip);// 192.168.31.122
// 获取主机名
String name = address.getHostName();
System.out.println(name);// WPF-PC
}
}
B.UDP协议
1)Socket通信原理
Socket=IP+端口号
1.Socket套接字概述
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
2.Socket原理机制
通信的两端都有Socket。
网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输。
2)涉及类DatagramSocket/DatagramPacket
DatagramSocket:此类表示用来发送和接收数据报包的套接字
构造方法:DatagramSocket(int port, InetAddress laddr)
创建数据报套接字,将其绑定到指定的本地地址。
DatagramPacket:数据报包
构造方法:DatagramPacket(byte[] buf, int length, InetAddress address, int port)
用来将长度为 length 的包发送到指定主机上的指定端口号
3)UDP协议发送数据/接收数据
方便演示(我建立单机通讯,有局域网的,可以建立双机通讯)
先建立服务器端(接收端)
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPSrver {
public static void main(String[] args) throws IOException {
// 创建服务端对象:端口号一直
DatagramSocket ds = new DatagramSocket(9527);
// 按照字节数组接收数据
byte[] bys = new byte[1024];
int length = bys.length;
// 创建数据报包对象用来接收数据
DatagramPacket dp = new DatagramPacket(bys, length);
// 接收数据
System.out.println("服务器已开启,等待中...");
ds.receive(dp);// 服务端会卡在这里,直到客户端连接
System.out.println("数据传输完毕...");
// 获取传输来源
String ip = dp.getAddress().getHostAddress();// 客户端ip
String name = dp.getAddress().getHostName();// 客户端主机名
// 解析数据
byte[] data = dp.getData();
// 获取数据实际长度
int length2 = dp.getLength();
System.out.println(new String(data, 0, length2));
System.out.println("客户端主机名:" + name);
System.out.println("客户端ip地址:" + ip);
// 释放资源
ds.close();
}
}后建立客户端(发送端)
import java.io.IOException;运行结果(先运行服务端,再运行客户端)
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPClicent {
public static void main(String[] args) throws IOException {
// 封装服务端主机 查找服务端主机ip: ipconfig /all
InetAddress address = InetAddress.getByName("192.168.31.122");
// 创建客户端对象
DatagramSocket ds = new DatagramSocket();
// 传出数据
byte[] bys = "我爱你中国".getBytes();
int length = bys.length;
// 创建数据报包对象 端口号要一致
DatagramPacket dp = new DatagramPacket(bys, length, address, 9527);
// 发送数据
ds.send(dp);
System.out.println("发送完毕!");
// 释放资源
ds.close();
}
}先运行服务端
再运行客户端(查看服务端控制台)
查看客户端控制台
注意:只运行客户端也不会报错,因为UDP不需要连接,至发送出去,至于能不能接收到不管
4)改进:UDP协议发送端的数据来自于键盘录入
服务端(不需要释放资源,服务器正常情况下不会关闭)
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPServer {
public static void main(String[] args) throws IOException {
// 创建服务端对象:端口号一直
DatagramSocket ds = new DatagramSocket(9527);
while (true) {
// 按照字节数组接收数据
byte[] bys = new byte[1024];
int length = bys.length;
// 创建数据报包对象用来接收数据
DatagramPacket dp = new DatagramPacket(bys, length);
// 接收数据
System.out.println("服务器已开启,等待中...");
ds.receive(dp);// 服务端会卡在这里,直到客户端连接
System.out.println("数据传输完毕...");
// 获取传输来源
String ip = dp.getAddress().getHostAddress();// 客户端ip
String name = dp.getAddress().getHostName();// 客户端主机名
// 解析数据
byte[] data = dp.getData();
// 获取数据实际长度
int length2 = dp.getLength();
System.out.println(new String(data, 0, length2));
System.out.println("客户端主机名:" + name);
System.out.println("客户端ip地址:" + ip);
}
}
}客户端
import java.io.IOException;客户端控制台
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UDPClicent {
public static void main(String[] args) throws IOException {
// 创建键盘录入对象
Scanner sc = new Scanner(System.in);
// 封装服务端主机
InetAddress address = InetAddress.getByName("192.168.31.122");
// 创建客户端对象
DatagramSocket ds = new DatagramSocket();
// 传输数据
String str = null;
System.out.println("请输入消息:");
while ((str = sc.next()) != null) {
System.out.println("请输入消息:");
byte[] bys = str.getBytes();
int length = bys.length;
// 创建数据报包对象
DatagramPacket dp = new DatagramPacket(bys, length, address, 9527);
// 传输数据
ds.send(dp);
}
// 释放资源
ds.close();
}
}
服务端控制台
5)改进:多线程改进聊天小程序(同一个控制台输出)
服务端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class MyServer extends Thread {
// 传递端口变量
private DatagramSocket ds;
public MyServer(DatagramSocket ds) {
super();
this.ds = ds;
}
@Override
public void run() {
try {
while (true) {
// 接收字节数组
byte[] bys = new byte[1024];
int length = bys.length;
// 创建数据报包对象
DatagramPacket dp = new DatagramPacket(bys, length);
// 接收数据
ds.receive(dp);;
// 解析数据
byte[] bs = dp.getData();
int length2 = dp.getLength();
String ip = dp.getAddress().getHostAddress();
System.out.println(new String(bs, 0, length2));
System.out.println("客户端ip:" + ip);
System.out.println("-----------------------");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}客户端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
public class MyClicent extends Thread {
// 传递端口变量
private DatagramSocket ds;
public MyClicent(DatagramSocket ds) {
super();
this.ds = ds;
}
@Override
public void run() {
try {
// 创建键盘录入对象
Scanner sc = new Scanner(System.in);
// 封装服务端主机
InetAddress address = InetAddress.getByName("192.168.31.122");
// 输入消息
String str = null;
System.out.println("请输入消息:");
while ((str = sc.next()) != null) {
byte[] bys = str.getBytes();
int length = bys.length;
// 创建数据报包对象
DatagramPacket dp = new DatagramPacket(bys, length, address, 9527);
// 传输
ds.send(dp);
System.out.println("-----------------------");
// 继续输入
System.out.println("请输入消息:");
}
// 释放资源
ds.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}测试类
import java.net.DatagramSocket;
import java.net.SocketException;
public class UDPDemo {
public static void main(String[] args) {
try {
// 创建客户端对象
DatagramSocket Client = new DatagramSocket();
// 创建服务端对象 定义服务端接口
DatagramSocket Reverse = new DatagramSocket(9527);
// 开启客户端线程
new MyClicent(Client).start();
// 开启服务端线程
new MyServer(Reverse).start();
} catch (SocketException e) {
e.printStackTrace();
}
}
}
运行结果
C.TCP协议
1)概述
ServerSocker服务端:此类实现服务器套接字。
服务器套接字等待请求通过网络传入
它基于该请求执行某些操作,然后可能向请求者返回结果。
Socker客户端:此类实现客户端套接字(也可以就叫“套接字”)。套接字是两台机器间通信的端点。
方法:Socket accept():侦听,有客户端连接,程序继续运行
Socket getInputStream():输入流,读取数据
Socket getOutputStream():输出流,写出数据
2)TCP协议发送数据/接收数据
(先服务端,后客户端。只运行服务端会报错,关闭先关闭客户端)
服务端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer {
public static void main(String[] args) {
try {
// 创建服务端的 端口
ServerSocket ss = new ServerSocket(9527);
// 侦听客户端
System.out.println("服务器已开启,等待客户端连接...");
Socket sk = ss.accept();// 服务器开启以后会卡在这里,等待客户端连接
System.out.println("客户端连接成功...");
// 读取数据流
InputStream in = sk.getInputStream();
// 获取ip
String ip = sk.getInetAddress().getHostAddress();
// 创建缓冲区,读取数据
byte[] bys = new byte[1024 * 8];// 习惯这么写
int len = in.read(bys);
System.out.println(new String(bys, 0, len));
System.out.println("客户端ip:" + ip);
// 收到消息后,反馈客户端
OutputStream out = sk.getOutputStream();
out.write("消息已经收到!".getBytes());
// 释放资源
sk.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}客户端
import java.io.IOException;运行结果
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class MyClicent {
public static void main(String[] args) {
try {
// 创建客户端对象
Socket sk = new Socket("192.168.31.122", 9527);
System.out.println("成功连接服务端,准备发送数据...");
// 获取输出流
OutputStream out = sk.getOutputStream();
// 写数据
out.write("我爱你中国".getBytes());
System.out.println("发送完毕,等待服务端接收确认...");
// 读取反馈
InputStream in = sk.getInputStream();
byte[] bys = new byte[1024 * 8];
int length = bys.length;
System.out.println(new String(bys, 0, length));
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}服务端(运行后先出现第一行,客户端一运行,服务端就出现第二行,然后接收数据)
客户端
3)方法
Socket shutdownOutput():结束输出
输出通道中判断结束条件 -1/null不顶用
4)需求:聊天对话
服务端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class MyServer {
public static void main(String[] args) {
try {
// 创建Scanner对象
Scanner sc = new Scanner(System.in);
// 创建服务端的 端口
ServerSocket ss = new ServerSocket(9527);
// 侦听客户端
Socket sk = ss.accept();// 服务器开启以后会卡在这里,等待客户端连接
// 读取数据流
InputStream in = sk.getInputStream();
OutputStream out = sk.getOutputStream();
while (true) {
// 创建缓冲区,读取数据
byte[] bys = new byte[1024 * 8];
int len = in.read(bys);
System.out.println(new String(bys, 0, len));
// 写数据
String str = sc.next();
out.write(str.getBytes());
out.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}客户端
import java.io.IOException;运行结果(服务端和客户端互相对照,两台机器效果更好)
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class MyClicent {
public static void main(String[] args) {
try {
// 创建键盘录入对象
Scanner sc = new Scanner(System.in);
// 创建客户端对象
Socket sk = new Socket("192.168.31.122", 9527);
// 获取通道流
OutputStream out = sk.getOutputStream();
InputStream in = sk.getInputStream();
while (true) {
// 写数据
String str = sc.next();
out.write(str.getBytes());
out.flush();
// 读数据
byte[] bys = new byte[1024 * 8];
int length = in.read(bys);
System.out.println(new String(bys, 0, length));
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}服务端控制台(白色为接收的数据,蓝色为发出的数据)
客户端控制台
5)需求:将客户端文件上传到服务端,服务端并接收
服务端
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MyReverse {
public static void main(String[] args) {
try {
// 创建服务器端口
ServerSocket ss = new ServerSocket(9527);
// 侦听
System.out.println("服务器开启,等待客户端连接!");
Socket sk = ss.accept();
System.out.println("客户端连接成功,正在接收数据!");
// 获取通道流
InputStream in = sk.getInputStream();
OutputStream out = sk.getOutputStream();
// 包装字节缓冲流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("eclipse.exe"));
BufferedInputStream bis = new BufferedInputStream(in);
// 接收数据
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
bos.flush();
}
System.out.println("文件接收完毕!");
// 反馈客户端
out.write("文件接收完毕!".getBytes());
out.flush();
// 释放资源
ss.close();// 服务器一般不释放资源
} catch (IOException e) {
e.printStackTrace();
}
}
}客户端
import java.io.BufferedInputStream;运行结果(对比服务端和客户端控制台,每条信息出现的时间先后顺序, 加上当前毫秒值)
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
public class MyClicent {
public static void main(String[] args) {
try {
// 封装服务端主机
InetAddress address = InetAddress.getByName("192.168.31.122");
// 创建客户端对象
Socket sk = new Socket(address, 9527);
System.out.println("成功连接服务器,开始上传文件!");
// 获取通道流
InputStream in = sk.getInputStream();
OutputStream out = sk.getOutputStream();
// 包装字节缓冲流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("eclipse-inst-win64.exe"));
BufferedOutputStream bos = new BufferedOutputStream(out);
// 传输数据
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
bos.flush();
}
// 传输完毕
sk.shutdownOutput();
System.out.println("文件上传成功,等待服务端确认!");
// 接受反馈
byte[] by = new byte[1024];
int lin = in.read(by);
System.out.println(new String(by, 0, lin));
// 释放资源
sk.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}服务端
客户端
结果
6)需求:多个客户端给服务器上传文本文件
客户端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class MyClicent {
public static void main(String[] args) {
try {
// 创建客户端对象
Socket sk = new Socket("192.168.31.122", 9527);
// 获取通道流
InputStream in = sk.getInputStream();
OutputStream out = sk.getOutputStream();
// 包装流
BufferedReader br = new BufferedReader(new FileReader("a.txt"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out));
// 传输
String line = null;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
// 传输完毕
sk.shutdownOutput();
// 接收反馈
byte[] bys = new byte[1024];
int len = in.read(bys);
System.out.println(new String(bys, 0, len));
// 释放资源
sk.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}服务端
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
public class UpLoadThread extends Thread {
private Socket sk;
public UpLoadThread(Socket sk) {
super();
this.sk = sk;
}
@Override
public void run() {
try {
// 获取通道流
InputStream in = sk.getInputStream();
OutputStream out = sk.getOutputStream();
// 包装流
BufferedReader br = new BufferedReader(new InputStreamReader(in));
BufferedWriter bw = new BufferedWriter(new FileWriter(System.currentTimeMillis() + "b.txt"));
// 读写
String line = null;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine();
bw.flush();
}
// 反馈
out.write("文件上传完毕!".getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}服务端测试类
import java.io.IOException;运行结果(先运行服务测试类)
import java.net.ServerSocket;
import java.net.Socket;
public class MyReverse {
public static void main(String[] args) {
try {
// 创建服务端对象
ServerSocket ss = new ServerSocket(9527);
// 侦听
System.out.println("服务器已开启,等待客户端连接...");
while (true) {
Socket sk = ss.accept();
// 开启线程
new UpLoadThread(sk).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
再运行客户端(多运行几次)
刷新工程(运行多少次,出现多少结果,为防止同名覆盖,加上上传时间System.currentTimeMillis())