目录
封装和分用是网络数据传输过程中不可或缺的流程。
操作系统提供的api:TCP api,UDP api
操作系统提供进行网络编程的api也叫”socket api“”网络编程套接字“
UDP socket api的使用
核心的类:
1.DatagramSocket
该类是UDP Socket用于发送和接收UDP数据报
负责对socket文件读写,借助网卡发送接收数据。
操作系统中有一类文件,叫做socket文件,抽象表示了”网卡“这样的硬件设备。
进行网络通信最核心的硬件设备:网卡;
通过网卡发送数据,就是写socket文件;通过网卡接收数据,就是读socket文件。
DatagramSocket():创建一个UDP数据报套接字的Socket,绑定到本机任意一个随机端口(客户端)
DatagramSocket(int port):创建一个UDP数据报套接字的Socket,绑定到指定端口(服务器)
2.DatagramPacket
该类是UDP Socket发送和接收的数据报
UDP面向数据报,每次发送接收数据的基本单位,就是一个UDP数据报。
DatagramPacket(byte[ ] buf, int length):构造一个DatagramPacket用来接收数据报,接收的数据保存在字节数组(第一个参数)接收指定长度(第二个参数)
DatagramPacket(byte[ ] buf, int offest, int length, SocketAddress address):SocketAddress address => 目的主机的IP和端口号。
构造UDP发送的数据报时,需要传入SocketAddress可以用InetSocketAddress来创建。
InetSocketAddress(SocketAddress的子类)
InetSocketAddress(InetAddress addr,int port):创建一个socket地址,包含IP地址和端口号
DatagramSocket方法:
void receive(DatagramPacket p):从此套接字,接收数据包(没有接收到,阻塞等待)
void send(DatagramPacket p):从此套接字,发送数据包(不会阻塞等待,直接发送)
DatagramPacket方法:
InetAddress getAddress():从接收的数据报中,获取发送端主机IP地址。/ 从发送的数据报中,获取接收端主机IP地址。
int getPort():从接收的数据报中,获取发送端主机端口号。/ 从发送的数据报中,获取接收端主机端口号。
byte[] getDate():获取数据报中的数据。
回显服务器
网络编程中,最简单的程序 => 网络编程中的hello world。
客户端发什么请求服务器返回什么响应(没有什么业务逻辑)
1.socket api的使用
2.典型的服务器客户端的基本工作流程
服务器代码实现
代码:
public class UdpEchoServer {
private DatagramSocket socket = null;
//第一步:创建DatagramSocket对象
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
//服务器的启动逻辑
public void start() throws IOException {
System.out.println("服务器启动!");
//每次循环,就是处理一个请求响应的过程
while(true){
//1.读取请求并解析
DatagramPacket requsetPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requsetPacket);
//读取到的字节数组,转换成String方便后续的逻辑处理
String request = new String(requsetPacket.getData(),0, requsetPacket.getLength());
//2.根据请求计算响应(对于回显服务器,这一步什么都不用做)
String response = process(request);
//3.把响应返回到客户端,构造一个DatagramPacket作为响应对象
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
response.getBytes().length,requsetPacket.getSocketAddress());
socket.send(responsePacket);
//打印日志
System.out.printf("[%s: %d] req:%s,resp:%s\n",
requsetPacket.getAddress().toString(),
requsetPacket.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对象来完成的。这个程序一旦启动就需要关联上/绑定上一个操作系统的端口号 => 是一个整数,用来区分一个主机上进行网络通信的程序(1024< port<65534)
一个主机上的一个端口号,只能被一个进程绑定。
一个端口已经被金册灰姑娘1绑定了,进程2也想绑定,就会失败,除非1释放这个端口。
一个进程可以绑定多个端口号。
端口号的socket对象是一一对应的,如果一个进程中多个socket对象就能绑定多个端口。
SocketExpection:网络编程中常见的异常,通常表示socket创建失败。(比如:端口号已经被别的进程占用了)
对于服务器来说,需要不断的接受请求,返回响应,接受请求,返回响应......一个服务器往往是7*24小时运行的,要持续不断运行下去,这里的while true没有退出的必要。如果想重启服务器,可以直接杀进程。
通过这个字节数组保存收到的消息正文(应用层数据包) => 也就是UDP载荷部分
此处socket就从网卡中读取到一个UDP数据报,就能放到requestPacket对象中了。
其中UDP数据报的载荷部分就被放到了requestPacket内置的字节数组里了,另外报头也会被requestPacket的其他属性保存。除了UDP报头之外,其他信息,比如收到的数据源IP是啥。
通过requestPacket还能知道数据从哪里来的(源IP,源端口)
如果执行到receive,还没有客户端发请求(阻塞等待)
requsetPacket.getData() => 对应 new byte[4096]
0 => 从字节数组为0位置开始构造String
requsetPacket.getLength() => 获取到字节数组中有效数据的长度,用来构造String
基于字节数组构造出string,字节数组中保存的内容可能为二进制数据/文本数据,把文本数据交给String保存恰到好处,就算是二进制数据,Java的string也能保存。
此处是回显服务器,只是单纯的retrun。
requsetPacket => 客户端来的数据报 requsetPacket.getSocketAddress() => 得到InetAddress对象 => 包括ip和端口和服务器对端的ip和端口。
把请求的源IP和源端口,作为响应的目的IP和目的端口,就可以做到把消息返回给客户端了。
总结:
上述代码中,可以看到UDP是无连接的通信。
UDP socket自身不保存对端的IP和端口,而是在每个数据报中有一个。另外代码也没有“建立连接”“接受连接”操作。
面向数据报:send和receive都是通过Datagram Packet为单位
全双工:一个socket既可以发送也可以接收