网络相关概念
IP地址
- 概念:用于唯一标识网络中的每台计算机
- 查看IP地址,控制台 ipconfig
- IP地址表示形式:点分十进制 xx.xx.xx.xx
- 每个十进制数范围:0~255
- IP地址组成 = 网络地址 + 主机地址,比如 192.168.16.69
- IPv6是代替IPv4的下一代IP地址
- IPv4最大的问题是地址资源有限,所有就有了IPv6
- IPv4是4个字节32位 IPv6是16个字节128位
- IPv4地址分类
域名和端口号
域名
- www.baidu.com
- 好处:为了方便记忆,解决IP难记问题
- 概念:将IP地址映射成域名(HTTP协议)
端口号
- 概念:用于标识主机中某个特定的网络程序(例如百度主机中它有很多服务网站服务,邮件服务,Tomcat服务等各种服务),通过端口来监听每一个服务,防止访问发生错乱。
- 表示形式:以整数形式,范围0~65535
- 0~1024已被占用(自己最好不要用),比如 ssh 22 ,ftp 21 ,smtp 25 ,http 80
- 常见的网络程序端口号:tomcat:8080 mysql:3306 oracle:1521 sqlserver:1433
网络通讯协议
TCP/IP (Transmission Control Protocol/Internet Protocol)的简写,中文译名为传输控制协议/因特网互联协议,又叫网络通讯协议,这个协议是lnternet最基本的协议、Internet国际互联网络的基础,简单地说,就是由网络层的IP协议和传输层的TCP协议组成的
TCP协议:传输控制协议
- 使用TCP协议前,须先建立TCP连接,形成传输数据通道
- 传输前,采用"三次握手"方式,是可靠的
- TCP协议进行通信的两个应用进程:客户端、服务端
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低UDP协议:用户数据协议
UDP协议:用户数据协议
- 将数据、源、目的封装成数据包,不需要建立连接
- 每个数据报的大小限制在64K内,不适合传输大量数据
- 因无需连接,故是不可靠的
- 发送数据结束时无需释放资源(因为不是面向连接的),速度快
- 举例:厕所通知:发短信(给对方发消息,不用确定是否对方是否收到)
InetAddress
InetAddress是用于管理IP的类,没有构造器
1. 单例模式
2. 根据静态的方法来返回该对象
public class InetAddress_ {
public static void main(String[] args) throws UnknownHostException {
//获取本机的 InetAddress 对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);//设备名称/IP地址 ->WIN-MBL1NG3RUMG/192.168.176.1
//根据主机名,获取设备的 InetAddress 对象
InetAddress byName = InetAddress.getByName("WIN-MBL1NG3RUMG");
System.out.println(byName);
//根据域名返回 InetAddress 对象
InetAddress byName1 = InetAddress.getByName("www.baidu.com");
System.out.println(byName1);//www.baidu.com/14.215.177.39
//通过 InetAddress 对象 获取IP/域名
String address = byName1.getHostAddress();
String name = byName1.getHostName();
System.out.println(name);//www.baidu.com
System.out.println(address);//14.215.177.39
}
}
Socket
基本介绍
- 套接字(Socket)开发网络应用程序被广泛采用,以至于成为事实上的标准。
- 通信的两端都要有Socket,是两台机器间通信的端点。
- 网络通信其实就是Socket间的通信。
- Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
- 一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
TCP网络通讯编程
字节流传输
可靠性
服务端 自己去使用了本地的某个端口 如下代码:端口9999
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
//1.在本机的9999端口建立监听,等待连接
// 要求本机没有其他服务在监听9999端口
// ServerSocket 可以通过accept() 返回多个socket[多个客服端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端在9999端口监听等待连接...");
//2.如果没有客服端去连接9999端口时,程序会 阻塞,等待连接,如果有客服端连接,则会返回Socket对象,程序继续执行
Socket socket = serverSocket.accept();
System.out.println("服务端 Socket = " + socket.getClass());
//3.通过socket.getInputStream() 读取客服端在数据通道上的数据,显示
InputStream is = socket.getInputStream(); //如果读不到内容,也会阻塞
byte[] buff = new byte[1024];
int readLen = 0;
while ((readLen = is.read(buff)) != -1) {
System.out.println(new String(buff,0,readLen));
}
//4.反馈给客服端
OutputStream os = socket.getOutputStream();
os.write("hello client".getBytes());
socket.shutdownOutput(); //传输结束标记
//5.关闭流和socket
os.close();
is.close();
socket.close();
serverSocket.close();
System.out.println("服务端退出");
}
}
客户端 在去连接服务端的端口时,TCP/IP协议也会随机给客服端一个端口,当在传送比较大的文件时(传输时间长)在Java的Terminal控制台输入netstat -an | more 就可以看到一个和本地端口8888号对应的一个端口号。
public class SocketTCPClient {
public static void main(String[] args) throws IOException {
//1.连接服务端 连接InetAddress.getLocalHost()本机服务端这个9999端口
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客服端返回 Socket = "+socket.getClass());
//2.连接之后,生成Socket,通过socket.getOutputStream()得到和socket相关联的输出流对象
OutputStream os = socket.getOutputStream();
//3.通过输出流,写入数据到数据通道
os.write("hello server".getBytes());
socket.shutdownOutput(); //传输结束标记
//4.接受服务端反馈过来的信息
InputStream is = socket.getInputStream();
byte[] buff = new byte[1024];
int readLen = 0;
while ((readLen = is.read(buff)) != -1) {
System.out.println(new String(buff,0,readLen));
}
//关闭流对象和socket,必须关闭
is.close();
os.close();
socket.close();
System.out.println("客服端退出");
}
}
字符流传输
因为socket没有直接提供字符流的传输,如果想用字符流传输,可以通过转换流,将字节流转换成字符流再进行传输
服务端
public class SocketTCPServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999);
Socket socket = serverSocket.accept();
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//System.out.println(br.readLine()); //只能读单行字符串
String s = null;
while (!(s = br.readLine()).equals("bye")) {
System.out.println(s);
}
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write("hello client 字符流\nhello client 字符流\nbye");
bw.newLine();//用字符流就可以不用socket.shutdownOutput()标记但是前提是接收时一定要用readLine()接收
bw.flush();//字符流,一定要手动刷新,不然不会写入到数据通道
//5.关闭流和socket
bw.close();
br.close();
socket.close();
serverSocket.close();
System.out.println("服务端退出");
}
}
客户端
public class SocketTCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
OutputStream os = socket.getOutputStream();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
bw.write("hello server 字符流\nhello server 字符流\nbye");
bw.newLine();//用字符流就可以不用socket.shutdownOutput()标记但是前提是接收时一定要用readLine()接收
bw.flush();//字符流,一定要手动刷新,不然不会写入到数据通道
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//System.out.println(br.readLine());
String s = null;
while (!(s = br.readLine()).equals("bye")) {
System.out.println(s);
}
//关闭流对象和socket,必须关闭
br.close();
bw.close();
socket.close();
System.out.println("客服端退出");
}
}
问题?
System.out.println(br.readLine()); //这个可以读取一行数据
while ((s = br.readLine())!=null) { //但是想要读取多行数据,这个做法为啥不行
System.out.println(s);
}
原因
首先要明白什么叫流结束,对于读取文件流,读到文件末尾算结束,
但对于socket流,把某次写入到该流的字符读取完能认为该socket流结束吗?
不能,因为这个流还存在,即使流中已经没有数据,但仍然可以继续写出和读入,那么程序自然会继续读流,而流真正的结束就是关闭。
自我理解 待求证
应该是字符串读到最后一行应该不为null或者是不能读取到最后。 好像是关于IO的阻塞和非阻塞
解决办法 在文本的最后加一个字符串作为结束标记
while (!(s = br.readLine()).equals("bye")) {
System.out.println(s);
}
netstat指令
- netstat -an 可以查看当前主机网络情况,包括端口监听情况和网络连接情况
- netstat -an|more 使查看的内容分页 按空格键 显示下一页
- 要求在dos控制台下执行
ctrl+c 停止当前代码运行 - Listening 表示某个端口在监听
- 如果外部程序连接到该端口,就会显示一条连接信息
-
本地地址(IP:端口号) 表示本地这个IP对应的端口号
-
外部地址(IP:端口号) 表示外部连接到本地地址的IP和对应的端口号
-
状态 表示监听的状态
-
用管理员启动的dos控制台输入netstat -anb|more 还可以查看是什么在监听这个端口
UDP网络通讯编程(了解)
基本介绍
- 类 DatagramSocket和 DatagramPacket[数据包|数据报]实现了基于UDP协议网络程序。
- UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
- DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号。
- UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和接收方的连接。
基本流程
案例
发送端 也可以说是接收端
public class UDPSenderB {
public static void main(String[] args) throws IOException {
//1.创建一个DatagramSocket对象,准备发送和接收数据
DatagramSocket dgs = new DatagramSocket(9998);
//2.将要发送的数据,封装到DatagramPacket对象
byte[] bytes = "hello world".getBytes();//InetAddress.getLocalHost()不推荐用这个因为,此时能用是是因为发送和接收在一台机子上面
DatagramPacket dgp = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.159.1"), 9999);
dgs.send(dgp);
//3.接收数据
byte[] buff = new byte[1024];
dgp = new DatagramPacket(buff, buff.length);
dgs.receive(dgp);
int length = dgp.getLength(); //接收到数据的字节长度
bytes = dgp.getData();
String s = new String(bytes, 0, length);
System.out.println(s);
//3.关闭资源
dgs.close();
System.out.println("B退出");
}
}
接收端 也可以说是发送端
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1.创建一个DatagramSocket对象,准备在9999端口接收数据
DatagramSocket dgs = new DatagramSocket(9999);
//2.构建一个DatagramPacket对象,准备接收数据 UDP数据包大小为64K
byte[] bytes = new byte[1024]; //64*1024
DatagramPacket dgp = new DatagramPacket(bytes, bytes.length);
//3.调用 接收方法 通过网络传输的DatagramPacket来填充DatagramSocket对象
// 当有数据包发送到本机9999端口,就会接收到数据,没有发送,就会阻塞等待
dgs.receive(dgp);
//4.把dgs进行拆包,取出数据,并显示
int length = dgp.getLength(); //接收到数据的字节长度
bytes = dgp.getData();
String s = new String(bytes, 0, length);
System.out.println(s);
//5.发送信息回去
bytes = "你好 世界".getBytes();//InetAddress.getLocalHost()不推荐用这个因为,此时能用是是因为发送和接收在一台机子上面
dgp = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("192.168.159.1"), 9998);
dgs.send(dgp);
//关闭资源
dgs.close();
System.out.println("A退出");
}
}