Java 网络编程(一) --- 概念,客户端和服务器通信代码实现
文章目录
- 网络
- IP地址
- 端口号
- 协议
- 五元组
- 协议分层
- 封装和分用
- 发送方
- 接收方
- 网络编程
- TCP和UDP的区别(网络原理的重点)
- UDP的socket api的使用
- 服务器端
- 客户端
- 理解客户端和服务器的交互过程
网络
- 网络:网络原理和网络编程
IP地址
-
ip地址:描述一个设备在网络上的地址

-
ipconfig:查看电脑上的ip地址

端口号
- 1 - 1023 这些是知名的端口号
22 -> ssh
80 -> http
443 -> https
23 -> telnet - 一个程序可以对应多个端口号,但是一个端口号只能对应一个程序


- 实际的通信过程中ip和端口往往是一对

协议
- 协议:是一种约定,约定了通信双方以何种方式传输数据
协议:双方都能够认同的一种约定

五元组
- 源IP,源端口,目标IP,目标端口,协议类型
- 协议类型:达到目标约定的类型
比如:送快递,要有收件人地址,收件人电话,发件人地址,发件人电话,送快递的类型,顺风,邮政…
协议分层
为什么要分层?
- 我们通常把一个大的协议拆分成多个小的协议,每个小的协议只专注做一小部分事情,使每个小的协议都不会太复杂(化繁为简)
比如就像写代码一样,我们的代码写的越多,就越复杂,我们往往把代码拆成多个部分
- 由于网络通信实在是太复杂了,拆成的小的协议就会越来越多,这么多小的协议不太好管理,就需要我们对协议进行分层了
分层:按照协议的定位/作用进行分类


3. 协议分层的好处:
1.协议分层之后,上层和下层之间就进行了封装
使用上层协议,不必过多关注下层
使用下层协议,也不必过多关注上层
比如:只要会说话就能够打电话

2.每一层协议都可以根据需要进行灵活替换

OSI七层网络模型 — 只存在于教课书中
TCP/IP五层网络模型 — 是OSI七层网络模型的简化版本

3. TCP/IP五层模型的具体内容:
每一层的作用需要重点理解

5)应用程序:如何使用这个数据
(程序员最关心的一点)
比如买的衣架,可以用来晒衣服,还可以用来打人
4 -> 3 -> 2 关注的细节越来越多
例子:传输层

网络层:

数据链路层:

网络上一些说成是四层TCP/IP,因为他们没有把物理层算上

驱动程序 + 硬件的例子:

经典的笔试题:

真实的路由器和交换机的情况分析:

封装和分用
- 描述了网络通信过程中,基本的数据传输流程
下面是网络传输的基本流程:
发送方
举个例子:
A通过qq把一个 hello 发送给B
应用层:

传输层:

网络层:

数据链路层:
主要是关注两个相邻节点之间的数据传输

把数据交给物理层
物理层:

封装:从上层协议到下层协议,层层给数据报添加报头
这里的封装不是面向对象的封装了,只是作用有点像
接收方

过程:



还有一些其他的情况:

网络编程
- 概念:通过网络,让两个主机之间能够通信,基于这样的通信来完成一定的功能
- 进行网络通信的时候,需要操作系统给我们提供一组API,通过这些API才能完成编程
- API可以认为是应用层和传输层之间的交互的路径
- API 是Socket(插座) API,通过这一套Socket API可以完成不同主机之间,不同系统之间的网络通信
- 传输层存在两套不同的网络协议:TCP和UDP

TCP和UDP的区别(网络原理的重点)
1. TCP 是有连接的,UDP是无连接的
(连接是抽象的概念)
计算机中,这种抽象的连接是很常见的。此处的连接本质上就是建立连接的双方,各自保存对方的信息
连接:
两台计算机建立连接,就是彼此保存了对方的信息

例子:

2. TCP是可靠传输,UDP是不可靠传输

传输是尽力而为的,不一定是百分百送达的,如果传输失败,我这边是可以感知到的,也可以进行下次的传输
TCP内置了可靠传输机制(知道有没有传输成功)
UDP没有内置可靠传输机制(传了就不管了)

可靠传输的代价:

3. TCP 是面向字节流的,UDP是面向数据报的
字节流:可以一次传一个,分多次传,也可以一次传10个,分多次传,也可以一次传输完

4. TCP和UDP都是全双工的

UDP的socket api的使用

服务器端
- 写一个简单的 UDP 客户端/服务器通信的程序
只是使用api进行网络通信的过程
请求发了什么,响应就发了什么

以下三个步骤,就是开发服务器程序的基本步骤:
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
// 创建一个DatagramSocket 对象,后续操作网卡的基础
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
// 手动指定端口号
socket = new DatagramSocket(port);
// 系统自动分配端口号
// socket = new DatagramSocket();
}
public void start() throws IOException {
// 通过这个方法启动服务器
System.out.println("服务器启动!");
// 一个服务器是长时间运行的(7*24)
// 不知道客户端什么时候来,随时来都有可能
// 所以一个服务器中经常能看到 while true 这样的代码
while(true){
// 1. 读取请求并解析
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
// 完成receive之后,数据是以二进制的形式保存到DatagramPacket中了
// 想要把这里的数据给显示出来还要将二进制数转为字符串
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
// 2. 根据请求计算响应(一般服务器都需要经历的过程)
// 这是回显服务器的特点,请求是什么样,响应就是什么样
String response = process(request);
// 3. 把响应写回客户端,
// 搞一个响应对象 DatagramPacket
// 往 DatagramPacket 里构造刚才的数据,再通过send返回
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
// 4. 打印一个日志,把这次数据交互的详情打印出来
System.out.printf("[%s:%d] req=%s, resp=%s
",requestPacket.getAddress().toString(),
responsePacket.getPort(),request,response);
}
}
public String process(String request){
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}

服务器的 socket 需要指定一个端口号的原因和客户端的 socket 不需要指定一个端口号的原因:

服务器这边手动指定端口号,就不会出现冲突了吗?为什么客户端在意这个冲突,服务器不在意这个冲突呢?

2. receive,从网卡中读数据

receive,start一启动,数据还没有来,就执行receive,会阻塞

将二进制数据转为字符串数据

3. 根据请求计算响应(一般服务器都需要有这个步骤)(最核心的步骤)

4. 把响应写回客户端,搞一个响应对象 DatagramPacket,往 DatagramPacket 里构造刚才的数据,再通过send返回

5. 使用response.getBytes.length和response.length()的区别?

6. 这里为什么不用close关闭socket?
因为socket不需要使用的时候就关闭了,程序就结束了,进程也就结束了,随之文件描述符表就销毁了(PCB都销毁了),就不会泄露了,随着销毁的过程,这些文件描述符表会被系统回收

客户端
- 3个DatagramPacket的构造方法:

package network;
import sun.dc.pr.PRError;
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIp = "";// ip地址
private int serverPort = 0;// 端口号
public UdpEchoClient(String ip, int port) throws SocketException {
// 创建这个对象,不能手动指定端口
socket = new DatagramSocket();
// 由于UDP自身不会持有对端的信息,就需要再应用程序中把对端的信息给记录下来
// 这里记录的是对端的ip和端口号
serverIp = ip;
serverPort = port;
}
public void start() throws IOException {
System.out.println("客户端启动!");
Scanner scanner = new Scanner(System.in);
while(true){
// 1. 从控制台读取数据,作为请求
System.out.print("->");
String request = scanner.next();
// 2. 把请求内容构造成 DatagramPacket 对象,发给服务器
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
// 3. 尝试读取服务器中返回的响应了
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
// 4. 把响应转化为字符串,然后显示出来
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
理解客户端和服务器的交互过程
-
理解这里的交互逻辑


-
问题:

-
云服务器的概念:

-
把我们写的 UDP 服务器部署到云服务器上,进一步就可以让大家来访问了
(部署的操作后续会介绍)
这样就可以实现跨主机通信(聊天)了









