1、网络编程
1、如何定位网络上的主机?
IP 地址: 定位主机位置
端口号: 主机上的应用程序。电脑上很多程序,比如微信,qq,浏览器等。这个用来定位到底是那一个程序来通信。
2、定位之后如何进行数据传输?
一定的规则。OSI 参考模型或者TCP/IP 协议
TCP/IP 协议 是事实上的协议,OSI 没有在英特网上广泛应用。
协议是对 速度、传输代码、代码结构、传输控制步骤、出错控制等制定标准
制定协议时,采用协议分层思想,把复杂的问题划分为简单的多个成分,最后在组合起来。
最常用组合方式是层次方式,即同层之间可以通信、上一层可以调用下一层,不能调用下一层的下一层。各层之间互不影响。
1.1 IP与端口号
IP地址:java 有一个 InetAddress 类 来表示。
唯一标识Internet 上的计算机。
本地回环地址 hostAddress:127.0.0.1
本地主机名:localhost
IP地址分类: IPV4、IPV6
IPV4:4个字节组成, 4个 0-255 大概42亿。 30亿在北美,亚洲4亿 2011年初用尽。以点隔开,十进制表示
,如:192.168.0.1
IPV6:16个字节表示,共128位,写成8个无符号整数,每个整数用4个十六进制位表示,用冒号分开。
如:3ff3:3201:1401:1280:c8ff:fe4d:db39:1984
分类方式2:公网地址、私有地址。
192.168.xxx.xxx 是私有地址。范围 192.168.0.0 - 192.168.255.255 为机构内部使用。
公网地址就是英特网使用。
InetAddress 类是 JDK4 开始引入。这个类构造器是私有的,只能通过静态方法获取对象。
try {
InetAddress byName = InetAddress.getByName("127.0.0.1");
System.out.println(byName); // /127.0.0.1
} catch (UnknownHostException e) {
e.printStackTrace();
}
上面是使用 getByName 得到 InetAddress 对象。传入的一个ip地址。还可以传入 域名
try {
InetAddress byName = InetAddress.getByName("localhost");
System.out.println(byName); // localhost/127.0.0.1
} catch (UnknownHostException e) {
e.printStackTrace();
}
可以使用 getLocalHost 方法获取本机的ip地址。注意:一个主机可以有多个不同的ip。
try {
InetAddress byName = InetAddress.getLocalHost();
System.out.println(byName); // xgz/192.168.56.1
} catch (UnknownHostException e) {
e.printStackTrace();
}
我的电脑在局域网的ip是 192.168.56.1 同时,127.0.0.1 也是指我的电脑ip。
端口号:用来标识正在计算机上运行的进程(程序)
不同的进程有不同的端口号。端口号是一个16位的整数,范围为0-65535
端口号分类:
0-1023 被预先定义的服务占用。比如 http 占用80端口,ftp占用21端口,teinet占用23
1024-49151: 分配给用户进程或程序。就是我们自己可以自定义占用的端口。
49152-65535: 动态、私有端口。
端口号与IP的组合得出一个网络套接字:socket
1.2 网络协议
ip和端口号 的作用是定位需要传输的对象。找到了需要传输的对象还需要知道怎么传输。
传输就需要用到协议,TCP/IP协议簇。
TCP (transmission Control Protocol)传输控制协议
IP(Internet Protocol) 网络互联协议,是网络层的主要协议。支持网间互联数据通信。
TCP/IP 协议模型形成了四层体系结构,物理链路层、IP层、传输层、应用层。
传输过程有2个非常重要的协议:TCP、UDP协议
-
TCP 协议:
使用TCP协议前,需要建立TCP连接,形成传输数据通道。
传输前,采用三次握手,点对点通信
TCP协议通信的两个进程 客户端、服务端
连接成功后可以进行大数据量传输
传输完毕后,需要释放已建立的连接。
1、3是客户端,需要进行通信的人,2是服务端,接收信息的人。
-
UDP 协议:
将数据、源、目的封装成数据包,不需要建立连接
每个数据包的大小限制在64K内
发送不管对方是否准备好,接收方收到也不确认。是不可靠的
可以进行广播发送
发送结束后无需释放资源,开销小,速度快。
TCP协议类似于打电话,必须先拨号,当对方收到铃声并接通电话,说一声 喂喂喂,才确认通信成功。可以交流。
如果网络有问题,会说,喂喂喂你还在吗?收不到回应就会挂电话,收到了才会继续通信。
UDP就像是 村里面的广播,不管你有没有准备好,也不管你有没有听到。播完就算了。
1.3 TCP 网络编程
1.3.1 客户端发送消息给服务端
以简单的例子看看是怎么用的。
1、先写服务端。
@Test
public void test2() {
// 服务端 接收数据并打印
ServerSocket serverSocket = null;
InputStream ips = null;
ByteArrayOutputStream baos = null;
Socket client = null;
try {
serverSocket= new ServerSocket(20000);
// 1、 接收客户端 套接字。 accept是阻塞方法。
client = serverSocket.accept();
// 2、客户端套接字获取 输入流
ips = client.getInputStream();
// 3、使用 ByteArrayOutputStream 流,来写数据,写到ByteArrayOutputStream内部的字节数组。不够会自动扩容。
// 可以全部接收完客户端发送的数据。最后再转字符串,避免出现乱码问题
baos = new ByteArrayOutputStream();
int len;
byte[] buf = new byte[1024];
while ((len=ips.read(buf)) != -1){
baos.write(buf, 0 , len);
}
System.out.println(baos);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (baos != null){
baos.close();
}
if (client!= null){
client.close();
}
if (ips != null){
ips.close();
}
if (serverSocket != null){
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2、再写客户端
@Test
public void test1() {
// 客户端 发送数据给服务端
OutputStream ops = null;
Socket socket = null;
try {
// 1、ip信息
InetAddress inet = InetAddress.getByName("127.0.0.1");
// 2、创建 包含 ip和端口的套接字对象
socket = new Socket(inet, 20000);
// 3、获取输出流
ops = socket.getOutputStream();
// 4、写数据
ops.write("你好,我是客户端".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5、关闭资源
try {
if (ops != null){
ops.close();
}
if (socket != null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
先启动服务端,再启动客户端即可测试。
1.3.2 客户端发送文件给服务端
-
以上只是客户端发送消息给服务端。再来看看客户端发送文件给服务端。服务端进行保存
@Test
public void test2() {
// 服务端 接收数据并打印
ServerSocket serverSocket = null;
InputStream ips = null;
FileOutputStream fos = null;
Socket client = null;
try {
serverSocket= new ServerSocket(20000);
// 1、 接收客户端 套接字
client = serverSocket.accept();
// 2、客户端套接字获取 输入流
ips = client.getInputStream();
// 3、使用 ByteArrayOutputStream 流,来写数据,写到ByteArrayOutputStream内部的字节数组。不够会自动扩容。
// 可以全部接收完客户端发送的数据。最后再转字符串,避免出现乱码问题
fos = new FileOutputStream("server_java.jpg");
int len;
byte[] buf = new byte[1024];
while ((len=ips.read(buf)) != -1){
fos.write(buf, 0 , len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null){
fos.close();
}
if (client!= null){
client.close();
}
if (ips != null){
ips.close();
}
if (serverSocket != null){
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
再写客户端:
@Test
public void test1() {
// 客户端 发送数据给服务端
OutputStream ops = null;
Socket socket = null;
FileInputStream fi = null;
try {
// 1、创建 包含 ip和端口的套接字对象
socket = new Socket(InetAddress.getByName("127.0.0.1"), 20000);
// 2、获取输出流
ops = socket.getOutputStream();
// 3、写数据
// 要发送文件,先要读进内存。
fi = new FileInputStream("java.jpg");
byte[] buf = new byte[1024];
int len;
while ((len = fi.read(buf)) != -1){
// 写出去
ops.write(buf, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5、关闭资源
try {
if (fi != null){
fi.close();
}
if (ops != null){
ops.close();
}
if (socket != null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
也是先运行服务端,再运行服务端。其实与消息的发送没有什么区别。
1.3.2 客户端发送文件给服务端,服务端发送收到了给客户端
前面都是例子都只是单向的通信,客户端发送给服务端。服务端没有发送给客户端。事实上是可以的
@Test
public void test2() {
// 服务端 接收数据并打印
ServerSocket serverSocket = null;
InputStream ips = null;
FileOutputStream fos = null;
Socket client = null;
try {
serverSocket= new ServerSocket(20000);
// 1、 接收客户端 套接字
client = serverSocket.accept();
// 2、客户端套接字获取 输入流
ips = client.getInputStream();
// 3、使用 ByteArrayOutputStream 流,来写数据,写到ByteArrayOutputStream内部的字节数组。不够会自动扩容。
// 可以全部接收完客户端发送的数据。最后再转字符串,避免出现乱码问题
fos = new FileOutputStream("server_java.jpg");
int len;
byte[] buf = new byte[1024];
while ((len=ips.read(buf)) != -1){
fos.write(buf, 0 , len);
}
// 4、 反馈给客户端
OutputStream os = client.getOutputStream();
os.write("我收到了,非常好!".getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null){
fos.close();
}
if (client!= null){
client.close();
}
if (ips != null){
ips.close();
}
if (serverSocket != null){
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
再写客户端:
@Test
public void test1() {
// 客户端 发送数据给服务端
OutputStream ops = null;
Socket socket = null;
FileInputStream fi = null;
ByteArrayOutputStream baos = null;
try {
// 1、创建 包含 ip和端口的套接字对象
socket = new Socket(InetAddress.getByName("127.0.0.1"), 20000);
// 2、获取输出流
ops = socket.getOutputStream();
// 3、写数据
// 要发送文件,先要读进内存。
fi = new FileInputStream("java.jpg");
byte[] buf = new byte[1024];
int len;
while ((len = fi.read(buf)) != -1){
// 写出去
ops.write(buf, 0, len);
}
// 关闭发送,表示这一次传输完成
socket.shutdownOutput();
// 4、 接收 客户端的反馈
InputStream is = socket.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buff = new byte[1024];
int len1;
while ((len1 = is.read(buff)) != -1){
baos.write(buff, 0, len1);
}
System.out.println(baos);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5、关闭资源
try {
if (baos != null){
baos.close();
}
if (fi != null){
fi.close();
}
if (ops != null){
ops.close();
}
if (socket != null){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意,客户端在发送图片之后,需要调用一下socket.shutdownOutput(); 表示传输结束,不然服务端不知道是否已传输完成。会阻塞。
启动还是先服务端,再客户端。
1.4 UDP 网络编程
发送端:
@Test
public void test1() {
// 发送端 发送数据给服务端
DatagramSocket socket = null;
try {
// 1、创建 UDP 套接字
socket = new DatagramSocket();
// 2、UDP的数据是放在 数据包里面。新建数据包
String data = "UDP数据";
byte[] bytes = data.getBytes(StandardCharsets.UTF_8);
InetAddress ip = InetAddress.getLocalHost();
// 数据包 指定 要发送的数据,目标的ip,目标的端口
DatagramPacket packet = new DatagramPacket(bytes, 0, bytes.length, ip, 9090);
// 2、发送数据
socket.send(packet);
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5、关闭资源
if (socket != null){
socket.close();
}
}
}
接收端:
@Test
public void test2() {
// 接收端 接收数据并打印
DatagramSocket socket = null;
try {
// 1、 接收端也是 DatagramSocket, 需要指定接收的端口号。
socket = new DatagramSocket(9090);
// 2、 用buf来存数据
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, 0, buf.length);
socket.receive(packet);
// 3、输出一下
System.out.println(new String(packet.getData(),0, packet.getLength()));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null){
socket.close();
}
}
}
UDP 没有服务端、客户端的概念。只有发送端和接收端。同一台主机,既可以是发送端,也可以是接收端。
上面的例子是一个充当发送,一个充当接收。先运行发送端不会报错,只是不能接收到数据。
要想接收到数据,还是要先运行接收端,再运行发送端。
1.5 URL 网络编程
URL (Uniform Resource Locator )统一资源定位地址
它表示Internet上某一个资源的地址。
URL 基本结构由 5个部分组成
传输协议://主机名:端口号/文件名#片段名?参数列表
比如下面这个:使用的是https协议。https是http的升级版。
https://fanyi.baidu.com/#en/zh/URL
URL 类
try {
URL url = new URL("https://baike.baidu.com/item/URL%E6%A0%BC%E5%BC%8F/10056474?fr=aladdin");
// 获取协议名
System.out.println(url.getProtocol());
// 获取主机名
System.out.println(url.getHost());
// 获取端口名
System.out.println(url.getPort());
// 获取文件路径
System.out.println(url.getPath());
// 获取文件名
System.out.println(url.getFile());
// 获取查询名
System.out.println(url.getQuery());
} catch (MalformedURLException e) {
e.printStackTrace();
}
// 输出
// https
// baike.baidu.com
// -1
// /item/URL%E6%A0%BC%E5%BC%8F/10056474
// /item/URL%E6%A0%BC%E5%BC%8F/10056474?fr=aladdin
// fr=aladdin
使用 URL 下载服务器资源 这里找的是百度上面的一张图片。可以自己在浏览器中打开看看。
https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimage.uc.cn%2Fs%2Fwemedia%2Fs%2Fupload%2F2018%2F424199fb84eb0adf50163cdd7ea424d3x1600x1000x104.jpeg&refer=http%3A%2F%2Fimage.uc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1633240502&t=9e446fc4aef58c515ae03fbebe350e6d
@Test
public void test1() {
FileOutputStream fo = null;
InputStream is = null;
HttpURLConnection conn = null;
try {
String tar = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fimage.uc.cn%2Fs%2Fwemedia%2Fs%2Fupload%2F2018%2F424199fb84eb0adf50163cdd7ea424d3x1600x1000x104.jpeg&refer=http%3A%2F%2Fimage.uc.cn&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1633240502&t=9e446fc4aef58c515ae03fbebe350e6d";
URL url = new URL(tar);
// 打开链接
conn = (HttpURLConnection) url.openConnection();
// 进行链接
conn.connect();
// 获取输入流
is = conn.getInputStream();
// 写到本地
fo = new FileOutputStream("lyf.jpg");
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf))!= -1){
fo.write(buf, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if ( is != null){
is.close();
}
if ( fo != null){
fo.close();
}
if (conn != null) {
conn.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}