网络编程
1.网络编程的基本常识
目前主流的网络通信软件:QQ,微信,MSN,...
2.七层协议
ISO(国际标准委员会组织),将数据的传输从逻辑上划分了以下七层
应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
当发送消息时,需要按照上述从前向后的次序对发送的内容进行层层加包,然后发送过去
当接收消息时,需要按照上述相反的次序对接收到的内容层层拆包,然后再解析出来
3.常见的协议
http协议-超文本传输协议,在浏览网站的时候使用该协议
ftp协议-文件传输协议,上传,下载文件的时候使用该协议
tcp协议-传输控制协议,主要是一种面向连接的协议,比如说类似于打电话,一般网络通信的时候需要使用该协议
udp协议-用户数据报协议,是一种面向非链接的协议,类似于写信或者发短信,网络通信时也使用该协议
ip协议-互联网协议,是上述协议的底层协议
协议-就是一种约定/规则。是通信双方需要去遵循的一种机制
4.网络通信三要素
案例:比如说,我要和你说话,首先
第一个条件就是找到你的位置
第二个条件就是你要有接受数据的耳朵
第三个条件就是我和你说话你能够接收到,按照什么方式接收,说的是什么语言
IP:
InetAddress:此类表示互联网协议(IP)地址
IP地址的底层是32位组成的整数IPv4,也有128位二进制组成的整数IPv6
端口:
IP地址 - 可以定位到具体的一台设备
端口号 - 可以定位到设备上具体进程
网络编程中需要提供:IP地址+端口号已经被系统占用,因此编程需要从1025开始使用
端口号:本质上是16位二进制组成的整数,范围是:0-65535,其中0-1024之间
协议:
TCP:
面向连接、安全可靠、效率稍低、通过三次握手建立连接
举例:
下载、打电话、QQ聊天
编程模型:
服务器端:
1.创建服务器socket服务。通过ServerSocket对象
2.服务器必须对外提供一个端口,否则客户端无法连接
3.获取连接过来的客户端对象
4.通过客户端对象获取socket流读取客户端发来的数据打印
5.关闭资源、关闭流、关闭服务器
客户端:
1.创建TCP客户端socket服务,使用的是socket对象
2.如果连接成功,说明数据传输通过建立
通过就是socket流,是底层建立好的
是流就应该有输入或者输出
想要获取输入或者输出对象,可以找socket获取
可以通过getOutputStream()和getInputStream()来获取两字字节流
3.使用输入流,将数据写入
4.关闭资源
UDP:
面向无连接、不可靠、速度快、将数据封装包传输,数据包最大64k
举例:
发短信、在线视频
//1.获取本机主机地址
InetAddress inetAddress = InetAddress.getLocalHost();
//自动调用toString();字符串格式:主机名/IP地址
System.out.println(inetAddress);
//拆分地址信息,分别获取主机名和IP地址 分别打印
System.out.println(inetAddress.getHostName());
System.out.println(inetAddress.getHostAddress());
//2.获取指定主机名中的地址信息
InetAddress byName = InetAddress.getByName("DESKTOP-K7TP4JH");
System.out.println(byName);
System.out.println(inetAddress.getHostName());
System.out.println(inetAddress.getHostAddress());
5.Socket套接字
Socket套接字:
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能标识符套接字
Socket原理机制:
通信的两端都有Socket
网络通信其实就是Socket间的通信
数据在两个Socket间通过IO传输
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZuRTNf5z-1580969373981)(C:\Users\王龙涛\AppData\Roaming\Typora\typora-user-images\image-20200202174654642.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ORb84tkU-1580969373985)(C:\Users\王龙涛\AppData\Roaming\Typora\typora-user-images\image-20200202175534151.png)]
6.相关类的方法解析
ServerSocket类
ServerSocket(int port)
accept():用于监听并接收到服务器套接字的连接请求
close():关闭套接字
Socket类
getInputStream:用户获取此套接字的输入流
getOutputStream:用户获取此套接字的输出流
close:用户关闭套接字
7.编程模型代码
服务器端
//1.创建服务器socket服务。通过ServerSocket对象
//2.服务器必须对外提供一个端口,否则客户端无法连接
ServerSocket serverSocket = new ServerSocket(8888);
//3.获取连接过来的客户端对象
Socket socket = serverSocket.accept();
//4.通过客户端对象获取socket流读取客户端发来的数据打印
BufferedReader bufferedReader = new BufferedReader(newInputStreamReader(socket.getInputStream()));
String readLine = bufferedReader.readLine();
//5.关闭资源、关闭流、关闭服务器
socket.close();
客户端
//1.创建TCP客户端socket服务,使用的是socket对象,指定IP地址和端口号
socket = new Socket("localhost", 8888);
//2.如果连接成功,说明数据传输通道建立,通道就是socket流,是底层建立好的,是流就应该有输入或者输出,想要获取输入或者输出对象,可以找socket获取,可以通过getOutputStream()和getInputStream()来获取两字字节流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//3.使用输入流,将数据写入
bufferedWriter.write("你好,服务器");
//4.关闭资源
bufferedWriter.flush();
bufferedWriter.close();
完整代码
/*Server.java*/
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(10006);
System.out.println("等待客户端连接");
//等待客户端连接,使用accept()
Socket socket = serverSocket.accept();
System.out.println("客户端连接成功");
//使用字符缓冲输入流接收客户端消息
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readLine = bufferedReader.readLine();
System.out.println("客户端发来的消息是:"+readLine);
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/*Client.java*/
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 10006);
//建立连接之后,通过socket中的IO流进行数据传输
//向服务器发送 你好,服务器
//如果想要使用字符流需要使用OutputStreamWriter/InputStreamWriter 转换流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("你好,服务器");
bufferedWriter.flush();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行结果
等待客户端连接
客户端连接成功
客户端发来的消息是:你好,服务器
8.TCP传输容易出现的问题
客户端连接上服务器,两端都在等待,没有任何数据传输。
通过例程分析:因为read方法或者readLine方法时阻塞式。
解决方法:
1.自定义结束标记
2.使用shutdownInput,shutdownOutput方法。
在上述代码之中添加如下字段之后将会出现阻塞
//在Server之中添加如下字段
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("客户端,你好呀");
bufferedWriter.newLine();
bufferedReader.close();
bufferedWriter.close();
//在Client之中添加如下字段
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readLine = bufferedReader.readLine();
System.out.println("服务器端发来的消息是:"+readLine);
上述代码中,Client想要获得服务器发来的消息,Server也想要获得客户端发来的消息,因此出现了阻塞现象
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(10006);
System.out.println("等待客户端连接");
//等待客户端连接,使用accept()
Socket socket = serverSocket.accept();
System.out.println("客户端连接成功");
//使用字符缓冲输入流接收客户端消息
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readLine = bufferedReader.readLine();
System.out.println("客户端发来的消息是:"+readLine);
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("客户端,你好呀");
bufferedWriter.newLine();
bufferedReader.close();
bufferedWriter.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
-----------------------------------------------------------------------------------------
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 10006);
//建立连接之后,通过socket中的IO流进行数据传输
//向服务器发送 你好,服务器
//如果想要使用字符流需要使用OutputStreamWriter/InputStreamWriter 转换流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bufferedWriter.write("你好,服务器");
bufferedWriter.flush();
//bufferedWriter.shutdownOutput();添加该行则不出现阻塞
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readLine = bufferedReader.readLine();
System.out.println("服务器端发来的消息是:"+readLine);
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
9.基于TCP上传文件
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("等待客户端上传文件");
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
//创建字节输出流向指定服务器中输入用户上传的文件
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("6.md"));
BufferedInputStream bufferedInputStream = new BufferedInputStream(socket.getInputStream());
//创建缓冲区
byte[] by = new byte[1024*8];
int num = 0 ;
while ((num=bufferedInputStream.read())!=-1) {
bufferedOutputStream.write(by,0,num);
bufferedOutputStream.flush();
}
bufferedInputStream.close();
bufferedOutputStream.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
-----------------------------------------------------------------------------------------
public class Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost",8888);
//读取本地文件
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("C://Users//王龙涛//Desktop//Java笔记//泛型.md"));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(socket.getOutputStream());
//创建缓冲区
byte[] by = new byte[1024*8];
int num = 0 ;
//读取本地文件
while ((num=bufferedInputStream.read())!=-1) {
bufferedOutputStream.write(by,0,num);
bufferedOutputStream.flush();
}
bufferedInputStream.close();
bufferedOutputStream.close();
socket.close();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
10.TCP传输练习
-
要求客户端发送的内容由用户手动输入,使用
BufferedReader
类 -
要求服务器收到客户端的消息之后,想客户端回发消息
"Receive!"
-
要求服务器和客户端可以不断地进行通信,当客户端发送
bye
时结束通信 -
要求服务能够同时支持多个客户端的连接,而且能够和多个客户端同事聊天,多线程
Server.java
public class Server {
public static void main(String[] args) {
try {
//1.创建ServerSocket类型的对象,并且绑定端口
ServerSocket serverSocket = new ServerSocket(8999);
//等待客户端的连接请求
Socket socket = serverSocket.accept();
//用来接收客户端发来的内容
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//向客户端发送字符串内容,"I receive"
PrintStream printStream = new PrintStream(socket.getOutputStream());
while (true) {
String string = bufferedReader.readLine();
if ("bye".equalsIgnoreCase(string)) {
System.out.println("客户端"+socket.getInetAddress()+"已经下线");
break;
}
System.out.println("客户端发来的消息是:"+string);
printStream.println("I Receive");
}
printStream.close();
bufferedReader.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Client.java
public class Client {
public static void main(String[] args) {
try {
//创建Socket类型的对象,并且提供IP地址和端口号
Socket socket = new Socket("localhost",8999);
//使用输入输出流进行通信
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
//用来接收服务端发来的消息
BufferedReader bufferedReader2 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream printStream = new PrintStream(socket.getOutputStream());
while (true) {
System.out.println("请输入要发送的内容:");
String string = bufferedReader.readLine();
printStream.println(string);
System.out.println("成功发送数据到服务器");
if("bye".equalsIgnoreCase(string)) break;
String string2 = bufferedReader2.readLine();
System.out.println("服务器发来的消息是:"+string2);
}
bufferedReader2.close();
bufferedReader.close();
printStream.close();
socket.close();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
上述代码实现了一对一通信,下面代码对上述代码进行更改,实现多对多进行通信
Server.java
public class Server {
public static void main(String[] args) {
try {
//1.创建ServerSocket类型的对象,并且绑定端口
ServerSocket serverSocket = new ServerSocket(8999);
while (true) {
System.out.println("等待客户端连接...");
//等待客户端连接请求
Socket socket = serverSocket.accept();
System.out.println("客户端:"+socket.getInetAddress()+"连接成功");
//只要有客户端连接成功,应该启动一个新线程为之服务,主线程始终接待
new ServerThread(socket).start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Client.java
public class Client {
public static void main(String[] args) {
try {
//创建Socket类型的对象,并且提供IP地址和端口号
Socket socket = new Socket("localhost",8999);
//使用输入输出流进行通信
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
//用来接收服务端发来的消息
BufferedReader bufferedReader2 = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintStream printStream = new PrintStream(socket.getOutputStream());
while (true) {
System.out.println("请输入要发送的内容:");
String string = bufferedReader.readLine();
printStream.println(string);
System.out.println("成功发送数据到服务器");
if("bye".equalsIgnoreCase(string)) break;
String string2 = bufferedReader2.readLine();
System.out.println("服务器发来的消息是:"+string2);
}
bufferedReader2.close();
bufferedReader.close();
printStream.close();
socket.close();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch
e.printStackTrace();
}
}
}
ServerThread.java
public class ServerThread extends Thread{
private Socket socket;
public ServerThread(Socket socket) {
this.socket=socket;
}
@Override
public void run() {
try {
} catch (Exception e) {
e.printStackTrace();
}
}
}
11.UDP编程
DatagramSocket
与DatagramPacket
- 建立发送端,接收端
- 调用Socket的发送接收方法
- 关闭Socket
- 发送端与接收端是两个独立的运行程序
UDP编程模型:
主机A(接收方):
1.创建DatagramSocket类型的对象,并且提供端口号
2.创建DatagramPacket类型的对象,用于接收发送过来的数据
3.使用上述的对象接收内容,使用receive()方法
4.关闭相关资源
主机B(发送方):
1.创建DatagramSocket类型的对象
2.创建DatagramPacket类型的对象,并且提供端口号和IP地址
3.使用上述对象发送数据内容,使用send()方法
4.关闭相关资源
DatagramSocket:是用于创建接收和发送数据报的套接字
DatagramPacket():无参构造
DatagramSocket(int port):创建套接字并且绑定端口号
void receive(DatagramPacket p):用于接收数据并且放到参数指定的数据报之中
void send():将参数指定的数据报发送出去
void close():关闭套接字
DatagramPacket:用于描述数据报的内容
DatagramPacket(byte[] buf,int length)
构造DatagramPacket,用来接收长度为length的数据报
DatagramPacket(byte[] buf,int length,InetAddress address,int port)
构造数据报包,用来将长度为length的包发送到指定主机的指定端口号
InetAddress getAddress() - 用于获取发送方/接收方IP地址信息
int getPort() - 用于获取发送方/接收方的端口号信息
int getLength() - 用于获取数据报中数据的长度
发送方代码
//1.创建DatagramSocket类型的对象
DatagramSocket datagramSocket = new DatagramSocket();
//2.创建DatagramPacket类型的对象,提供接收方的端口号和IP地址
byte[] data = "hello".getBytes();
InetAddress inetAddress = InetAddress.getLocalHost();
DatagramPacket datagramPacket = new DatagramPacket(data, data.length, inetAddress, 8888);
//3.发送数据内容,使用send()方法
datagramSocket.send(datagramPacket);
System.out.println("成功发送数据内容");
//4.关闭套接字
datagramSocket.close();
接收方代码
//1.创建DatagramSocket类型的对象,并提供端口号
DatagramSocket datagramSocket = new DatagramSocket(8888);
//2.创建DatagramPacket类型的对象,提供接收数据内容
byte[] data = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
//3.接收数据并保存到数据报中,使用receive()方法
datagramSocket.receive(datagramPacket);
System.out.println("接收到的数据是:"+new String(data,0,data.length)+"!");
//4.关闭套接字
datagramSocket.close();
12.TCP协议和UDP协议的比较
TCP是一种面向连接协议,类似于打电话,是一种全双工字节流通信方式,服务器压力比较大,资源消耗高,发送数据效率低
UDP是一种全双工的数据报通信方式,服务器压力比较小,资源消耗小,发送数据效率相对高
//4.关闭套接字
datagramSocket.close();
**接收方代码**
```java
//1.创建DatagramSocket类型的对象,并提供端口号
DatagramSocket datagramSocket = new DatagramSocket(8888);
//2.创建DatagramPacket类型的对象,提供接收数据内容
byte[] data = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(data, data.length);
//3.接收数据并保存到数据报中,使用receive()方法
datagramSocket.receive(datagramPacket);
System.out.println("接收到的数据是:"+new String(data,0,data.length)+"!");
//4.关闭套接字
datagramSocket.close();
12.TCP协议和UDP协议的比较
TCP是一种面向连接协议,类似于打电话,是一种全双工字节流通信方式,服务器压力比较大,资源消耗高,发送数据效率低
UDP是一种全双工的数据报通信方式,服务器压力比较小,资源消耗小,发送数据效率相对高