在日常生活中我们为什么需要网络编程
我们日常联网之后才能获取到一些资源是网络资源,网络资源就是由网络编程而来的。网络资源其实就是在网络中可以获取的各种数据资源。比如我们日常喜欢看剧这些就是通过网络编程来实现的。
什么是网络编程
网络编程就是通过两个主机之间能够进行通信,基于这样的通信来实现一定的功能。在进行网络编程的时候,需要操作系统给咱们提供一组API,通过这些API才能完成编程。API可以认为是应用层和传输层之间交互的路径,API中的SocketAPI可以完成不同主机之间,不同系统之间的网络通信
TCP,UDP协议
在传输层中提供的协议主要是这两,并且这两个协议的特性(工作原理)差异很大,使用这两种协议进行网络编程,也存在一定的差别。所以就弄了两套API来使用
TCP和UDP的区别
TCP是有连接的,UDP是无连接的
计算机中这种抽象的连接是很常见的,这里的连接本质上就是建立连接的双方,各自保存对方的信息。两台计算机建立连接,就是双方彼此保存了对方的关键信息。
TCP想要建立连接,就需要先建立连接(保存对方的信息),连接完之后才能进行通信。
如果A和B想要进行连接,但是B拒绝了通信就无法完成。
UDP想要通信,就直接发送数据即可 不需要征得对方的同意UDP自身也不会保存对方的信息。但是UDP不知道,我们需要知道。UDP不保存,但是调用UDP的socket API的时候要将对方的位置等信息传过去。
TCP是可靠传输的,UDP是不可靠传输的
在网络进行中进行通信时,A向B发送一个信息,这个信息是不可能100%完全送到的。我们日产需要进行可靠传输,只能退而求其次,A向B发送信息 ,消息是不是到达B这一方,A自己能感知到(A自己到达会返回)进一步就在发送失败的时候采取一定的措施(重传等)
TCP内置了可靠传输机制
UDP没有内置可靠传输比不可靠传输:机制会更加复杂,传输效率会降低。
TCP是面向字节流的,UDP是面向数据报
TCP和文件操作一样,使用字节来作为单位进行传输。
UDP按照数据报为单位进行传输。(数据报有严格的格式)
TCP和UDP都是全双工的
一个信道允许双向通信,就是全双工。
一个信道只能单向通信,就是半双工。
使用Socket对象,既可以发送数据,也可以接受数据。
UDP的Socket API如何使用
DatagramSocket:在Java中使用这个类表示系统内部的Socket文件。
注:Socket其实也是操作系统中的一个概念,本质上是一种特殊的文件。Socket属于是把网卡这个设备,抽象成文件,往socket文件中写数据,相当于通过网卡发送数据,从socket文件中读数据相当于通过网卡接受数据。通过这种操作,就将网络通信和文件操作给统一了。
void receive(DatagramPacket p) void send(DatagramPacket p) void close()
这三个是其中的主要方法,并且第一个方法中的参数是输出型参数其,意思就是将一个参数传进方法中通过这个参数去保存读取到的信息。
DatagramPacket使用这个类,表示一个UDP数据报。UDP每次传输都是以数据报为基本单位。
写一个UDP简单通信程序单纯的调用socket api客户端给服务器发送一个请求(请求就是客户端输入的字符串),服务器在接收到时,会原封不动的返回客户端,客户端在打印出来。也叫回显服务器(echo server)。
服务器
private DatagramSocket socket= null;
先建立一个UDP的数据报使用其进行保存。
//建立构造函数将客户端的端口号存在socket中进行保存
public UdpEchoServer(int port) throws SocketException {
socket = new DatagramSocket(port);
}
DUP的服务器和客户端都需要创建Socket对象,来进行绑定使其两建立连接。服务器的socket一般要显示的指定一个端口号。客户端的socket一般不能显示指定(系统会自动分配一个随机的端口)。
服务器进行指定端口号如同在食堂里面的窗口一般商家在租期中是不会该变窗口位置,客户端如同学生,每次去窗口吃饭排队的顺序这没有一个准确的位置只有去到窗口才会知道,自己的顺序。客户端类似于这样访问服务器时在去安排一个随机的端口号。如果你客户端的端口号进行指定,如你去窗口吃饭不管其他人怎样,你总要在那个位置开始排队,没有人还好,有人会起冲突。
服务器这边手动指定端口,不会出现冲突?客户端在意冲突,服务器不在意??
服务器一般是开发人员手里控制的,一个服务器上都有哪些程序 ,都使用哪些端口,开发人员都是可控的!开发人员写代码时,会指定一个空闲的端口,给当前的服务器使用即可。
客户端不可控,客户端在用户的电脑上,每个用户的电脑上安装的程序不一样,占用的端口不一样,用户这边如果出现端口冲突,也不会理解这些什么错误,一般会认为是程序的问题。所以由系统分配一个空闲的端口比较稳定。
//启动服务器
public void start() throws IOException {
System.out.println("服务器启动!!!");
while(true){//设置为一直循环是因为服务器可能要一天一直在跑一直在等待客户端传输信息
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);//创建了一个UDP的数据报来保存信息从网卡中读的数据并且因为其不能自动创建空间所以手动分配空间
socket.receive(requestPacket);//以二进制的形式保存到requestPack中
}
}
服务器一旦启动会顺利的执行但receive中此时客户端的请求可能还不会来,receive会发生阻塞一直等待客户端发请求过来才会往下执行
while(true){
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String request = new
String(requestPacket.getData(),0,requestPacket.getLength());//将传输进来的二进制改为字符串
}
这个 0和requestPacket.getLength()是将这个requestPacket.getData()得到的数据从0到其最后一个位置之间的全部二进制转化为字符串。并且requestPacket.getLength()其得到的不是4096长度而是传过来数据的长度。
public void start() throws IOException {
System.out.println("服务器启动!!!");
while(true){
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String response = process(request);//根据请求计算响应什么一个服务器中最为重要的部分服务器的区别就是根据客户的请求来响应什么
}
}
public String process(String request){//具体响应的计算根据需求来进行更改这里是回显服务器只会返回客户端请求的内容
return request;
}
响应是服务器区别的重要原因
public void start() throws IOException {
System.out.println("服务器启动!!!");
while(true){
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String response = process(request);
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());//构造这个数据报为了指定数据内容,也指定要发给谁,前两个参数是数据是什么最后一个参数是请求的地址数据从哪里来就要返回到哪
}
}
UDP无连接的~~(UDP自身不会保存数据要发给谁),就需要每次发送的时候,重新指定,数据要发到哪里去!所以创建一个数据报在将响应的来的信息全部传送回去。并且response.getBytes().length求长度改为response.length()是否可以。如果字符串中的内容全部是英文字符,字节和字符个数是一样的,包含中文不行 因为一个英文字符占一个字节,中文字符占3个字节 ,response.length()这个方法返回的是字符数,也就是有如果有2个英文字符会返回2,有两个中文字符也返回2,
response.getBytes().length返回的是字符串的字节数,这个中文字符会返回的值大于前面的方法,两者的值不相等只有英文字符时两者相等。如果使用前面的方法可能会导致不会返回正确的值。
public void start() throws IOException {
System.out.println("服务器启动!!!");
while(true){
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
socket.receive(requestPacket);
String request = new String(requestPacket.getData(),0,requestPacket.getLength());
String response = process(request);
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
socket.send(responsePacket);//使用send方法将响应传送回去
System.out.printf("[%s:%d] req=%s, resp=%s\n", requestPacket.getAddress().toString(),
requestPacket.getPort(), request, response);//打印交互时的详情信息
}
}
上述代码中没有涉及到关闭,socket也是文件,不关闭的会出现文件泄露。不写close的原因是什么
socket是文件描述符表中的一个表项。每次打开一个文件,就会占用一个位置
socket在整个程序的运行中都是需要使用的不能关闭,当其不需要使用的时候才可以关闭,因为到了程序结束的时候。结束时进程进程结束随之文件描述符表就会被摧毁(PCB一样被销毁),随着销毁,系统自动回收 注:文件描述符是在pcb中的跟随进程的。
什么时候会出现文件泄露:代码中频繁的打开文件,但是不关闭.
在一个进程的运行过程中,不断积累打开的文件,逐渐消耗掉文件描述符表里的内容最终就消耗殆尽了.
但是如果进程的生命周期很短,打开一下没多久就关闭了.谈不上泄露.
也就是说这里没有涉及到关闭是因为socket的生命周期短,在客户请求之后就会立刻响应,待用户请求不进行就会关闭。如果没有请求socket里面一直都是空的因为其阻塞了。而且用户的不断请求创建的也只是给同一个socket文件来进行的,没有涉及到多个socket文件,并且因为没有在循环内部进行实例化或者关闭所有都是同一个socket.
客户端
import java.io.IOException;
import java.net.*;
import java.util.Scanner;
public class UdpEchoClient {
private DatagramSocket socket =null;
private int serverPort = 0;
private String sereveIp = "";
public UdpEchoClient(String sereveIp ,int serverPort) throws SocketException {
socket = new DatagramSocket();//使用客户端时创建这个创建
this.sereveIp = sereveIp;
this.serverPort = serverPort;
}
public void start() throws IOException {
System.out.println("客户端启动");
Scanner scanner = new Scanner(System.in);//输入要请求的指令
while (true){
System.out.println("->");
String request = scanner.next();
DatagramPacket requestPack = new DatagramPacket(request.getBytes(),request.length(),
InetAddress.getByName(sereveIp),serverPort);//创建一个数据报来传输用户的内容
socket.send(requestPack);//向客户端传输
DatagramPacket responsePacket = new DatagramPacket(new byte[4096],4096);
socket.receive(responsePacket);//接收客户端返回的响应
String response = new String(responsePacket.getData(),0,responsePacket.getLength());//将其转化为String
System.out.println(response);
}
}
public static void main(String[] args) throws IOException {
UdpEchoClient udpEchoClient = new UdpEchoClient("127.0.0.1",9090);
udpEchoClient.start();
}
}
和创建服务器的基本逻辑一样不同之处就在于创建了两个private的数据要进行传输,以及
在上述的代码中总共使用了三个DatagramPacket的构造方法
DatagramPacket requestPacket = new DatagramPacket(new byte[4096],4096);
只指定字节数组缓冲区的(服务器收请求的时候需要使用,客户端收响应的时候也需要使用)
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length,requestPacket.getSocketAddress());
指定字节数组缓冲区,同时指定一个InetAddress对象(这个对象包含了IP和端口服务器返回响应给客户端)(服务器)
DatagramPacket requestPack = new DatagramPacket(request.getBytes(),request.length(),
InetAddress.getByName(sereveIp),serverPort);//创建一个数据报来传输用户的内容
指定字节数组缓冲区同时指定IP+端口号(客户端)
这个服务器客户端的运行逻辑
服务器先启动.服务器启动之后,就会进入循环,执行到 receive这里并阻塞(此时还没有客户端过来呢)
客户端开始启动,也会先进入while循环,执行scanner.next.并且也在这里阻塞
当用户在控制台输入字符串之后, next就会返回,从而构造请求数据并发送出来~~
客户端发送出数据之后,
服务器:就会从receive中返回,进一步的执行解析请求为字符串,执行process操作,执行send操作
客户端:继续往下执行,执行到receive,等待服务器的响应
客户端收到从服务器返回的数据之后,就会从receive 中返回.执行这里的打印操作,也就把响应给显示出来了.
服务器这边完成一次循环之后,又执行到receive这里.客户端这边完成一次循环之后,又执行到scanner.next这里.双双进入阻塞。