发送端和接收端
在一次网络数据传输时:
发送端:数据的发送方进程,称为发送端 。发送端主机即网络通信中的源主机。
接收端:数据的接收方进程,称为接收端 。接收端主机即网络通信中的目的主机。
收发端:发送端和接收端两端,也简称为收发端。
客户端和服务端
服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端 ,可以提供对外服务。
客户端:获取服务的一方进程,称为客户端。
最常见的场景,客户端是指给用户使用的程序,服务端是提供用户服务的程序:
-
客户端先发送请求到服务端
-
服务端根据请求数据,执行相应的业务处理
-
服务端返回响应:发送业务处理结果
-
客户端根据响应数据,展示处理结果(展示获取的资源,或提示保存资源的处理结果)
Socket套接字
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元。
基于Socket套接字的网络程序开发就是网络编程。
我们学习网络编程,和应用层打交道就是家常便饭,因为我们的编写程序都要在这里完成,使用操作系统提供的一组api(socket api,传输成提供给应用层)。
传输层为我们提供了两套协议:TCP与UDP。
TCP:有连接、可靠传输、面向字节流、全双工。
UDP:无连接、不可靠传输、面向数据流、全双工。
有/无连接:
这个概念会有些抽象,在逻辑上的链接,不是通过物理通过数据线、网线等方式,而是彼此之间保存对方的信息。就如同身份证一般,身份证上有着你的个人信息,是属于你的物品,即使弄丢了他还是属于你,而不是身份证长在你身上才是你的,离开了就不是了。
在TCP协议中,有连接就是在两个端口里互相保存着对方的信息,能够建立链接,这就是有连接
而DUP协议没有这个功能,不会和对方建立连接,保存对方的信息,这便是无连接
可靠/不可靠传输
网络传输中会出现数据丢失的情况:光电信号由于某些原因遭到破坏、干扰而丢失信息或者变成无效信号,被识别出来数据错误后将其丢弃,简称丢包 。
可靠传输:尽可能提高数据传输的成功率,减少丢包的现象出现。出现丢包现象会及时补发,确保信息能够全部发送过去。缺点:消耗的开销会很大。
不可靠传输:不会管接收方收没收到,只管把数据发送出去就完事了。
将路由器/交换机转发的数据看出跑动的汽车,路由器/交换机看成一个一个的十字路口,当路由器/交换机需转发的数据量超过数据转发数据量的上限,就会出现堵车的现象,进而造成丢包现象出现。
最直接的表现就是在公共场所连接公共网络会很卡。
面向字节流/数据流
顾名思义,读写的时候是以字节为单位还是以一个数据报为单位(不是字符)。
全双工/半双工
**全双工:**一个通信链路支持双向通信(能读能写)
**半双工:**一个通信链路支持单向通信(只能读或者写)
举例子:单行路就是半双工,对面的车就得让行等待。双行道就是全双工,来往都行,不受影响。
UDP的socket api
DatagramSocket类
把操作网卡转换成操作socket文件,间接操控网卡,用于发送和接收UDP数据。
构造DatagramSocket端口号
DatagramSocket():创建一个UDP的数据报套接字的Socket,,绑定到本机的任意一个随机端口。(客户端不需要指定端口号,用系统随机分配一个。)
DatagramSocket(int port):创建一个UDP的数据报套接字的Socket,绑定到本机的指定端口(一般用于服务端,区分同一个主机上不同的应用程序)
接收与发送方法
void recieive(DatagramPacket p):从此套接字接收数据报,若没接收到数据报则阻塞等待
void send(DatagramPacket p):从此套接子发送数据包,不会阻塞,直接发送
void close():关闭此数据报套接字
DatagramPacket
构造方法
DatagramPacket(byte [] buf , int length):构造一个DatagramPacket以用来接收数据报,接收的数据保存在字节数组(第一个参数buf)中,接收指定长度(第二个参数length)
DatagramPacke(byte [] , int offset ,int length,SocketAddress address):构造一个DatagramPacket以用来发送数据报,发送的数据为字节数组(第一个参数buf)中,从0到指定长度(第二个参数length)。address指定目的主机的IP和端口号
接收方法
getAddress():从接收的数据报中,获取发送端主机IP地址;或从发送的数据报中,获取接收端主机IP地址
getPort():从接收的数据报中,获取发送端主机的端口号;或从发送的数据报中,获取接收端主机端口号
getData():获取数据报中的数据getBytes():转换字符串为字节
代码实现
回显服务器
java
public class UdpEchoServer{
private DatagramSocket socket = null;
public UdpEchoServer(int port) throws SocketException {//网络异常处理
// 指定一个固定端口号,给服务器使用
socket = new DatagramSocket(port);
}
//服务器启动
public void start() throws IOException {
System.out.println("服务器启动");
while(true){
//1.接收读取请求信息
// DatagramPacket表示一个UDP数据报,传入字节数组就是保存接收UDP的载荷部分。
// DatagramPacket中也能保存了其他五元组的信息
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);//创建空对象
socket.receive(requestPacket);//接收客户端发来的数据
// 读取到的二进制转化成字符串
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
// 拿到字节数组,指定有效范围起始位置,有效的长度
//在调用receive之前,先构一个造新的对象,把对象传递给receive,receive就会把数据从网卡里读出来,填充到参数中
//2.根据请求计算响应(重点功能)但因为是回显服务器,这一步被简化,你发出上面回应什么
String response = process(request);
//3.把响应环境返回给客户端 得到的字节 不能使用response.length(),代表String字符个数
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,//字节个数
requestPacket.getSocketAddress());//返回IP和端口号
//发送UDP,但因为没有存储对方信息,需要我们手动指定目的IP和目的端口
//requestPacket中存储了客户端的源IP和源端口,可以将其作为目的IP和目的端口
socket.send(responsePacket);
//4。打印日志
System.out.printf("[%s:%d] req: %s, res :%\n",
requestPacket.getAddress().toString(),
requestPacket.getPort(),
request,
response);
}
}
private String process(String request) {
return request;
}
public static void main(String[] args) throws IOException {
UdpEchoServer server = new UdpEchoServer(9090);
server.start();
}
}
socket对象代表网卡文件,对socket读文件相当于接收网卡中的数据,写文件相当于网卡发送数据
public class UdpEchoClient { private DatagramSocket socket = null; public UdpEchoClient(int port) throws SocketException { socket = new DatagramSocket(port); }
getSocketAddress方法以读取接受存储到DatagramPacket里的源IP和源端口,特别的,hetAddress只能拿到IP,getPort只能拿到端口
DatagramPacket虽然说是读取UDP报头信息,但实际上他也会读取IP报头里 的信息,获取源IP和目的IP。
在while循环中:
构建DatagramPacket对象requestPacket (创建一个DUP数据包,报头+载荷),由receive接收请求数据包(作为输出型参数接收)往里面填充
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);//客户端若没有发来数据会阻塞等待
将UDP数据包中载荷取出:requestPacket.getData()可以拿到字节数组,requestPacket.getLength()可以拿到长度,然后构建一个Sting。
由于这里是回显服务器,你传进来是什么发回去就是什么,将Sting进行包装。
String request = new String(requestPacket.getData(),0,requestPacket.getLength()); String response = process(request);
response.getBytes()将字符串转换为字节数组、response.getBytes().length得到字节长度,requestPacket.getSocketAddress()取出源IP和源端口信息,然后新构建一个DatagramPacket数据包,然后调用send发送回去。
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
socket.send(responsePacket);
用户端
java
public class UdpEchoClient {
private String serverIP;
private int serverPort;
private DatagramSocket socket = null;
//指定访问服务器的地址
public UdpEchoClient(String serverIP,int serverPort) throws SocketException {
this.serverIP = serverIP;//目的IP
this.serverPort = serverPort;//目的端口
//serverIP是目的IP,serverPort是目的端口,源IP是客户端主机IP,源端口是本机一个系统随机分配的端口
socket = new DatagramSocket();//不能传固定源端口,每次都是随机分配使用,一个固定的端口可能会被其他应用使用导致无法使用。
}
public void start() throws IOException {
while(true) {
System.out.println("输入发送的信息");
Scanner scanner = new Scanner(System.in);
if (!scanner.hasNext()) {
break;
}
String request = scanner.next();
//构造DatagramPacket数据报,传入载荷、目的IP和目的端口,InetAddress.getByName()方法按照字符串创建(给人看的)的IP转换成字符串(给机器看的)。
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
InetAddress.getByName(serverIP), serverPort);
//发送数据报
socket.send(requestPacket);
//接收服务器响应,创建一个数据报,用具接收服务端的信息
DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
socket.receive(responsePacket);
//解析,将收到的数据报转化成字符串类型
String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient client = new UdpEchoClient("127.0.0.1",9090);
client.start();
}
}
用户端的代码这里就不展开讲了,基本上和服务端一样的。