目录
1.构造socket对象(需要知道服务器的IP和端口,自己则不需要指定端口)
一、套接字(Socket)
我们在编写网络程序时,主要操作编写的是应用层代码,真正要发这个数据,就需要上层协议调用下层协议,所以,应用层要调用的,就是传输层的协议
传输层会给应用层提供一组API,这组API就称为Socket api
Socket api主要分为两组,分别是
- 基于UDP的api
UDP的特点:
无连接
不可靠传输
面向数据报
全双工
- 基于TCP的api
TCP的特点:
有连接
可靠传输
面向字节流
全双工
二、协议特点
1)连接
连接,指的是通信时是否要记录双方的相关信息
例如发短信就是一个无连接的例子,发短信时不需要建立连接,只要把信息投递出去即可。
而打电话就是一个有连接的例子,打电话首先要双方建立连接,然后才能进行通信。
2)可靠传输
可靠传输指的是是否能感知信息传递是否成功
- 不可靠传输:信息发送出去即可,不关注是否发送成功,不关注对方是否接收到数据
- 可靠传输:信息不一定100%发送成功,但如果发送失败,自身可以感知到
3)面向数据报/字节流
- 面向数据报:以一个数据报为单位进行传输
- 面向字节流:以字节为基本单位进行传输,更为灵活
4)全双工/半双工
- 半双工:单向通信,A向B通信时,B只能等待,无法向A传输信息
- 全双工:一条路径,双向通信:A向B通信时,B也可以向A通信
三、UDP
1)核心的两个类
DatagramSocket():
这个类主要表示一个Socket对象
socket对象:Socket对象就相当于系统中一个特殊的文件,这个文件并非对应到硬盘的某个数据存储区域,而是对应到网卡这个硬件设备,简而言之,直接操作网卡并不方便,所以就引入了一个Socket文件,借助这个文件来对网卡进行操作。
这些是DatagramSocket的常用构造器和内部方法
构造器
DatagramSocket提供了两个构造器,一个是无参版本,另一个是有参版本,有参版本中的参数是一个端口号,意为在构造Socket对象时给他指定一个端口号。
一个Socket对象可能被服务器/客户端都使用,而服务器的socket往往需要关联一个具体的端口号,客户端则不需要,让系统自动分配一个端口号即可
例如:你在学校里开了一个餐馆,你需要做宣传来吸引同学们来餐馆就餐,就需要明确指出你的餐馆在学校的哪个位置,此时你的餐馆就相当于一台服务器,端口号就相当于你的地址,同学们就相当于客户端,同学们根据你提供的地址找到你的餐馆,就相当于客户端根据端口号连接到服务器。
而同学们来餐馆点餐后,只需要就近找到一个没有人的位置坐下等餐即可,而下一次来你这里吃饭,就不需要坐到原来的位置上,所以说,客户端是不需要指定端口的,而服务器需要指定端口
方法
如图所示,非常直观
DatagramPacket():
这个类主要表示一个UDP数据报
构造器
- 第一个构造器,一般用于接收数据,不需要设置地址。
- 第二个构造器,一般用于发送数据,需要手动设置地址。
方法
如图所示,非常直观
四、实例:回显服务器
回显服务器:一个最简单的客户端-服务器程序,客户端向服务器发送一个请求,服务器返回一个一模一样的请求。
构造一个回显服务器一共有以下几个步骤
服务端:
1.构造Socket(定义端口)
public class UdpEchoServer {
//构造一个空的socket对象
private DatagramSocket socket = null;
//构造器:因为是服务端,需要指定分配一个端口号
public UdpEchoServer(int port)throws SocketException {
socket = new DatagramSocket(port);
}
}
2.读取请求
public class UdpEchoServer {
//构造一个空的socket对象
private DatagramSocket socket = null;
//构造器:因为是服务端,需要指定分配一个端口号
public UdpEchoServer(int port)throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
while (true){
//每次循环要做的三件事
System.out.println("服务器,启动!");
//1.接收收据
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);//这是一个输出型函数
}
这里是receive方法是一个输出型方法,大概意思就是,你需要传入一个空的数据类型进去,然后方法内部会填充这个数据类型。例如你拿着空饭盒去食堂打菜,食堂大妈会返回给你一个装满菜的饭盒。
3.根据请求计算响应
public class UdpEchoServer {
//构造一个空的socket对象
private DatagramSocket socket = null;
//构造器:因为是服务端,需要指定分配一个端口号
public UdpEchoServer(int port)throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
while (true){
//每次循环要做的三件事
System.out.println("服务器,启动!");
//1.接收收据
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);//这是一个输出型函数
//2.处理数据
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String response = process(request);
//将得到的响应在构造成一个数据报
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());//这里得到的地址是客户端的IP和端口,因为requestPacket是从客户端发来的数据
}
}
private String process(String request) {
return request;
}
}
注意:这里是把requestPacket包装成了一个String类型的数据,这一步不是必须的。requestPacket是一段二进制数据,把他包装成String类型的数据在进行处理,会更加的方便,但实际上直接对二进制数据进行处理也是完全没问题的。对String类型的数据处理完后,还需要重新转为二进制数据报进行返回,这是因为socket.send接收的数据类型是数据报而不是String。
4.返回请求
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
//构造一个空的socket对象
private DatagramSocket socket = null;
//构造器:因为是服务端,需要指定分配一个端口号
public UdpEchoServer(int port)throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
while (true){
//每次循环要做的三件事
System.out.println("服务器,启动!");
//1.接收收据
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);//这是一个输出型函数
//2.处理数据
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String response = process(request);
//将得到的响应在构造成一个数据报
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());//这里得到的地址是客户端的IP和端口,因为requestPacket是从客户端发来的数据
//3.发送数据
socket.send(responsePacket);
System.out.printf("[%s:%d] 请求:%s,发送:%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
}
}
private String process(String request) {
return request;
}
}
总:
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class UdpEchoServer {
//构造一个空的socket对象
private DatagramSocket socket = null;
//构造器:因为是服务端,需要指定分配一个端口号
public UdpEchoServer(int port)throws SocketException {
socket = new DatagramSocket(port);
}
public void start() throws IOException {
while (true){
//每次循环要做的三件事
System.out.println("服务器,启动!");
//1.接收收据
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);//这是一个输出型函数
//2.处理数据
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String response = process(request);
//将得到的响应在构造成一个数据报
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());//这里得到的地址是客户端的IP和端口,因为requestPacket是从客户端发来的数据
//3.发送数据
socket.send(responsePacket);
System.out.printf("[%s:%d] 请求:%s,发送:%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),request,response);
}
}
private String process(String request) {
return request;
}
}
客户端:
1.构造socket对象(需要知道服务器的IP和端口,自己则不需要指定端口)
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String serverIP,int serverPort)throws IOException{
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
}
}
}
2.从控制台读取字符串
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String serverIP,int serverPort)throws IOException{
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
//1.读取字符串
System.out.print("->:");
String request = scanner.next();
}
}
}
3.把字符串构造成UDP数据报并发送
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String serverIP,int serverPort)throws IOException{
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
//1.读取字符串
System.out.print("->:");
String request = scanner.next();
//2.构建数据报并发送
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);
}
}
}
4.读取从服务器返回的数据
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String serverIP,int serverPort)throws IOException{
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
//1.读取字符串
System.out.print("->:");
String request = scanner.next();
//2.构建数据报并发送
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);
//3.读取数据
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);//输出型函数
String response = new String(requestPacket.getData(),0,responsePacket.getLength());
}
}
}
5.打印返回的数据
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String serverIP,int serverPort)throws IOException{
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
//1.读取字符串
System.out.print("->:");
String request = scanner.next();
//2.构建数据报并发送
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);
//3.读取数据
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);//输出型函数
String response = new String(requestPacket.getData(),0,responsePacket.getLength());
//4.打印数据
System.out.printf("req:%s,resp:%s\n", request, response);
}
}
}
总:
package network;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket = null;
private String serverIP;
private int serverPort;
public UdpEchoClient(String serverIP,int serverPort)throws IOException{
socket = new DatagramSocket();
this.serverIP = serverIP;
this.serverPort = serverPort;
}
public void start() throws IOException {
Scanner scanner = new Scanner(System.in);
while (true){
//1.读取字符串
System.out.print("->:");
String request = scanner.next();
//2.构建数据报并发送
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName(serverIP),serverPort);
socket.send(requestPacket);
//3.读取数据
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);//输出型函数
String response = new String(requestPacket.getData(),0,responsePacket.getLength());
//4.打印数据
System.out.printf("req:%s,resp:%s\n", request, response);
}
}
}
五、UDP报文的格式
端口号:每个端口号在UDP报文里都占两个字节,所以端口号的取值范围是 0->65535
一般来说,我们可以指定一个客户端-服务端的端口号,但也有例外
知名端口号:端口号小于1024的端口被称为知名端口号,这是给一些比较名气大的服务器预留的端口号,相当于VIP端口,我们平时应该不使用这些端口
- 源端口:表示数据从哪里来
- 目的端口:表示数据到哪里去
- UDP长度:这是UDP的报文,大小一般限制在0-65535之间,也就是最大为64KB。
- 校验和:网络传输并非非常稳定,网络环境会因为各种各样的因素波动,所以数据通信是很容易受到干扰的,所以校验和存在的意义就在于校验数据的传输是否出错。
校验和并不100%准确。例如:你妈妈给你了一个清单让你去超市买东西,清单上有四样东西:A B C D。你从超市出来以后,看着购物袋中的物品,如果数量为四,那也不一定购买正确,但是如果数量为三或者五,就肯定不正确。
所以说,校验和无法100%检测数据传输是否出错