网络编程基础
为什么需要网络编程?--丰富的网络资源
用户在浏览器中,打开在线视频网站,如优酷看视频,实质是通过网络,获取到网络上的一个视频资源。与本地打开视频文件类似,只是视频文件这个资源的来源是网络。
相比本地资源来说,网络提供了更为丰富的网络资源:
所谓的网络资源,其实就是在网络中可以获取的各种数据资源。而所有的网络资源,都是通过网络编程来进行数据传输的。
什么是网络编程
网络编程,指网络上的主机,通过不同的进程,以编程的方式实现网络通信:网络数据传输当然,我们只要满足进程不同就行;所以即便是同一个主机,只要是不同进程,基于网络来传输数
据,也属于网络编程。
特殊的,对于开发来说,在条件有限的情况下,一般也都是在一个主机中运行多个进程来完成网络编程。
但是,我们一定要明确,我们的目的是提供网络上不同主机,基于网络来传输数据资源:
进程A:编程来获取网络资源
进程B:编程来提供网络资源
网络编程中的基本概念
1.发送端和接收端
在一次网络数据传输时:
发送端:数据的发送方进程,称为发送端。发送端主机即网络通信中的源主机。
接收端:数据的接收方进程,称为接收端。接收端主机即网络通信中的目的主机。
收发端:发送端和接收端两端,也简称为收发端。
注意:发送端和接收端只是相对的,只是一次网络数据传输产生数据流向后的概念。
2.请求和响应
一般来说,获取一个网络资源,涉及到两次网络数据传输:
第一次:请求数据的发送
第二次:响应数据的发送。
好比在快餐店点一份炒饭:
先要发起请求:点一份炒饭,再有快餐店提供的对应响应:提供一份炒饭3.客户端和服务端
服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外提供服务。
客户端:获取服务的一方进程,称为客户端。
对于服务来说,一般是提供:客户端获取服务资源
Socket套接字
概念
Socket(套接字) 是计算机网络中用于进程间通信的一种机制,它允许不同主机或同一主机上的不同进程之间进行数据交换。Socket 是应用层与传输层之间的抽象接口,为程序提供了使用网络协议(如 TCP/IP)的标准化方式。
Socket套接字,是由系统提供用于网络通信的技术,是基于TCP/IP协议的网络通信的基本操作单元;基于Socket套接字的网络程序开发就是网络编程。
分类
Socket套接字主要针对传输层协议划分为如下三类:
1.流套接字:使用传输层TCP协议
TCP,即Transmission ControlProtocol (传输控制协议),传输层协议。
以下为TCP的特点:
有连接 可靠传输 面向字节流 有接收缓冲区,也有发送缓冲区 大小不限
对于字节流来说,可以简单的理解为,传输数据是基于10流,流式数据的特征就是在I0流没有关闭的情况下,是无边界的数据,可以多次发送,也可以分开多次接收。2.数据报套接字:使用传输层UDP协议
UDP,即User Datagram Protocol(用户数据报协议),传输层协议。
以下为UDP的特点:
无连接 不可靠传输 面向数据报 有接收缓冲区,无发送缓冲区 大小受限:一次最多传输64k
对于数据报来说,可以简单的理解为,传输数据是一块一块的,发送一块数据假如100个字节,必须一次发送,接收也必须一次接收100个字节,而不能分100次,每次接收1个字节。
3.原始套接字
原始套接字用于自定义传输层协议,用于读写内核没有处理的IP协议数据。不能指望,一个数据包发送之后,100%到达对方
可靠传输的意思,不是保证数据包100%到达,而是尽可能的提高传输成功的概率.不可靠传输,只是把数据发了,就不管了
对于TCP来说,TCP协议中,就保存了对端的信息.
A和B通信,A和B先建立连接
让A保存,B的信息,B保存A的信息(彼此之间知道,谁是和他建立连接的那个)
对于UDP来说,UDP协议本身不保存对方的信息--就是无连接面向字节流vs面向数据报
面向字节流,读写数据的时候,是以字节为单位 支持任意长度=>粘包问题
面向数据报,读写数据的时候,以一个数据报为单位(不是字符)
不存在粘包>长度限制 一次必须读写一个UDP数据报,不能是半个
半双工vS全双工
一个通信链路,支持双向通信(能读,也能写)
一个通信链路,只支持单向通信~~(要么读,要么写)
UDP的socket API
Java数据报套接字通信模型
对于UDP协议来说,具有无连接,面向数据报的特征,即每次都是没有建立连接,并且一次发送全部的数据报,一次接收全部的数据报。计算机中的"文件"通常是一个"广义的概念"
文件还能代指一些硬件设备(操作系统管理硬件设备,也是抽象成文件,统一管理的)
网卡=>socket 文件 操作网卡的时候,流程和操作普通文件差不多
也会在文件描述符表中分配一个表项
操作网卡的时候流程和操作普通文件差不多 打开->读写->关闭
其中,打开这个操作也会在文件描述符表中分配一个表项 操作网卡,直接操作不好操作.
把操作网卡转换成操作socket文件.socket文件就相当于"网卡的遥控器"就是电脑的网卡--操作系统当做文件来管理
java中使用UDP协议通信,主要基于DatagramSocket类来创建数据报套接字,并使用
DatagramPacket作为发送或接收的UDP数据报。对于一次发送及接收UDP数据报的流程如下:
以上只是一次发送端的UDP数据报发送,及接收端的数据报接收,并没有返回的数据。也就是只有请求,没有响应。对于一个服务端来说,重要的是提供多个客户端的请求处理及响应,流程如下:
DatagramSocket 构造方法:
这里传输的就是端口号,用于区分主机上的不同的应用程序
DatagramSocket 方法:
DatagramPacket 构造方法:
DatagramPacket 方法:
package thread; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class UdpEchoServer { private DatagramSocket socket=null; private String process(String request){ return request; } 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 requestPacket=new DatagramPacket(new byte[4096],4096); socket.receive(requestPacket); String request=new String(requestPacket.getData(),0, requestPacket.getLength()); // 2. 根据请求, 计算响应. (服务器最关键的逻辑) // 但是此处写的是回显服务器. 这个环节相当于省略了. String response=process(request); //3.把响应返回给客户端 // 根据 response 构造 DatagramPacket, 发送给客户端. // 此处不能使用 response.length() DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length); // 此处还不能直接发送. UDP 协议自身没有保存对方的信息(不知道发给谁) // 需要指定 目的 ip 和 目的端口. socket.send(responsePacket); //4,打印一个日志 System.out.printf("[%s:%d] req:%s,resp:%d\n",responsePacket.getAddress().toString(),requestPacket.getPort(), request,response); } } }
DatagramPacket有三个方法.
1)getAddress 只拿到 IP
2)getPort 只拿到端口
3)getSocketAddress同时拿到IP和端口(IP和端口通过
一个lnetAddress 对象表示)后续客户端给服务器发的数据,就是发字符串.(echo,客户端发一个hello,服务器返回hello)
本身收到的DatagramPacket 的二进制数据从 String转来的.只是还原回去客户端访问服务器,serverlp是目的ip,serverport 是目的端口
源ip客户端所在的主机ip,源端口,应该是随机搞一个端口(操作系统分配空闲端口)一定是不能填写serverPort 必须使用无参数版本
进阶--代码解析
端口号是区分同一个主机不同的应用程序的.
同一时刻,不能有两个程序使用同一个端口
(操作系统中,一个程序尝试关联一个非空闲的端口,就会关联失败)如果客户端是固定端口,很可能客户端运行的时候,这个端口被别的程序占用,就会使得当前这个程序运行失败!!
客户端是在用户手里,无法控制你的用户电脑上都运行啥样的程序
服务器是在程序员手里的,就算出现端口冲突,程序员也方便处理构造请求的数据报.
1.载荷
2.目的IP目的端口127.0.0.1特殊的IP 环回IP 表示当前这个主机.
无论你主机的IP真实是啥,都可以使用127.0.0.1代替.
类似于this
由于此时,客户端和服务器在同一个主机上,就可以使用127.0.0.1来访问
如果是不同主机,就需要填写其他的IP了.客户端和服务器在同一个主机上 是否联网都是可以的.
不同主机上必须联网了不同的主机要进行这样的通讯:(1)在同一个局域网下(2)部署在云服务器上
若是想部署在虚拟机:取决于虚拟机的网络是怎么设置的.
虚拟机网络设置规格主要有三种典型:
1.桥接网络(可以)
2.NAT网络(有的虚拟机软件可以,有的不行)
3.Host Only网络(不可以)(为啥没有云服务器不行?NAT机制搞鬼)
把程序部署到云服务器上,就可以访问到云服务器了DatagramPacket UDP面向数据报协议.
发送接收的时候,以数据报作为基本单位socket正常是需要释放的
这个socket都是跟随整个进程的. 此时就不需要单独close.
进程退出,进程内部的pcb也就会销毁,里面的文件描述符表也就释放了.部署: 启动服务器:类似于idea中点一下运行按钮 java-jar jar包名
启动客户端(本地的电脑) 把连接的服务器的IP地址,改成云服务器的IP
ServerSocket 专门给服务器用的
Socket 服务器和客户端都会用
TCP的一个核心特点,面向字节流. 读写数据的基本单位就是字节byte虽然同一个端口,但是如果协议不同,不会冲突的
这个操作只是把数据放到"发送缓冲区"中 还没有真正写入到网卡里.
这个发送缓存区就是指的是内存空间.flush方法来"冲刷缓冲区"
其中,println行为是自动加上\n
如果修改成了下面的这样:
这个时候,数据是发过去了的 服务器收到了的 服务器没有真正处理.
判定收到的数据中是否包含"空白符":换行,回车,空格,制表符,翻页符.....
遇到空白符,认为是一个"完整的next"
在遇到之前,都会阻塞.暗暗约定,一个请求/响应使用\n作为结束标记
对端读的时候,也是读到\n就结束(认为是读到一个完整的请求了)UDP就是以 DatagramPacket 作为单位的.,TCP则是字节为单位.
实际上一个请求,往往是由多个字节构成的
每个客户端连接,都会创建一个新的.
每个客户端断开连接,这个对象也就可以不要了一个服务器要能同时给多个客户端提供服务
accept 就是接受客户端发的连接请求
socket 是基于TCP的 TCP是有连接的 可以理解为建立连接的过程
无法同时等待accept和等待用户请求,等待用户发请求的时候,没法等accept.
这个时候,有新的客户端连过来了,也无法接通电话.如果客户端1不发请求,服务器就会阻塞在hasNext 这里
此时就需要使用多线程来解决这个问题