2020.6.8 课堂笔记
网络编程
网络编程三要素
- IP地址:网络中设备的唯一标识,可用主机名。
- 端口号:用于标识进程的逻辑地址,区分不同应用程序之间的标识。(端口范围0-65535,0-1023被系统资源占用)。
- 端口"是0到65535之间的一个整数,正好16个二进制位。0到1023的端口被系统占用,用户只能选用大于1023的端口。不管是浏览网页还是在线聊天,应用程序会随机选用一个端口,然后与服务器的相应端口联系。
- 传输协议:通讯的规则TCP/UDP协议。
- UDP:特点:
- 通信两端不需要建立链接,因为不需要建立连接,效率高。属于不可靠协议,可能会发生数据丢失。
- 发送数据包的大小限制在64K。
- TCP特点:
- 发送段和接受端需要建立链接,可靠协议。
- 无大小限制。
- UDP:特点:
- 获取主机名hostname
IP地址概述
- 规定网络地址的协议,叫做IP协议。它所定义的地址,就被称为IP地址。
- 目前,广泛采用的是IP协议第四版,简称IPv4。这个版本规定,网络地址由32个二进制位(4位)组成。习惯上,我们用分成四段的十进制数表示IP地址,从0.0.0.0一直到255.255.255.255。
- IP地址由两部分组成,前面是网络地址,后面是主机地址。处于同一个子网络下的的IP地址,网络地址都相同。主机地址有区别。
- IP地址 = 网络地址+主机地址
- A类IP地址:第一段号码为网络地址,剩下的三段号码为本地计算机的号码
- B类IP地址:前二段号码为网络地址,剩下的二段号码为本地计算机的号码
- C类IP地址:前三段号码为网络地址,剩下的一段号码为本地计算机的号码
- IP地址 = 网络地址+主机地址
- 我们怎么判断,是否属于同一个字网络?
- 为了解决这个问题就出现了子网络掩码,将两个IP地址与子网络掩码进行(&)位运算,两个运算结果相同就是同一个字网络,不相同就不是同一个子网络。
- 子网络掩码就是:将网络地址的二进制全给成1,主机地址全给成0(子网络掩码就是11111111.11111111.11111111.00000000),写成十进制就是255.255.2555.0,这个就是子网络掩码。知道这个我们就可以判断了。
- IP的作用:就是为每一台电脑分配一个IP,然后确定哪些IP在同一个子网络下。
InetAddress
-
为了我们对ip的操作,Java提供的类,可以在里面获取ip和主机名
-
当我们知道主机名时,通过主机名获取IP地址
- getByName(String host):根据主机名获取IP地址。
-
知道IP地址时,通过IP地址获取主机名
- getByName(String host):根据IP获取主机名。
-
不知道IP或者主机名时
- getLocalHost():返回主机名和IP地址
- 也可以通过DOS命令获取。
-
DOS 命令 意义 net view 获取局域网中的所有主机名 ipconfig -all 获取本地IP,主机名,MAC地址 arp -a 获取本局域网中的所有IP地址和物理地址 ping -a x.x.x.x 获取x.x.x.x的主机名 nbtstat -a 主机名 获取MAC地址
代码:
package demo6;
/*Author:LH
CreatTime:2020.06.16.16:22*/
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Test {
public static void main(String[] args) throws UnknownHostException {
// 根据主机名获取IP地址
InetAddress name = InetAddress.getByName("DESKTOP-ITO7TPJ");
System.out.println(name);
// 获取IP地址
System.out.println(name.getHostAddress());
System.out.println("===========");
// 根据主机名获取所有的IP地址
InetAddress[] allByName = InetAddress.getAllByName("DESKTOP-ITO7TPJ");
for (InetAddress inetAddress : allByName) {
System.out.println(inetAddress);
}
System.out.println("===========");
// 返回本地主机名和地址
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);
System.out.println("===========");
// 根据IP地址获取主机名
InetAddress address = InetAddress.getByName("192.168.1.105");
System.out.println(address.getHostName());
}
}
/*
结果
DESKTOP-ITO7TPJ/192.168.1.105
192.168.1.105
===========
DESKTOP-ITO7TPJ/192.168.1.105
DESKTOP-ITO7TPJ/fe80:0:0:0:99ec:fbf4:eb1b:d19d%5
===========
DESKTOP-ITO7TPJ/192.168.1.105
===========
DESKTOP-ITO7TPJ*/
Socket编程
-
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
-
要求:
-
通信两端两端都要有socket
-
数据在两个Socket间通过IO传输。
-
网络通信其实就是Socket之间通信
-
DUP协议编程
- DatagramSocket:此类表示用于发送和接收数据报数据包的套接字。采用的是DUP通信协议。
构造方法 | 功能 |
---|---|
DatagramSocket() | 构造数据报套接字并将其绑定到本地主机上的任何可用端口。 |
DatagramSocket(int port) | 构造数据报套接字并将其绑定到本地主机上的指定口。 |
DatagramSocket(int port, InetAddress laddr) | 创建一个数据报套接字,绑定到指定的本地地址。 |
- 常用方法:
方法 | 功能 |
---|---|
close() | 关闭套接字 |
getInetAddress() | 返回此套接字连接到的地址。 |
getLocalAddress() | 获取套接字所绑定的本地地址。 |
getLocalPort() | 返回此套接字绑定到的本地主机上的端口号。 |
getPort() | 返回此套接字连接到的端口号。 |
数据报包类
-
DatagramPacket :数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
-
为了传送数据,数据报包就要封装(数据,数据长度,IP地址,端口),封装完才能正确传输。
-
构造方法:
-
DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length偏移量为 offset的包发送到指定主机上的指定端口号。
-
DatagramPacket(byte[] buf, int offset, int length, SocketAddress address)
构造数据报包,用来将长度为
length
偏移量为offset
的包发送到指定主机上的指定端口号。 -
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
构造数据报包,用来将长度为 length的包发送到指定主机上的指定端口号。
-
-
代码:
package demo1;
/*Author:LH
CreatTime:2020.06.13.10:14*/
//UDP协议接受和发送数据
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
//服务端套接字编程
public class UDPCilent {
public static void main(String[] args) throws IOException {
// 服务端是接受数据,因此要暴露自己的端口
DatagramSocket socket = new DatagramSocket(8888);
byte[] bytes = new byte[1024];
// 创建一个数据包,将指定长度的数据接收到字节数组中
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
System.out.println("等待接收");
// 接收数据报包
socket.receive(packet);
// 获取数据报包里面的数据
byte[] data = packet.getData();
String s = new String(data);
System.out.println(s);
}
}
package demo1;
/*Author:LH
CreatTime:2020.06.13.10:24*/
//UDP协议接受和发送数据
import java.io.IOException;
import java.net.*;
//客户端UDP套接字编程
//客户端数发送数据,因此需要封装接收端IP和端口
public class UDPServe {
public static void main(String[] args) throws IOException {
InetAddress ip = InetAddress.getByName("DESKTOP-ITO7TPJ");
System.out.println(ip);
DatagramSocket socket = new DatagramSocket();
byte[] bytes = "你好,UDP测试".getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.3.6"), 8888);
socket.send(packet);
socket.close();
}
}
使用线程开启
package demo22;
/*Author:LH
CreatTime:2020.06.13.13:56*/
//使用线程开启网络编程
public class Test {
public static void main(String[] args) {
new MyCilentThread().start();
new MyServertThread().start();
}
}
package demo22;
/*Author:LH
CreatTime:2020.06.17.9:35*/
import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class MyCilentThread extends Thread {
@Override
public void run() {
try {
DatagramSocket socket = new DatagramSocket();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String s = reader.readLine();
byte[] bytes =s.getBytes();
// 封装接收端的ip和端口
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.3.6"), 9909);
socket.send(packet);
if ("再见".equals(s)){
break;
}
}
socket.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package demo22;
/*Author:LH
CreatTime:2020.06.17.9:42*/
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class MyServertThread extends Thread {
@Override
public void run() {
try {
// 端口号只能暴露一次,放在循环中就会报端口号被占用的异常
DatagramSocket socket = new DatagramSocket(9909);
while (true) {
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 暴露自己本地端口
System.out.println("等待接收");
socket.receive(packet);
byte[] data = packet.getData();
String hostName = packet.getAddress().getHostName();
String s = new String(data);
System.out.println(hostName+"给你发来消息:"+s);
if ("再见".equals(s)) {
break;
}
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端与服务器聊天模式
package demo25;
/*Author:LH
CreatTime:2020.06.17.11:34*/
//和服务器对话模式,
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class MyCilentTest {
public static void main(String[] args) {
new MyCilentRevecie().start();
new MyCilentSend().start();
}
}
class MyCilentSend extends Thread {
@Override
public void run() {
try {
DatagramSocket socket = new DatagramSocket();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String s = reader.readLine();
byte[] bytes = s.getBytes();
// 封装接收端的ip和端口
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.3.6"), 9909);
socket.send(packet);
if ("再见".equals(s)) {
break;
}
}
socket.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class MyCilentRevecie extends Thread{
@Override
public void run() {
try {
// 端口号只能暴露一次,放在循环中就会报端口号被占用的异常
DatagramSocket socket = new DatagramSocket(9908);
System.out.println("等待接收");
while (true) {
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 暴露自己本地端口
socket.receive(packet);
byte[] data = packet.getData();
String hostName = packet.getAddress().getHostName();
String s = new String(data);
System.out.println(hostName+"给你发来消息:"+'\n'+s);
if ("再见".equals(s)) {
break;
}
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package demo25;
/*Author:LH
CreatTime:2020.06.17.11:39*/
//和客户端对话模式,
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class MyServerTest {
public static void main(String[] args) {
new MyServerReverice().start();
new MyServerSend().start();
}
}
class MyServerSend extends Thread{
@Override
public void run() {
try {
DatagramSocket socket = new DatagramSocket();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String s = reader.readLine();
byte[] bytes = s.getBytes();
// 封装接收端的ip和端口
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.3.6"), 9908);
socket.send(packet);
if ("再见".equals(s)) {
break;
}
}
socket.close();
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class MyServerReverice extends Thread{
@Override
public void run() {
try {
// 端口号只能暴露一次,放在循环中就会报端口号被占用的异常
DatagramSocket socket = new DatagramSocket(9909);
System.out.println("等待接收");
while (true) {
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
// 暴露自己本地端口
socket.receive(packet);
byte[] data = packet.getData();
String hostName = packet.getAddress().getHostName();
String s = new String(data);
System.out.println(hostName+"发来消息:"+'\n'+s);
if ("再见".equals(s)) {
break;
}
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
TCP协议编程
-
Socket客户端编程的套接字
-
ServeSocket服务端套接字
-
端口被占有异常
-
读取文件时,读取通道数据结束标记的确定
- 方案1:手写标记,如果文件中有这个标记,就可能造成文件的不完整。
- 方案2:就是客户端把输出流用完之后关闭,服务端那边就知道输出结束了,就不再等待了。调用shutdownOutPut()方法结束输出流。
-
TCP协议编程的步骤
- 发送数据:
- 创建客户端套接字对象
- 获取输出流
- 输出数据
- 释放资源
- 接收数据
- 创建服务端套接字对象
- 监听客户端
- 获取输入流对象
- 读取数据
- 释放资源
- 发送数据:
-
由于TCP协议传输数据时,两端必须建立连接,因此要先打开服务端,然后再开启客户端,否则会报异常Exception in thread “main” java.net.ConnectException: Connection refused: connect
-
在客户端和服务端连接上之后,没有数据的传输,因为read方法和readline方法都是阻塞方法。没有读取到就一直等待。
package demo27;
/*Author:LH
CreatTime:2020.06.17.14:16*/
//客户端发送消息,服务端给出反馈
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class MyCilent {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("192.168.3.6",5555);
// 通过输出流将数据传输到服务端
OutputStream out = socket.getOutputStream();
out.write("你好,服务端".getBytes());
// 获取服务端的反馈
byte[] bytes = new byte[1024];
// 创建输入流,将数据传送给客户端
InputStream in = socket.getInputStream();
in.read(bytes);
String s = new String(bytes);
System.out.println(s);
socket.shutdownInput();
socket.shutdownOutput();
socket.close();
}
}
package demo27;
/*Author:LH
CreatTime:2020.06.17.14:34*/
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) throws IOException {
ServerSocket socket = new ServerSocket(5555);
Socket accept = socket.accept();
InputStream in = accept.getInputStream();
byte[] bytes = new byte[1024];
in.read(bytes);
String s = new String(bytes);
System.out.println(s);
// 给客户端一个反馈信息
OutputStream out = accept.getOutputStream();
out.write("接受完毕".getBytes());
out.close();
socket.close();
}
}
package demo28;
/*Author:LH
CreatTime:2020.06.17.15:01*/
import java.io.*;
import java.net.Socket;
//文本文件的上传到服务器,服务器保存到文件
public class MyCilent {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("192.168.3.6", 7777);
OutputStream out = socket.getOutputStream();
// 创建一个字符流,将文件通信到服务端
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
// 读取文件
BufferedReader reader = new BufferedReader(new FileReader("a.md"));
String s = null;
while ((s = reader.readLine()) != null) {
writer.write(s);
writer.flush();
}
// 这的输出流关闭是个重点,因为,读取文件的时候,可以读到null,能判断到结束,但是服务器从输出流是读取不到null的,因此需要关闭字符输出流,来告诉服务器,你已经传数据结束了.
socket.shutdownOutput();
// 接收复制完毕的消息
InputStream in = socket.getInputStream();
byte[] bytes = new byte[1024];
in.read(bytes);
String s1 = new String(bytes);
System.out.println(s1);
reader.close();
out.close();
in.close();
socket.close();
}
}
package demo28;
/*Author:LH
CreatTime:2020.06.17.15:16*/
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Myserver {
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(7777);
Socket accept = socket.accept();
InputStream in = accept.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
BufferedWriter writer = new BufferedWriter(new FileWriter("a-new.md"));
String s;
// 其实这里服务器端从输出流中是读取不到null的,所以这个就是个死循环,必须读取到结束标识,来结束读取
// 一般会使用客户端的方法,关闭输出流,来告诉服务端,数据传输结束.
while ((s=reader.readLine())!=null){
writer.write(s);
writer.flush();
}
// 将接收完毕的消息传回去
OutputStream out = accept.getOutputStream();
out.write("接收完毕".getBytes());
out.close();
socket.close();
}
}
一个服务器连接多个客户端
- 循环侦探客户端
代码
package demo29;
/*Author:LH
CreatTime:2020.06.17.18:49*/
//循环监听客户端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Myserver {
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(45454);
int i=0;
while (true){
Socket accept = socket.accept();
System.out.println("第"+(++i)+"个客户端进来了");
new MyServerThread(accept,socket,i).start();
}
}
}
class MyServerThread extends Thread{
private Socket accept;
private ServerSocket socket;
private int i;
public MyServerThread(Socket accept,ServerSocket socket,int i) {
this.accept = accept;
this.socket = socket;
this.i = i;
}
@Override
public void run() {
try {
InputStream in = accept.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
BufferedWriter writer = new BufferedWriter(new FileWriter(i+"a-new.md"));
String s;
while ((s=reader.readLine())!=null){
writer.write(s);
writer.flush();
}
// 将接收完毕的消息传回去
OutputStream out = accept.getOutputStream();
out.write("接收完毕".getBytes());
out.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package demo28;
/*Author:LH
CreatTime:2020.06.17.15:01*/
import java.io.*;
import java.net.Socket;
//文本文件的上传到服务器,服务器保存到文件
public class MyCilent {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("192.168.3.6", 45454);
OutputStream out = socket.getOutputStream();
// 创建一个字符流,将文件通信到服务端
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out));
// 读取文件
BufferedReader reader = new BufferedReader(new FileReader("a.md"));
String s = null;
while ((s = reader.readLine()) != null) {
writer.write(s);
writer.flush();
}
// 这的输出流关闭是个重点,因为,读取文件的时候,可以读到null,能判断到结束,但是服务器从输出流是读取不到null的,因此需要关闭字符输出流,来告诉服务器,你已经传数据结束了.
socket.shutdownOutput();
// 接收复制完毕的消息
InputStream in = socket.getInputStream();
byte[] bytes = new byte[1024];
in.read(bytes);
String s1 = new String(bytes);
System.out.println(s1);
reader.close();
out.close();
in.close();
socket.close();
}
}