概述
网络编程是指编写运行在多个设备(计算机)的程序,这些设备之间在网络中进行数据的传递、发送、接受数据
网络基础知识
网络模型是计算机网络通讯规范,然后再做实现
OSI及TCP/IP参考模型
IP地址
- IP是每台电脑在互联网上的唯一标识符
IP地址表示
- IPv4
它是由一个4字节32位的整数表示,为了方便记忆,使用点分10进制表示D.D.D.D,每8位使用一个0-255表示,用 . 分隔。 平时见到的 如192.168.0.123 这种就是 IPv4地址表示。
由于 表示容量的 问题 ,IPv4 早已不够使用,它的下一个版本IPv6 拥有更大的表示范围。
- IPv6
它是由一个16字节128位的整数表示,表示的范围更大,也就意味着IP地址越多,夸张到 沙漠中每一粒沙子都可以分配到一个IP,它使用8段16进制表示,每段0-65535。X.X.X.X.X.X.X.X 。
InetAddress、InetSocketAddress
**InetAddress**
:封装了**ip**
```java public class InetAddressDemo { public static void main(String[] args) throws UnknownHostException {//InetAddress ia = new InetAddress(); 不能直接创建对象,因为InetAddress被default修饰了
InetAddress ia = InetAddress.getByName("localhost");//localhost指本机的ip地址
System.out.println(ia);
InetAddress ia1 = InetAddress.getByName("127.0.0.1");//127.0.0.1指本机的ip地址
System.out.println(ia);
InetAddress ia2 = InetAddress.getByName("www.baidu.com");//封装域名
System.out.println(ia2);
System.out.println(ia2.getHostName());//获取域名
System.out.println(ia2.getHostAddress());//获取ip地址
} }
<a name="PEtTX"></a>
##
`**InetSocketAddress**`:封装了`**IP**`、`**端口号**`
```java
public class Test02 {
public static void main(String[] args) {
InetSocketAddress isa = new InetSocketAddress("localhost", 8080);
System.out.println(isa);
System.out.println(isa.getHostName());
System.out.println(isa.getPort());
InetAddress ia = isa.getAddress();
System.out.println(ia.getHostName());
System.out.println(ia.getHostAddress());
}
}
端口号
通过IP地址可以连接到指定计算机,但如果想访问目标计算机中的某个应用程序,还需要指定端口号。在计算机中,不同的应用程序是通过**端口号**
区分的。端口号是用两个字节(16位的二进制数)表示的,它的取值范围是**0~65535**
,其中,0~1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应用或服务所占用。
常见默认端口
MySQL | 3306 |
---|---|
Tomcat | 8080 |
HTTP | 80 |
HTTP | 443 |
SSH | 22 |
FTP | 21 |
TCP/UDP
TCP
TCP协议通过三次握手
协议将客户端与服务器端连接,两端使用各自的Socket对象。Socket对象中包含了IO流,供数据传输。
1、建立连接协议(三次握手)
(1) 客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1。
(2)服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行 数据通讯。
(3) 客户必须再次回应服务段一个ACK报文,这是报文段3。
2、连接终止协议(四次握手)
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段4)。
(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端(报文段6)。
(4) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。
TCP 特点
- TCP是
面向连接的运输层协议
。应用程序在使用 TCP 协议之前,必须先建立 TCP 连接。在传送数据完毕后,必须释放已经建立的 TCP 连接。 - 每一条TCP连接只能有两个
端点
,每一条 TCP 连接只能是点对点
的(一对一) - TCP提供
可靠交付
的服务。通过 TCP 连接传送的数据,无差错、不丢失、不重复,并且按序到达。 - TCP提供
全双工通信
。TCP 允许通信双方的应用进程在任何时候都能发送数据。TCP 连接的两端都设有发送缓存和接受缓存,用
来临时存放双向通信的数据。 面向字节流
。TCP中的“流”指的是流入到进程或从进程流出的字节序列
。
TCP协议实现
Socket客户端和ServerSocket服务器端:
1、建立客户端和服务器端;
2、建立连接后,通过Socket中的IO流(Socket流)进行数据的传输;
(如果是服务器端,则需要添加一步操作:通过Socket服务获取Socket,再获取其中的IO流)
3、关闭socket。
注意点:
1、服务器端开启后等待客户端访问,可以不关闭;
2、一个服务端可以对应多个客户端;
3、不同客户端间通信可以通过服务器端中转信息。
UDP
UDP 特点
UDP是无连接的
,即发送数据之前不需要建立连接(当然,发送数据结束时也没有连接可以释放),因此减少了开销和发送数据之前的时延。- UDP使用尽最大努力交付,即不保证可靠交付,因此主机不需要维持复杂的连接状态表(这里面有很多参数)。
UDP是面向报文的
。发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。这就是说,应 用层交给UDP多长 的报文,UDP就照样发送,即一次发送一个报文。- UDP没有拥塞控制,因此网络出现的拥塞不会使源主机的发送速率降低。但是不使用拥塞控制功能的UDP有可能会引起网络产生严重的拥塞问题。
- UDP支持一对一、一对多、多对一和多对多的交互通信。
- UDP的首部开销小,只有8个字节,比TCP的20个字节的首部还要短。
UDP协议实现
发送和接收均可以使用的Socket类:
DatagramSocket
数据打包的相关类:
DatagramPacket
使用UDP发送或接收数据的步骤:
1、建立发送端,接收端;
2、创建数据;
3、建立数据包;
4、调用Socket的发送、接收方法;
5、关闭Socket。
Socket编程
套接字使用**TCP**
提供了两台计算机之间的通信机制。 客户端程序创建一个套接字
,并尝试连接服务器的套接字
。当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信
两台计算机之间使用套接字建立TCP连接步骤
- 服务器实例化一个
ServerSocket
对象,表示通过服务器上的端口
通信。 - 服务器调用
ServerSocket
类的**accept()**
方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。 - 服务器正在等待时,一个客户端实例化一个
Socket
对象,指定服务器IP
和端口号
来请求连接。 - Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
- 在服务器端,
**accept() **
方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。 - 连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
单向通信
客户端:
public class TestClient {
public static void main(String[] args) throws IOException {
// 1.创建套接子,指定服务器ip和端口号
Socket s = new Socket("localhost", 8888);
// 2.利用OutputStream向外发送数据,但是没有直接发送String方法
OutputStream os = s.getOutputStream();
// 3. OutputStream外套一层DataOutputStream
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("你好啊");
// 4.关闭流+关闭网络资源
dos.close();
os.close();
s.close();
}
}
服务端:
public class TestServer {
public static void main(String[] args) throws IOException {
// 1.创建套接子,指定服务器端口号
ServerSocket ss = new ServerSocket(8888);
/*
2.等待客服端发来信息
阻塞方法:等待接受客户端的数据,什么时候收到数据,什么时候程序继续执行下去
接受到这个Socket后,客户端和服务器才真正产生了连接,才真正可以通信
*/
Socket s = ss.accept();
// 3.操作流
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
// 4.读取客户端发来的数据
String str = dis.readUTF();
System.out.println("客户端发来的数据:"+str);
// 5.关闭流+关闭网络资源
dis.close();
is.close();
s.close();
ss.close();
}
}
双向通信
客户端:
public class TestClient {
public static void main(String[] args) throws IOException {
// 1.创建套接子,指定服务器ip和端口号
Socket s = new Socket("localhost", 8888);
// 2.利用OutputStream向外发送数据,但是没有直接发送String方法
OutputStream os = s.getOutputStream();
// 3. OutputStream外套一层DataOutputStream
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("你好啊");
// 4.接受服务端的回话
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
String str = dis.readUTF();
System.out.println("服务端对我说:"+str);
// 5.关闭流+关闭网络资源
dis.close();
is.close();
dos.close();
os.close();
s.close();
}
}
服务端:
public class TestServer {
public static void main(String[] args) throws IOException {
// 1.创建套接子,指定服务器端口号
ServerSocket ss = new ServerSocket(8888);
/*
等待客服端发来信息
阻塞方法:等待接受客户端的数据,什么时候收到数据,什么时候程序继续执行下去
接受到这个Socket后,客户端和服务器才真正产生了连接,才真正可以通信
*/
Socket s = ss.accept();
// 2.操作流
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
// 3.读取客户端发来的数据
String str = dis.readUTF();
System.out.println("客户端发来的数据:"+str);
// 4.向客户端输出话
OutputStream os = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("你好,我是服务端,我接受到你的请求了");
// 5.关闭流+关闭网络资源
dos.close();
os.close();
dis.close();
is.close();
s.close();
ss.close();
}
}
对象流传送
客户端:
public class TestClient {
public static void main(String[] args) throws IOException {
// 1.创建套接子,指定服务器ip和端口号
Socket s = new Socket("localhost", 8888);
// 2.录入用户账号密码
Scanner sc = new Scanner(System.in);
System.out.println("请输入你的账号");
String name = sc.next();
System.out.println("请输入你的密码");
String pwd = sc.next();
// 3.将账号和密码封装为一个User对象
User user = new User(name, pwd);
// 4.利用OutputStream向外发送数据,但是没有直接发送String方法
OutputStream os = s.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(user);
// 5.OutputStream外套一层DataOutputStream
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("你好啊");
// 6.接受服务端的回话
InputStream is = s.getInputStream();
DataInputStream dis = new DataInputStream(is);
boolean b = dis.readBoolean();
if(b){
System.out.println("恭喜,登录成功");
}else {
System.out.println("对不起,登录失败");
}
// 7.关闭流+关闭网络资源
dis.close();
is.close();
oos.close();
os.close();
s.close();
}
}
服务端:
public class TestServer {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1.创建套接子,指定服务器端口号
ServerSocket ss = new ServerSocket(8888);
/*
2.等待客服端发来信息
阻塞方法:等待接受客户端的数据,什么时候收到数据,什么时候程序继续执行下去
接受到这个Socket后,客户端和服务器才真正产生了连接,才真正可以通信
*/
Socket s = ss.accept();
// 3.操作流
InputStream is = s.getInputStream();
ObjectInputStream ois = new ObjectInputStream(is);
// 4.读取客户端发来的数据
User user = (User) ois.readObject();
// 5.对数据进行验证
boolean flag = false;
if(user.getName().equals("嘿嘿")&& user.getPwd().equals("1234567")){
flag = true;
}
// 6.向客户端输出话
OutputStream os = s.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
dos.writeBoolean(flag);
// 7.关闭流+关闭网络资源
dos.close();
os.close();
ois.close();
is.close();
s.close();
ss.close();
}
}
user类
public class User implements Serializable {
private String name;
private String pwd;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public User(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
}