在应用层中操作系统提供的一组api=>socket api(传输层给应用层提供)。
在传输层中有两个重要的协议:TCP协议和UDP协议,这两个协议差别非常大,编写代码的时候也是不同的风格。
TCP:有链接、可靠运输、面向字节流、全双工
UDP:无连接、不可靠传输、面向数据报、全双工
1.有链接/无连接
这里的连接是抽象的概念,虚拟的/逻辑上的连接。对于TCP来说,TCP协议中,就保存了对端的信息,A和B通信,A和B先建立连接,让A保存B的信息,B保存A的信息(彼此之间知道,谁是和它建立连接的那个),这就是有链接;对于UDP来说,UDP协议本身不保存对方的信息,所以是无连接。
2.可靠传输/不可靠传输
可靠传输就是不保证数据包100%到达,而是尽可能的提高传输成功的概率,如果出现丢包了就能感知到;不可靠传输是把数据发了,就不管了。
3.面向字节流、面向数据报
面向字节流指读写数据的时候,是以字节为单位的,支持任意长度,但会发生粘包问题;面向数据报读写数据的时候,以一个数据报为单位(不是字符),一次必须读写一个UDP数据报,不能是半个,不存在粘包问题。
4.全双工、半双工
全双工一个通信链路支持双向通信(即能读也能写);半双工一个通讯链路只支持单向通信(要么读要么写)。
接下来就开始网络编程,⽹络编程,指⽹络上的主机,通过不同的进程,以编程的⽅式实现⽹络通信(或称为⽹络数据传 输)。
Socket套接字
Socket套接字,是由系统提供⽤于⽹络通信的技术,是基于TCP/IP协议的⽹络通信的基本操作单元。 基于Socket套接字的⽹络程序开发就是⽹络编程。
分类:
Socket套接字主要针对传输层协议划分为如下三类: 流套接字:使⽤传输层TCP协议;数据报套接字:使⽤传输层UDP协议;原始套接字 原始套接字⽤于⾃定义传输层协议,⽤于读写内核没有处理的IP协议数据。
UDP数据报套接字编程
计算机中的文件通常是一个广义上的概念,文件还能代替一些硬件设备(操作系统管理硬件设备,也是抽象成文件,统一管理的),电脑中的网卡就可以抽象成socket文件,我们操作网卡的过程和操作普通文件差不多,直接操作网卡不好操作,把网卡转换成操作socket文件,socket文件就相当于“网卡的遥控器”
API介绍:
1. DatagramSocket 是UDP Socket,⽤于发送和接收UDP数据报。
构造方法:就相当于打开文件
创建socket的时候,就会关联上一个端口号,使用端口号区分主机上不同的应用程序。
DatagramSocket方法:这就相当于读写操作
2.DatagramPacket 表示一个完整的UDP数据报
构造方法:
方法:
代码实现:UDPEchoServe
public class UdpEchoServer {
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {
// 指定了一个固定端口号, 让服务器来使用.
socket = new DatagramSocket(port);
}
public void start() throws IOException {
// 启动服务器
System.out.println("服务器启动");
while (true) {
// 循环一次, 就相当于处理一次请求.
// 处理请求的过程, 典型的服务器都是分成三个步骤的.
// 1. 读取请求并解析.
// DatagramPacket 表示一个 UDP 数据报. 此处传入的字节数组, 就保存 UDP 的载荷部分.
DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(requestPacket);
// 把读取到的二进制数据, 转成字符串. 只是构造有效的部分.
String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
// 2. 根据请求, 计算响应. (服务器最关键的逻辑)
// 但是此处写的是回显服务器. 这个环节相当于省略了.
String response = process(request);
// 3. 把响应返回给客户端
// 根据 response 构造 DatagramPacket, 发送给客户端.
// 此处不能使用 response.length()
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
requestPacket.getSocketAddress());
// 此处还不能直接发送. UDP 协议自身没有保存对方的信息(不知道发给谁)
// 需要指定 目的 ip 和 目的端口.
socket.send(responsePacket);
// 4. 打印一个日志
System.out.printf("[%s:%d] req: %s, resp: %s\n", requestPacket.getAddress().toString(), requestPacket.getPort(),
request, response);
}
}
// 后续如果要写别的服务器, 只修改这个地方就好了.
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
代码相关说明:
1.UDPEchoServe在这个类中的Echo是回声的意思,即回显服务器(就是客户端给服务器发送一个数据(请求),服务器返回一个数据(响应))。
2. 对于服务器来说,客户端什么时候发送请求,发多少个请求我们无法预测,因此服务器中通常都需要一个死循环,持续不断的读取客户端的请求数据。
3.这里的参数是作为“输出的结果”,为输出型参数,在调用该方法之前,先构造空的(但不null),把对象传到receive里面,receive就会把数据从网卡中读取出来,填充到参数中。
4.计算String中字节的个数;response.length()计算String中字符的个数。
5.收到请求的源IP,源端口就是返回响应的目的IP、目的端口。
6.这里用printf打印,用于格式化输出,它可以使用占位符(%d %s %f等)来控制输出格式,而println不可以。
7.socket关闭不需要close,因为此处的socket对象,伴随整个UDP服务器,如果服务器关闭,进程结束就会自动释放PCB的文件描述符表中的所有资源,也不需要手动调用close了。
8.receive会触发阻塞行为,客户端请求发来了,receive才会返回,客户端的请求没来,receive就会一直阻塞。
代码相关解析:
1.socket对象代表网卡文件,读这个文件,相当于从网卡收文件,写这个文件等于让网卡发数据。
2.
在该循环中主要做三件事:1. 读取请求并解析2. 根据请求, 计算响应. (服务器最关键的逻辑),但是此处写的是回显服务器. 这个环节相当于省略了.3. 把响应返回给客户端。
3.读取请求并解析
构造DatagramPacket对象,DatagramPacket就代表UDP数据包;然后调用receive,理解输出型参数;把UDP数据包载荷取出来,构造成一个String,通过,拿到DatagramPacket中的字节数组,
拿到有效数组的长度,根据字节数组,构造出一个String
.
4.根据请求计算响应
5.
拿到字符串中的字节数组,
拿到字节数组的长度,而不是使用字符串长度,
拿到客户端的IP和端口号。
构造响应数据报,
把构造好的数据报发送出去,前提是数据包中包含了目的IP和目的端口。
6.
记录这次的客户端/服务器的交互过程。
UDPEchoClient
public class UdpEchoClient {
private DatagramSocket socket = null;
// UDP 本身不保存对端的信息, 就自己的代码中保存一下
private String serverIp;
private int serverPort;
// 和服务器不同, 此处的构造方法是要指定访问的服务器的地址.
public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
socket = new DatagramSocket();
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true) {
// 1. 从控制台读取用户输入的内容.
System.out.println("请输入要发送的内容:");
if (!scanner.hasNext()) {
break;
}
String request = scanner.next();
// 2. 把请求发送给服务器, 需要构造 DatagramPacket 对象.
// 构造过程中, 不光要构造载荷, 还要设置服务器的 IP 和端口号
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName(serverIp), serverPort);
// 3. 发送数据报
socket.send(requestPacket);
// 4. 接收服务器的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);
// 5. 从服务器读取的数据进行解析, 打印出来.
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();
}
}
代码相关说明:
1.
在这里DatagramSocket方法,一定不能填写servePort,必须使用无参数版本。
客户端访问服务器serveip是目的IP,serveport是目的端口,源IP客户端所在的主机IP,源端口应该是随机搞一个端口(操作系统随机分配空闲窗口),不推荐客户端使用固定端口。如果客户端是固定端口,很可能客户端运行的时候,这个端口被别的程序占用,就会使当前这个程序运行失败。
2.
构造请求的数据报:1.载荷2.目的IP 目的端口,serverIp按照字符串的方式来构造。
3.
构造client对象指定服务器的IP和端口,这里的127.0.0.1是一个特殊的IP,环回IP,表示当前这个主机,无论你主机的IP真实是啥,都可以使用127.0.0.1代替,类似于this,必须客户端和服务器在同一个主机上才可以用该IP访问,如果是不同主机,就要填写其他IP。
其他:
SMTP:简单邮件协议
FTP:文件传输协议
UDP:用户数据报协议
TELNET:Internet远程登录服务的标准协议