概述
网络编程是指编写运行在多个设备(计算机)的程序,这些设备之间在网络中进行数据的传递、发送、接受数据
网络基础知识
网络模型是计算机网络通讯规范,然后再做实现
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**`、`**端口号**````javapublic 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外套一层DataOutputStreamDataOutputStream 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外套一层DataOutputStreamDataOutputStream 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外套一层DataOutputStreamDataOutputStream 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;}}

