JavaEE-网络编程(1)
目录
1.TCP和UDP
TCP/IP的五层网络模型(从下到上):物理层->网络链路层->网络层->传输层->应用层
在传输层中,有两个重要协议:一个是TCP,一个是UDP
这两个协议的差别非常大,编写代码的时候也是不同的风格
TCP:有连接,可靠传输,面向字节流,全双工
UDP:无连接,不可靠传输,面向数据报,全双工
1.1 有连接/无连接
此处的有连接/无连接是一个抽象的概念,指的是逻辑上的连接,而不是物理上的连接(网线)
有连接:
对于TCP协议来说,TCP协议中,就保存了对端的信息。
A 和 B 通信,A 和 B 先建立连接,让A 保存 B 的信息,B 保存 A 的信息
(彼此之间知道,谁是和他建立连接的那个)
对于UDP来说,UDP 协议本身不保存对方的信息,这就是无连接。
1.2 可靠传输/不可靠传输
网络上,数据是非常容易出现丢失的情况的(丢包),
光信号/电信号,都有可能受到外界的干扰
可靠传输:不是保证数据包 100% 到达,而是尽可能提高传输的成功率,如果出现丢包情况,能够感知到
不可靠传输:只是把数据包发了,然后就不管了
直观感受,可靠传输也许更好?
事实上,可靠传输也是需要付出代价的:可靠传输的效率比不可靠传输的效率要低~~
1.3 面向字节流/面向数据报
面向字节流:读写数据的时候,是以字节为单位的。
优点:支持任意长度 缺点:粘包问题
面向数据报:读写数据的时候,以一个数据报为单位(不是字符),一次必须读写一个数据报,不能是半个。
优点:不存在粘包问题 缺点:有长度限制
1.4 全双工/半双工
全双工:一个通信链路,支持双向通信(能读,也能写)
半双工:一个通信链路,只支持单向通信(要么读,要么写)
举个例子:
马路上,单行车道=>半双工
双车道/四车道=>全双工
2. socket
在计算机中,“文件” 通常是一个“广义的概念”,文件可以代指电脑上的软件,也能代指一些硬件设备(操作系统管理硬件设备,也是抽象成文件,统一管理的)
而电脑的网卡,就可以抽象为是一个文件=>socket文件
操作网卡的时候,流程和操作普通文件差不多:打开->读写->关闭
操作网卡,直接操作硬件不好操作
于是,把操作网卡转换成操作 socket 文件,socket 文件就相当于“网卡的遥控器”
简而言之就是:电脑的网卡被操作系统当作文件来管理
既然被操作系统当作文件来管理,操作系统就提供了一套socket api 供程序员进行网络编程
UDP 和 TCP 都各有一套 socket api
3.UDP的 socket api
3.1DatagramSocket
Java中使用 DatagramSocket类 对socket api进行了封装
构造方法:打开文件
接收数据报/发送数据报的方法
关闭文件
3.2 DatagramPacket
Java用 DatagramPacket类 来表示一个完整的数据报
UDP 数据报的载荷数据,就可以通过构造方法来指定:
4. 写一个UDP回显服务器
回显服务器:
客户端给服务器发送一个数据(请求),服务器返回一个数据(响应),请求是啥,响应就是啥
服务器运行的步骤如下:
1.接收请求
2.解析并处理请求
3.响应客户端
4.打印日志
代码如下:
public class UdpEchoServer {
private DatagramSocket socket=null;
public UdpEchoServer(int port) throws SocketException {
this.socket = new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!");
//进行不断的接收请求
while (true) {
//1.接收请求
DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String request=new String(requestPacket.getData(),0,requestPacket.getLength());
//2.解析并处理请求
String response=precess(request);
//3.响应客户端
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(),requestPacket.getPort(),
request,response);
}
}
private String precess(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer udpEchoServer=new UdpEchoServer(9090);
udpEchoServer.start();
}
}
【注意】
- 对于服务器来说,客户端啥时候发请求,发多少个请求,我们无法预测。因此服务器中通常需要一个死循环,持续不断的尝试读取客户端的请求数据(7*24h)
-
socket的receive方法的返回值是void,此处是把参数作为“输出的结果”,也就是输出型参数。调用前,先构造空的(不是null)数据报,把对象传入 receive 里,receive 就会把数据从网卡中读出来,填充到参数中 - 构造一个用于发送的数据报时,需要填入发送的长度,此处的长度指的是有多少个字节,因此使用response.getBytes().length,该方法返回的是String中字节的个数。不可以使用response.length,这个方法返回的是String中字符的个数
- 收到请求的 源ip、源端口,就是返回响应的目的ip、目的端口。而这个IP和端口号在socket的receive操作就已经填充到了 request 数据报中,直接调用request.getSocketAddress()就能够直接获取到一个InetSocketAdress对象,该对象中包含了该数据报的ip和端口号
- UDP报头里包含了源端口、目的端口,IP 报头里包含了源IP、目的IP,而DatagramPacket对象里既包含了UDP的信息,也包含了IP信息,因此可以直接获取到源IP、目的IP,源端口、目的端口
- socket 不需要 close 。一个文件是否要关闭,首先要考虑这个文件对象的生命周期。此处的socket 对象,伴随着整个udp服务器,自始至终。如果服务器关闭(进程结束),进程结束时就会自动释放 PCB 的文件描述符表中的所有资源,不需要手动调用 close
- 当服务器启动了,客户端没有发送请求,服务器会处于一个阻塞状态。这是由于 receive会触发阻塞行为,当客户端发来了请求,receive 才会返回,客户端的请求没来,receive 就一直阻塞
DatagramPacket 有三个方法:
- getAddress 只拿到 IP
- getPort 只拿到端口
- getSocketAddress 同时拿到IP 和端口(IP 和端口通过一个InetAdress 对象来表示)
5.写一个UDP客户端
步骤如下:
1.用户输入请求
2.把请求发送到服务器
3.接收服务器的响应
4.把响应的信息转换成字符串,并打印出来
代码如下:
public class UdpEchoClient {
private String serverIp;
private int serverPort;
DatagramSocket socket=null;
public UdpEchoClient(String serverIp, int serverPort) throws SocketException {
this.serverIp = serverIp;
this.serverPort = serverPort;
this.socket = new DatagramSocket();
}
public void start() throws IOException {
while (true) {
//1.请用户输入请求
System.out.println("请输入要发送的内容:");
Scanner scanner=new Scanner(System.in);
String request=scanner.next();
//2.把请求发送到服务器
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 udpEchoClient=new UdpEchoClient("127.0.0.1",9090);
udpEchoClient.start();
}
}
【注意】
- UDP客户端需要定义 severIp 和 severPort 属性来保存目的 IP 和目的端口
- 构造 DatagramSocket 时,必须使用无参数版本。原因:如果客户端使用固定端口,很可能客户端运行的时候,这个端口被别的程序占用,就会使得当前这个程序运行失败。因此客户端不推荐使用固定端口,使用无参数版本,操作系统会自动分配空闲端口。(服务器使用固定端口是因为服务器要经常被各种客户端访问,使用随机端口可能会使客户端获取到的目的端口失效,造成损失)
构造请求的数据报:
1.载荷
2.目的 IP 目的端口
传入 severIp 的时候,需要使用InetAddress.getByName()将 severIp从String类型转换成InetAddress类型
构造服务器对象,设置端口为 9090
构造出客户端对象,传入IP地址:127.0.0.1,和服务器端口 9090
为什么使用127.0.0.1?
127.0.0.1 是一个特殊的 IP,环回 IP ,表示当前这个主机
无论你主机的 IP 是啥,都可以使用 127.0.0.1 代替,类似于 this
分别运行服务器的main方法和客户端的main方法,执行结果如下:
UDP回显服务器和UDP客户端正常运行
【注意】
给客户端设置端口时,可能会出现以下报错:
出现这种情况的原因是,这个端口号已经被其他客户端、服务器占用了
解决办法:换一个端口
完
如果哪里有疑问的话欢迎来评论区指出和讨论,如果觉得文章有价值的话就请给我点个关注还有免费的收藏和赞吧,谢谢大家
原文地址:https://blog.csdn.net/NoobNo2/article/details/143830944
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!