文章目录
网络编程
软件的架构:
B/S:
brower(浏览器) Server(服务器)
javaWeb tomcat
C/S:
Client(客户端) Server
有独立的客户端:qq 微信 大型游戏
网络通信协议
TCP/IP协议参考模型
OSI参考模型 | TCP/IP参考模型 | TCP/IP参考模型各层对应协议 |
---|---|---|
应用层 | 应用层 | HTTP、FTP、Telent、DNS…… |
表示层 | ||
会话层 | ||
传输层 | 传输层 | TCP、UDP…… |
网络层 | 网络层 | IP、ICMP、ARP…… |
数据链路层 | 物理+数据链路层 | Link |
物理层 |
- 上表,OSI参考模型:过于理想,未能在因特网广泛推广,TCP/IP参考模型(或TCP/IP协议):事实上的国际标准
TCP与UDP协议
- TCP:面向连接的(安全的,可靠的)
- UDP:非面向连接的(不安全的)
一次最多传输64k的消息
分为一个一个数据包 - 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠
- 第一次握手,客户端向服务器端发送连接请求,等待服务器确认
- 第二次握手,服务器端向客户端会送一个相应,通知客户端收到连接请求
- 第三次握手,客户端再次向服务器端发送确认信息,确认连接
- 四次挥手:TCP协议中,在发送数据结束后,释放连接时需要经过四次挥手
- 第一次挥手:客户端向服务端提出结束连接,让服务器做最后的准备工作。此时,客户端处于半关闭状态,即表示不再向服务器发送数据,但还可以接收数据
- 第二次挥手:服务器接收到客户端释放连接的请求后,会将最后的数据发给客户端。并告知上层的应用进程不在接收数据
- 第三次挥手:服务器发送完数据后,会给客户端发送一个释放连接的报文。那么客户端接收后就知道可以正式释放连接了
- 第四次挥手:客户端接收到服务器最后的释放连接报文后,要回复一个彻底断开的报文。这样服务器收到后才会彻底释放连接。这里客户端,发送完最后的报文后,会等待2MSL,因为有可能服务器没有收到最后的报文,那么服务器迟迟没收到,就会再次给客户端发送释放连接的报文,此时客户端在等待时间范围内接收到,会重新发送最后的报文,并重新计时。如果等待2MSL后没有收到,那么彻底断开。
网络编程三要素
协议
- 协议:计算机网络通信必须遵守的规则
IP地址
- IP地址:指互联网协议地址,俗称IP。IP地址用来给一个网络中的计算机设备做唯一的编号
- IP地址分类方式一:
- IPv4:是一个32位的二进制数,通常被分为四个字节,表示成 a.b.c.d 的形式;其中a、b、c、d都是0~255之间的十进制整数,那么最多可以表示42亿个
- IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,拟通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十制数,表示成ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,号称可以为全世界的每一粒沙子编上一个网址,这样就解决了网络地址资源数量不够的问题。
- IP地址分类方式二:
- 公网地址(万维网使用)和私有地址(局域网使用)。192.168.开头的就是私有址址,范围即为192.168.0-192.168.255.255,专门为组织机构内部使用
端口号
- 网络的通信,本质上是两个进程(应用程序)的通信。每台计算机都有很多的进程
- 如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的进程(应用程序)了。
- 端口号:用两个字节表示的整数,它的取值范围是0~65535。
- 公认端口:031023。被预先定义的服务通信占用,如:HTTP (80),FTP (21),Telnet(23)。
- 注册端口:1024~49151。分配给用户进程或应用程序。如:Tomcat (8080),MySQL(3306),Oracle (1521) 。
- 动态/私有端口:49152~65535。
- 如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败。创建网络编程程序端口不要和其他程序冲突
- 利用协议+IP地址+端口号三元组合,就可以标识网络中的进程了,那么进程间的通信就可以利用这个标识与其它进程进行交互。
InetAddress
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressTest {
public static void main(String[] args) throws UnknownHostException {
// 获取本地主机名和IP地址
InetAddress ia = InetAddress.getLocalHost();
System.out.println(ia);//获取主机名以及端口号
System.out.println(ia.getHostName());//获取主机名
System.out.println(ia.getHostAddress());//获取端口号
// 通过主机名获取对应IP地址
System.out.println(InetAddress.getByName("WWW.baidu.com"));
// 通过字节数组封装IP地址
byte[] b = {(byte) 220,(byte) 181,38,(byte) 149};
//底层采用int类型数组
InetAddress byAddress = InetAddress.getByAddress(b);
System.out.println(byAddress);
}
}
Socket
- 通信的两端都要有Socket(也可以叫"套接字”),是两台机器间通信的端点。网络通信其实就是Socket间的通信。
- Socket可以分为:
- 流套接字(stream socket) :使用TCP提供可依赖的字节流服务
- ServerSocket:此类实现TCP服务器套接字。服务器套接字等待请求通过网络传入。
- Socket:此类实现客户端套接字(也可以就叫"套接字"”)。套接字是两台机器间通信的端点。
- 数据报套接字(datagram socket):使用UDP提供"尽力而为"的数据报服务
- DatagramSocket:此类表示用来发送和接收UDP数据报包的套接字。
- 流套接字(stream socket) :使用TCP提供可依赖的字节流服务
单向通信
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/*客户端*/
public class Client {
public static void main(String[] args) throws IOException {
System.out.println("====================客户端===================");
// 1.创建客户端socket对象
// host:将数据发送给哪一台电脑 port:发送给这一台电脑的哪一个程序
Socket socket = new Socket("localhost",8888);
/*Socket socket = new Socket(InetAddress.getLocalHost(),8888);
Socket socket = new Socket("127.0.0.1",8888);*/
// 2. 获取输出流对象
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
// 3.写出数据
dos.writeUTF("小明,你好!");
// 4.关闭客户端socket,关闭流
dos.close();
socket.close();
}
}
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*服务端*/
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("====================服务端===================");
// 1. 创建服务端socket对象
ServerSocket ss = new ServerSocket(8888);
// 2. 获取来连接的客户端socket对象
Socket socket = ss.accept();//接收 线程堵塞
System.out.println("来连接了…………");
// 3. 获取输入流对象
InputStream is = socket.getInputStream();
DataInputStream dis = new DataInputStream(is);
// 4. 取出数据
String s = dis.readUTF();
System.out.println("客户端发来了消息: "+s);
// 5. 关闭资源
dis.close();
ss.close();
}
}
双向通信
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
public class Client {
public static void main(String[] args) throws IOException {
// 1. 创建客户端对象
Socket socket = new Socket(InetAddress.getLocalHost(),9999);
// 2. 获取输出流
OutputStream outputStream = socket.getOutputStream();
PrintStream ps = new PrintStream(outputStream);
// 3. 写出数据到服务端
ps.println("何以解忧?");
// 4. 获取输入流
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 5. 读取服务端发来的消息
String s = bufferedReader.readLine();
// 6. 展示信息
System.out.println("服务端发来的消息是: "+s);
// 7. 关闭资源
bufferedReader.close();
ps.close();
socket.close();
}
}
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/*
双向通信:
客户端发来一条信息
服务端返回一条信息
*/
public class Server {
public static void main(String[] args) throws IOException {
// 1. 创建服务器端对象
ServerSocket serverSocket = new ServerSocket(9999);
// 2. 获取对应的客户端socket
Socket socket = serverSocket.accept();
// 3. 获取输入流
InputStream inputStream = socket.getInputStream();
// 将字节流变为字符流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
// 将字符流转换为缓冲字符流
BufferedReader br = new BufferedReader(inputStreamReader);
// 4. 读取消息
String s = br.readLine();
System.out.println("客户端发来的消息: "+s);
// 5. 获取输出流
OutputStream outputStream = socket.getOutputStream();
PrintStream ps = new PrintStream(outputStream);
// 6. 写出数据到客户端
ps.println("何以解忧,唯有暴富!");
// 7. 关闭资源
ps.close();
br.close();
socket.close();
}
}
单人多次聊天
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("===========服务端============");
// 1. 创建服务端对象
ServerSocket serverSocket = new ServerSocket(9999);
// 2. 获取对应的客户端socket
Socket socket = serverSocket.accept();
System.out.println(socket.getInetAddress().getHostName());
// 3. 获取输入流
InputStream inputStream = socket.getInputStream();
// 将字节流转换成字符缓冲流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
// 写出数据使用输出流
OutputStream outputStream = socket.getOutputStream();
PrintStream ps = new PrintStream(outputStream);
// 4. 读取客户端数据,
String s = "";
while((s=bufferedReader.readLine())!= null){
System.out.println("客户端发来的消息是: " + s);
// 将客户端传递的信息存到StringBuilder中
StringBuilder sbl = new StringBuilder(s);
// 5. 将客户端消息进行处理:反转
sbl.reverse();
// 6. 将处理后的消息
// 7. 使用输出流,返回给客户端
ps.println(sbl.toString());
}
// 8. 关闭资源
ps.close();
bufferedReader.close();
socket.close();
serverSocket.close();
}
}
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/*
需求:
客户端向服务端发送消息
客户端发送的是hello
服务端返回olleh
当客户端发送stop 则停止客户端
*/
public class Client {
public static void main(String[] args) throws IOException {
System.out.println("===========客户端============");
// 1. 创建客户端对象
Socket socket = new Socket("localhost",9999);
// 2、 获取输出流;
PrintStream ps = new PrintStream(socket.getOutputStream());
// 获取输入流,用于接收消息
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 3. 写出数据n条
// 4. 创建Scanner,进行自定义数据输出
Scanner scanner = new Scanner(System.in);
while(true){
System.out.println("请输入您要发送给服务端的信息");
String message = scanner.next();
// 5. 判断信息是否为"stop"
if (message.equalsIgnoreCase("stop")){
// 忽略大小写比较
break;
}
// 如果不是stop,再将信息发送给服务端
ps.println(message);
// 6. 接收服务端反馈的信息
String result = br.readLine();
// 7. 输出服务端反馈的消息
System.out.println("服务端反馈的消息是:\r" + result);
}
// 8. 关闭资源
br.close();
ps.close();
socket.close();
}
}
一个服务器多人连接
- 服务器:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("===========服务端============");
// 1. 创建服务端对象
ServerSocket serverSocket = new ServerSocket(9999);
// 2. 获取对应的客户端的socket对象
int count = 0;
while(true){
Socket socket = serverSocket.accept();
String hostName = socket.getInetAddress().getHostName();
System.out.println(hostName+": 来连接了,您是第【" + (++count) + "】个连接的");
SocketThread thread = new SocketThread(hostName, socket);
// 启动线程
thread.start();
}
}
}
class SocketThread extends Thread{
private Socket socket;
private String name;
public SocketThread(String name, Socket socket) {
super(name);
this.name = name;
this.socket = socket;
}
@Override
public void run() {//run方法不能向上抛异常
try(
// 1. 接收客户端信息:输入流
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 2. 向客户端反馈消息:输出流
PrintStream ps = new PrintStream(socket.getOutputStream());
){
// 3. 使用循环不停地接收消息
String mess = "";
while((mess = br.readLine())!=null){
System.out.println("客户端【 " + name + " 】发来的消息: "+ mess);
// 4. 将消息进行反转
StringBuilder sbl = new StringBuilder(mess);
sbl.reverse();
// 5. 返回给客户端
ps.println(sbl.toString());
}
} catch (IOException e) {
e.printStackTrace();
}
// 6. 关闭资源:使用try(){}不用关闭资源
}
}
- 多个客户端与单人多次聊天客户端一样
UDP网络编程
- UDP(User Datagram Protocol,用户数据报协议):是一个无连接的传输层协议、提供面向事务的简单不可靠的信息传送服务,类似于短信。
- UDP协议是一种面向非连接的协议,面向非连接指的是在正式通信前不必与对方先建立连接,不管对方状态就直接发送,至于对方是否可以接收到这些数据内容,UDP协议无法控制,因此说,UDP协议是一种不可靠的协议。无连接的好处就是快,省内存空间和流量,因为维护连接需要创建大星的数据结构。UDP会尽最大努力交付数据,但不保证可靠交付,没有TCP的确认机制、重传机制,如果因为网络原因没有传送到对端,UDP也不会给应用层返回错误信息。
- UDP协议是面向数据报文的信息传送服务。UDP在发送端没有缓冲区,对于应用层交付下来的报文在添加了首部之后就直接交付于ip层,不会进行合并,也不会进行拆分,而是一次交付一个完整的报文。比如我们要发送100个字节的报文,我们调用-次send()方法就会发送100字节,接收方也需要用receive()方法一次性接收100字节,不能使用循环每次获取10个字节,获取十次这样的做法。
- UDP协议没有拥塞控制,所以当网络出现的拥塞不会导致主机发送数据的速率降低。虽然UDP的接收端有缓冲区,但是这个缓冲区只负责接收,并不会保证UDP报文的到达顺序是否和发送的顺序一致。因为网络传输的时候,由于网络拥塞的存在是很大的可能导致先发的报文比后发的报文晚到达。如果此时缓冲区满了,后面到达的报文将直接被丢弃。这个对实时应用来说很重要,比如:视频通话、直播等应用。
- 因此UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境,数据报大小限制在64K以下。
DatagramSocket单向通信
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Arrays;
/*
UDP协议:非面向连接
不可靠的
一次只能最大发送64kb的数据
DatagramSocket:客户端/服务端
DatagramPacket :数据包
*/
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("================Server===============");
// 服务端
DatagramSocket dsServer = new DatagramSocket(8888);
// 准备数据包
byte[] bytes = new byte[1024*10];
DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
// 接收数据
dsServer.receive(packet);
// 有效数据的个数
System.out.println("packet.getLength() = " + packet.getLength());
// 获取发送数据包客户端的端口号【如果客户端端口没有指定,则会随机分配】
System.out.println("packet.getPort() = " + packet.getPort());
// 获取客户端的地址
System.out.println("packet.getAddress() = " + packet.getAddress());
// 获取发送过来的数据
System.out.println("packet.getData() = " + packet.getData());
System.out.println("Arrays.toString(packet.getData()) = " + Arrays.toString(packet.getData()));
String s = new String(bytes,0,packet.getLength());
System.out.println(s);
}
}
import java.io.IOException;
import java.net.*;
public class Client {
public static void main(String[] args) throws IOException {
System.out.println("================Client===============");
// 1. 新建客户端对象
DatagramSocket ds = new DatagramSocket(9999);
// 2. 准备数据包
String mess = "Hello World";
//转换成对应的字节数组
byte[] bytes = mess.getBytes();
// 数据 目的地 : 数据 数据长度 ip地址 端口号
DatagramPacket packet = new DatagramPacket(bytes,bytes.length, InetAddress.getLocalHost(),8888
);
// 3. 发送数据
ds.send(packet);
}
}
DatagramSocket双向通信
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class Server {
public static void main(String[] args) throws IOException {
System.out.println("================Server===============");
// 1. 穿件服务端对象
DatagramSocket dsServer = new DatagramSocket(8888);
// 2. 准备数据包
byte[] bytes = new byte[1024];
DatagramPacket packet = new DatagramPacket(bytes,bytes.length);
// 3. 接收数据
dsServer.receive(packet);
// 4. 输出数据
String s = new String(bytes,0,packet.getLength());
System.out.println("客户端发来的数据: "+s);
// 5. 返回数据给客户端
byte[] bytes1 = "我收到了".getBytes();
// 客户端发来的数据包中有客户端的ip地址和端口号
DatagramPacket sendPacket = new DatagramPacket(bytes1, bytes1.length, InetAddress.getLocalHost(),packet.getPort());
// 6. 将数据发送给客户端
dsServer.send(sendPacket);
}
}
import java.io.IOException;
import java.net.*;
/*
使用UDP完成双向通信
*/
public class Client {
public static void main(String[] args) throws IOException {
System.out.println("================Client===============");
// 1. 创建客户端对象
DatagramSocket ds = new DatagramSocket();
// 2. 准备数据包
byte[] bytes = "你好啊".getBytes();
DatagramPacket sedPacket = new DatagramPacket(bytes, bytes.length, InetAddress.getLocalHost(),8888) ;
// 3. 发送数据包
ds.send(sedPacket);
// 准备数据包,接收服务端反馈的数据
// 4. 接收服务端反馈的消息
byte [] bytes1 = new byte[1024];
DatagramPacket receivePacket = new DatagramPacket(bytes1, bytes1.length);
ds.receive(receivePacket);
// 输出服务器反馈给客户端的信息
System.out.println(new String(bytes1, 0, receivePacket.getLength()));
}
}