目录
🌹 UDP回显服务器
🍃服务器端
🍃客户端
socket
我们将socket的api分为两组,一组是基于TCP的api,一组是基于UDP的api。
其中基于UDP的api的特点:无连接,不可靠传输,面向数据报,全双工。
基于TCP的api的特点:有连接,可靠传输,面向字节流,全双工。
理解有无连接,我们可以想一下打电话和发短信,打电话的信息传输就可以认为是有连接的传输,双方通话需要一方申请连接,一方同意建立连接后才能进行通话。如果有一方拒绝连接那么信息传输就无法完成。而发短信则不需要建立这样的连接,只需要发送方将短信发给接收方即可,接收并没有提前得知,也无法拒绝。
理解可不可靠传输,这里的可靠或者不可靠并不是绝对的可不可靠,可靠意思是会尽力进行传输,尽可能的将信息传过去,有一系列的帮助传输的策略。即使传输失败也是知道失败了。而不可靠传输则是将信息传输过去就不管了,至于有没有传输成功,并不会关心,也不会知道结果。
面向数据包则是说传输数据以一个UDP数据包为基本单位。我们可以使用Scanner来进行数据的读和写操作。
面向字节流就是以字节为传输单位进行读和写。读写操作更灵活。
全双工则是双方通信一条路径。
UDP数据报
一般一个数据报由两部分组成,包含数据包的报头和载荷。
UDP的包头一共8个字节,包含4个部分:源端口,目的端口,数据报长度,校验和。每个部分各占2个字节。可以知道表示数据报的长度的信息也是只有两个字节,两个字节的范围(0~65535),显然也就制约了一个UDP数据报的大小最多为64k左右。
载荷就是上层协议的报文,每层的数据报经过封装到下一层后就变成下层协议数据报的载荷。
DatagramPacket API
DatagramSocket API
UDP回显服务器
服务器端
public class UdpEchoSeverse {
private DatagramSocket datagramSocket = null;
public UdpEchoSeverse(int pose) throws SocketException {
//服务器的socket构造方法需要输入一个端口号,相当于某个餐厅的10号窗口
//要让客户端(顾客)知道窗口位置
datagramSocket = new DatagramSocket(pose);
}
public void start() throws IOException {
System.out.println("服务器启动");
//while来循环接受客户端发来的请求,来处理
while (true) {
//这里创建了一个空的packet,相当于一个空包裹,用来接受客户端发来的请求
DatagramPacket requestPacket = new DatagramPacket(new byte[4090], 4090);
//receive是一个返回值是参数的方法,参数里写的是上面创建好的空的packet,通过调用receive方法后返回一个有客户端请求的packet
datagramSocket.receive(requestPacket);
//这里将datagrampacket转成String只是为了方便对客户端的请求进行计算(process)
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String response = process(request);
//将经过process处理后的packet再次封装成新的responsepacket(包裹),准备接下来将相应发送给客户端
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,
requestPacket.getSocketAddress());
//发送响应
datagramSocket.send(responsePacket);
//打印日志:
System.out.println("["+requestPacket.getAddress()+","+requestPacket.getPort()+"]"+","+"req:"
+request+"rep:"+response);
}
}
//process方法是为了处理客户端请求的,是服务器中的关键方法,由于这个程序至少最简单的回显服务器,所以这里直接return了
public String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
//必须有main方法才能运行,输入端口号
UdpEchoSeverse udpEchoSeverse = new UdpEchoSeverse(6060);
udpEchoSeverse.start();
}
}
客户端
public class UdpEchoClient {
private DatagramSocket datagramSocket = null;
//severIp和severpose分别指的是服务器的ip和端口号,要给服务器发送请求就必须知道对应服务器的ip和端口号
//就相当于买东西要知道商店的位置
private String severIp;
private int severpose;
//构造方法,不需要知道客户端socket的端口号,至于传服务器的ip和端口号
//不需要传客户端的端口号不是意味着客户端没有端口号,而是让系统自动分配一个空闲的端口号给客户端,相当于餐厅的座位,那里没人坐哪里就行
public UdpEchoClient(String severIp,int severpose) throws SocketException {
datagramSocket = new DatagramSocket();
this.severIp = severIp;
this.severpose = severpose;
}
public void start() throws IOException {
System.out.println("客户端启动");
//同理,循环发送请求
while (true){
//udp是以一个udp数据包为单位的读与写
//可以直接通过scanner来输入请求
Scanner scanner = new Scanner(System.in);
String request = scanner.next();
//将输入的请求打包成requestpacket,来进行发送
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(severIp),
severpose);
//发送请求
datagramSocket.send(requestPacket);
//准备一个空的packet来接收服务器发过来的响应
DatagramPacket responsePacket = new DatagramPacket(new byte[4090],4090);
datagramSocket.receive(responsePacket);
//转成字符串方便打印
String response = new String(responsePacket.getData(),0,responsePacket.getLength());
System.out.printf("req:%s,res:%s\n",request,response);
}
}
public static void main(String[] args) throws IOException {
//写的是服务器的ip和端口号
UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",6060);
udpEchoClient.start();
}
}