简单认识一下传输层中的UDP和TCP:
TCP:有链接,可靠传输,面向字节流,全双工
UDP:无连接,不可靠传输,面向数据报,全双工
有链接类似于打电话,通了就是有链接。没通就一直在等待。
无连接类似于发短信,只管发,不管到。
可靠传输就是保证信息传输的可靠性。就好比打电话时,你会询问对方在吗,对方回复你,你在发送重要数据给对方。不可靠传输,就好比发短信。假设对方开启了飞行模式,你短信依然能发,但是对方收不收得到,你并不关心也不会询问。
字节流:能按需所取,比如100个字节,可以一个字节一个字节取100次,也能5个字节5个字节的读20次。
数据报:固定的字节数据构成一个数据报,一次只能读取发送一个数据报。
全双工就是双向通信。既能发送也能接收的意思。
简单介绍了TCP,UDP的一些特性,下面我们来看Java中两个重要的网络编程的类。
DatagramSocket这个类提供了两个构造方法:
一个是无参构造方法,相当于只是创建了一个socket,另外一个需要传入一个端口号。 此时就是让当前的socket对象和这个指定的端口,关联起来。无参构造也会分配一个端口号,不过是系统自动分配空闲的端口号。
这两个方法的参数是一个DatagaramPacket.这也是一个类,先来介绍下:
用于释放资源的。
DatagramPacket这个类也提供了2个构造方法:
第一个构造方法相当于设置好了一个缓冲区。
第二个构造方法既构造了一个缓冲区又构造了一个地址。
基于这两个API,构造一个最简单的UDP客户端服务器程序。
服务器端的代码:
package network;
//Udp回显的服务器
import javax.imageio.IIOException;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.nio.charset.StandardCharsets;
public class UdpEchoServer {
//网络编程的本质是要操作网卡
//网卡不好直接操作,因此操作系统把socket这样的文件抽象成了网卡
//因此进行网络通信,势必先有一个socket对象。
private DatagramSocket socket=null;
//对于服务器来说,创建socket对象的同时,要让他绑定上一个具体的端口号
public UdpEchoServer(int port) throws SocketException {
socket =new DatagramSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!!");
while (true){
//并不知道有多少个客户端想建立链接,因此写个循环
//只要有客户端过来,就可以提供服务
//1.读取客户端发来的请求是啥
//receive方法,需要一个空白DatagramPacket对象,交给receive来进行填充。填充的数据来自于网卡
DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
//此时这个DatagramPacket是一个特殊的对象,并不方便直接处理,可以把这里包含的数据拿出来,构成一个字符串
String request=new String(requestPacket.getData(),0,requestPacket.getLength());
//2.根据请求计算响应,由于此处是回显服务器,响应和请求相同。
String response=process(request);
//3.把回显写回到客户端,send参数也是DatagramPacket 需要把这个Packet对象构造好
DatagramPacket responsePacket =new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
socket.send(responsePacket);
//4.打印请求响应的处理中间结果
System.out.printf("[%s:%d] req: %s; resp:%s\n",requestPacket.getAddress().toString(),requestPacket.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();
}
}
这里我们用while循环实现了一直等待客户端发送请求这么一个过程。假设客户端不停的发送请求,并且请求的频率非常快。我这个服务器是否就忙不过来处理了呢?这个情况是很可能出现的。那么解决的方法就是高并发执行。好比我开了一家餐馆,生意不好的时候,我一个人绰绰有余。但是生意非常好。我一个人忙不过来,这个时候我雇了2个人来帮忙。这就是高并发的原理。那么高并发是如何实现呢。那就是通过多线程实现。一个核心一个线程的并发执行。效率就会高很多。但是硬件设施终有上限。那么通过加机器也就就解决这个硬件问题了。
客户端的代码:
package network;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
//Udp回显客户端
public class UdpEchoClient {
private DatagramSocket socket=null;
private String serverIp=null;
private int serverPort=0;
public UdpEchoClient (String serverIp,int serverPort) throws SocketException {
socket =new DatagramSocket();
this.serverIp=serverIp;
this.serverPort=serverPort;
}
public void start()throws IOException {
System.out.println("客户端启动!");
Scanner scanner=new Scanner(System.in);
while (true) {
//1.从控制台读取要发送的数据
System.out.print("> ");
String request =scanner.next();
if(request.equals("exit")){
System.out.println("goodbye");
break;
}
//2.构造成UDP请求,并发送,构造这个Packet的时候,需要传入serverIp和port,此处的IP地址是需要一个32位的整数形式
//而上述的IP是一个字符串,所以需要进行转换
DatagramPacket requestPacket =new DatagramPacket(request.getBytes(),request.getBytes().length,
InetAddress.getByName(serverIp),serverPort);
socket.send(requestPacket);
//3.读取服务器的UDP请求,并解析
DatagramPacket responsePacket =new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);
String response =new String(responsePacket.getData(),0,responsePacket.getLength());
//4.解析结果显示出来
System.out.println(response);
}
}
public static void main(String[] args) throws IOException{
UdpEchoClient client =new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
首先启动我们的服务器:
就会进入等待请求的状态。
再启动服务器:
我们输入一个hello;
服务器就会给我们回一个hello。我们再看服务器那边:
收到了客户端的的IP地址端口号。以上就是实现了一个简单的回显服务器和客户端。