网络原理 网络编程
一.网络初识链接: https://pan.baidu.com/s/1WspOI-oXB-WwE03z3BMU7Q 提取码: tyke二.网络编程1.网络编程基础⽹络资源就是在⽹络中可以获取的各种数据资源。⽽所有的⽹络资源都是通过⽹络编程来进⾏数据传输的。网络编程指⽹络上的主机通过不同的进程以编程的⽅式实现⽹络通信或称为⽹络数据传输当然我们只要满⾜进程不同就⾏所以即便是同⼀个主机只要是不同进程基于⽹络来传输数 据也属于⽹络编程。特殊的对于开发来说在条件有限的情况下⼀般也都是在⼀个主机中运⾏多个进程来完成⽹络编 程。 但是我们⼀定要明确我们的⽬的是提供⽹络上不同主机基于⽹络来传输数据资源进程A编程来获取⽹络资源进程B编程来提供⽹络资源发送端和接收端在⼀次⽹络数据传输时发送端数据的发送⽅进程称为发送端。发送端主机即⽹络通信中的源主机。接收端数据的接收⽅进程称为接收端。接收端主机即⽹络通信中的目的主机。收发端发送端和接收端两端也简称为收发端。注意发送端和接收端只是相对的只是⼀次⽹络数据传输产⽣数据流向后的概念。请求和响应⼀般来说获取⼀个⽹络资源涉及到两次⽹络数据传输第⼀次请求数据的发送第⼆次响应数据的发送。好⽐在快餐店点⼀份炒饭 先要发起请求点⼀份炒饭再有快餐店提供的对应响应提供⼀份炒饭。客⼾端和服务端服务端在常⻅的⽹络数据传输场景下把提供服务的⼀⽅进程称为服务端可以提供对外服务。客⼾端获取服务的⼀⽅进程称为客⼾端。对于服务来说⼀般是提供客⼾端获取服务资源客⼾端保存资源在服务端常⻅的客⼾端服务端模型客⼾端是指给⽤⼾使⽤的程序服务端是提供⽤⼾服务的程序客⼾端先发送请求到服务端服务端根据请求数据执⾏相应的业务处理服务端返回响应发送业务处理结果客⼾端根据响应数据展⽰处理结果展⽰获取的资源或提⽰保存资源的处理结果2.Socket套接字1概念和分类Socket套接字是由系统提供⽤于⽹络通信的技术是基于TCP/IP协议的⽹络通信的基本操作单元。基于Socket套接字的⽹络程序开发就是⽹络编程。Socket套接字主要针对传输层协议划分为如下三类流套接字使⽤传输层TCP协议 TCP即TransmissionControlProtocol传输控制协议传输层协议。以下为TCP的特点 • 有连接 • 可靠传输 • ⾯向字节流 • 有接收缓冲区也有发送缓冲区 • ⼤⼩不限对于字节流来说可以简单的理解为传输数据是基于IO流流式数据的特征就是在IO流没有关闭的情况下是⽆边界的数据可以多次发送也可以分开多次接收。数据报套接字使⽤传输层UDP协议 UDP即UserDatagramProtocol⽤⼾数据报协议传输层协议。以下为UDP的特点 • ⽆连接 • 不可靠传输 • ⾯向数据报 • 有接收缓冲区⽆发送缓冲区 • ⼤⼩受限⼀次最多传输64k 对于数据报来说可以简单的理解为传输数据是⼀块⼀块的发送⼀块数据假如100个字节必须⼀次发送接收也必须⼀次接收100个字节⽽不能分100次每次接收1个字节。原始套接字原始套接字⽤于⾃定义传输层协议⽤于读写内核没有处理的IP协议数据。2Java数据报套接字通信模型对于UDP协议来说具有⽆连接⾯向数据报的特征即每次都是没有建⽴连接并且⼀次发送全部 数据报⼀次接收全部的数据报。以下只是⼀次发送端的UDP数据报发送及接收端的数据报接收并没有返回的数据。对于⼀个服务端来说重要的是提供多个客⼾端的请求处理及响应流程如下3Java流套接字通信模型4Socket编程注意事项1. 客⼾端和服务端开发时经常是基于⼀个主机开启两个进程作为客⼾端和服务端但真实的场 景⼀般都是不同主机。2. 注意⽬的IP和⽬的端⼝号标识了⼀次数据传输时要发送数据的终点主机和进程3. Socket编程我们是使⽤流套接字和数据报套接字基于传输层的TCP或UDP协议但应⽤层协议 也需要考虑这块我们在后续来说明如何设计应⽤层协议。4. 关于端⼝被占⽤的问题5. 如果⼀个进程A已经绑定了⼀个端⼝再启动⼀个进程B绑定该端⼝就会报错这种情况也叫端⼝ 被占⽤。对于java进程来说端⼝被占⽤的常⻅报错信息如下此时需要检查进程B绑定的是哪个端⼝再查看该端⼝被哪个进程占⽤。以下为通过端⼝号查进程的⽅式在cmd输⼊netstat -ano | findstr端⼝号 则可以显⽰对应进程的pid。如以下命令显⽰了8888进程的pid在任务管理器中通过pid查找进程解决端⼝被占⽤的问题如果占⽤端⼝的进程A不需要运⾏就可以关闭A后再启动需要绑定该端⼝的进程B如果需要运⾏A进程则可以修改进程B的绑定端⼝换为其他没有使⽤的端⼝。3.UDP数据报套接字编程1API介绍①DatagramSocket⽤于发送和接收UDP数据报构造方法方法②DatagramPacketSocket发送和接收的数据报构造方法方法③InetSocketAddress SocketAddress 的⼦类构造⽅法2代码示例①UDP Echo ServerUDP回显服务器public class UDPEchoServer { private DatagramSocket socketnull; public UDPEchoServer(int port) throws SocketException { this.socketnew DatagramSocket(port); //端口号 区分一个主机上的不同应用程序 } //启动服务器 public void start() throws IOException { System.out.println(server start); while (true){ //1.读取请求并解析 此时requestPacket是receive的输出型参数 DatagramPacket requestPacketnew DatagramPacket(new byte[1024],1024); socket.receive(requestPacket); //此处把二进制数据转成字符串 String requestnew String(requestPacket.getData(),0, requestPacket.getLength()); //2.根据请求计算响应 String responseprocess(request); //3.把响应返回给客户端 再去构造一个DatagramPacket DatagramPacket responsePacketnew DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress()); socket.send(responsePacket); } } //由于当前是回显服务器 所以直接把request作为response返回 //如需编写其他服务器 只需修改process的逻辑 private String process(String request) { return request; } public static void main(String[] args) throws IOException { UDPEchoServer udpEchoServernew UDPEchoServer(9121); udpEchoServer.start(); } }②UDP Echo ClientUDP客户端public class UDPEchoClient { //创建一个socket对象 private DatagramSocket socketnull; //服务器端的ip和端口号 String serverIp; int serverPort; //注意不用指定客户端的端口号 系统会自动分配 public UDPEchoClient(String serverIp, int serverPort) throws SocketException { this.socketnew DatagramSocket(); this.serverIpserverIp; this.serverPortserverPort; } public void start() throws IOException { System.out.println(client start); Scanner scannernew Scanner(System.in); //用户通过控制台输入字符串 把字符串发给服务器 从服务器读取响应 while (true){ //1.从控制台读取用户输入 System.out.println(-); String request scanner.next(); if(request.equals(exit)){ break; } //2.把用户输入的字符串包成一个数据报 并进行发送 DatagramPacket requestPacketnew DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(this.serverIp),this.serverPort); socket.send(requestPacket); //3.从服务器读取响应 DatagramPacket reponsePacketnew DatagramPacket(new byte[1024],1024); socket.receive(reponsePacket); String reponsenew String(reponsePacket.getData(),0,reponsePacket.getLength()); //4.显示响应 System.out.println(reponse); } } public static void main(String[] args) throws IOException { UDPEchoClient udpEchoClientnew UDPEchoClient(127.0.0.1,9121); udpEchoClient.start(); } }③UDP Dict Server编写⼀个英译汉的服务器.只需要重写processpublic class UDPDictServer extends UDPEchoServer{ private MapString,String dictnew HashMapString, String(); public UDPDictServer(int port) throws SocketException { super(port); dict.put(hello,你好); dict.put(world,世界); dict.put(love,爱); dict.put(file,文件); //...... } Override public String process(String request){ return dict.getOrDefault(request,该单词不存在); } public static void main(String[] args) throws IOException { UDPDictServer udpDictServernew UDPDictServer(9101); udpDictServer.start(); } }4.TCP流套接字编程1API介绍①ServerSocket创建TCP服务端Socket的API构造方法方法②SocketSocket 是客⼾端Socket或服务端中接收到客⼾端建⽴连接accept⽅法的请求后返回的服 务端Socket。不管是客⼾端还是服务端Socket都是双⽅建⽴连接以后保存的对端信息及⽤来与对⽅收发数据的。构造方法方法2代码示例①TCP Echo Serverpublic class TCPEchoServer { private ServerSocket serverSocketnull; public TCPEchoServer(int port) throws IOException { this.serverSocketnew ServerSocket(port); } public void start() throws IOException { System.out.println(server start); while (true){ //1.接受客户端的连接 //如果有客户端和服务器建立了链接 那么accept就可以返回 否则就会阻塞 Socket socket serverSocket.accept(); //通过这个方法处理客户端的连接过程 processConnection(socket); } } private void processConnection(Socket socket) throws IOException { //在一次连接中 客户端和服务端可能会进行多组数据传输 InputStream inputStreamnull; OutputStream outputStreamnull; try { inputStreamsocket.getInputStream(); outputStreamsocket.getOutputStream(); Scanner scannernew Scanner(inputStream); PrintWriter printWriternew PrintWriter(outputStream); while (true){ //处理多次请求响应的读写操作 //1.读取响应并解析使用Scanner if(!scanner.hasNext()){ //客户端关闭连接 break; } String requestscanner.next(); //2.根据请求计算响应 String responseprocess(request); //3.把响应写回给客户端 printWriter.println(response); printWriter.flush();//把缓冲区的内容快速写入 } } catch (IOException e) { throw new RuntimeException(e); }finally { socket.close();//否则存在文件资源泄露问题 } } private String process(String request) { return request; } public static void main(String[] args) throws IOException { TCPEchoServer tcpEchoServernew TCPEchoServer(9090); tcpEchoServer.start(); } }②TCP Echo Clientpublic class TCPEchoClient { private Socket socketnull; public TCPEchoClient(String socketIp,int socketPort) throws IOException { //客户端在new Socket时 就会和服务器建立tcp连接 socketnew Socket(socketIp,socketPort); } public void start() throws IOException { System.out.println(client start); try (InputStream inputStream socket.getInputStream(); OutputStream outputStream socket.getOutputStream()) { Scanner scannernew Scanner(System.in); Scanner scannerNetwork new Scanner(inputStream); PrintWriter printWriternew PrintWriter(outputStream); while (true) { //1.从控制台读取用户输入 System.out.println(-); String request scanner.next(); //2.把请求发给服务器 printWriter.println(request); printWriter.flush();//把缓冲区的内容快速写入 //3.从服务器读取响应 if(!scannerNetwork.hasNext()){ break; } String responsescannerNetwork.next(); //4.把响应显示到控制台 System.out.println(response); } }catch (IOException e){ e.printStackTrace(); } } public static void main(String[] args) throws IOException { TCPEchoClient tcpEchoClientnew TCPEchoClient(127.0.0.1,9090); tcpEchoClient.start(); } }③服务器引入多线程默认情况下IDEA 为了防止端口冲突不允许同一个程序同时运行多个实例。没勾选时你启动第一个TcpEchoClient后再点运行按钮会报错 “程序已经在运行”无法启动第二个客户端。勾选Allow multiple instances后你就可以同时启动多个TcpEchoClient连接到你的服务器上测试 “多客户端同时连接” 的场景。public class TCPEchoServer { private ServerSocket serverSocketnull; public TCPEchoServer(int port) throws IOException { this.serverSocketnew ServerSocket(port); } public void start() throws IOException { System.out.println(server start); while (true){ //1.接受客户端的连接 //如果有客户端和服务器建立了链接 那么accept就可以返回 否则就会阻塞 Socket socket serverSocket.accept(); //通过这个方法处理客户端的连接过程 //不能直接调用processConnection方法 否则就会进入方法内部的while循环 //processConnection(socket); //此处应该创建新线程 在新线程里调用processConnection Thread tnew Thread(()-{ try { this.processConnection(socket); } catch (IOException e) { throw new RuntimeException(e); } }); t.start(); } } private void processConnection(Socket socket) throws IOException { //在一次连接中 客户端和服务端可能会进行多组数据传输 InputStream inputStreamnull; OutputStream outputStreamnull; try { inputStreamsocket.getInputStream(); outputStreamsocket.getOutputStream(); Scanner scannernew Scanner(inputStream); PrintWriter printWriternew PrintWriter(outputStream); while (true){ //处理多次请求响应的读写操作 //1.读取响应并解析使用Scanner if(!scanner.hasNext()){ //客户端关闭连接 break; } String requestscanner.next(); //2.根据请求计算响应 String responseprocess(request); //3.把响应写回给客户端 printWriter.println(response); printWriter.flush();//把缓冲区的内容快速写入 } } catch (IOException e) { throw new RuntimeException(e); }finally { socket.close();//否则存在文件资源泄露问题 } } private String process(String request) { return request; } public static void main(String[] args) throws IOException { TCPEchoServer tcpEchoServernew TCPEchoServer(9090); tcpEchoServer.start(); } }④服务器引入线程池public class TCPEchoServer { private ServerSocket serverSocketnull; public TCPEchoServer(int port) throws IOException { this.serverSocketnew ServerSocket(port); } public void start() throws IOException { System.out.println(server start); //创建线程池 不能使用FixedThreadPool数目固定) ExecutorService executorService Executors.newCachedThreadPool(); while (true){ //1.接受客户端的连接 //如果有客户端和服务器建立了链接 那么accept就可以返回 否则就会阻塞 Socket socket serverSocket.accept(); //通过这个方法处理客户端的连接过程 //不能直接调用processConnection方法 否则就会进入方法内部的while循环 //processConnection(socket); //此处应该创建新线程 在新线程里调用processConnection // Thread tnew Thread(()-{ // try { // this.processConnection(socket); // } catch (IOException e) { // throw new RuntimeException(e); // } // }); // t.start(); //如果当前项目进一步增多 创建销毁进一步频繁 此时线程创建销毁的开销就不可忽视了 //引入线程池 executorService.submit(()-{ try { processConnection(socket); } catch (IOException e) { throw new RuntimeException(e); } }); } } private void processConnection(Socket socket) throws IOException { //在一次连接中 客户端和服务端可能会进行多组数据传输 InputStream inputStreamnull; OutputStream outputStreamnull; try { inputStreamsocket.getInputStream(); outputStreamsocket.getOutputStream(); Scanner scannernew Scanner(inputStream); PrintWriter printWriternew PrintWriter(outputStream); while (true){ //处理多次请求响应的读写操作 //1.读取响应并解析使用Scanner if(!scanner.hasNext()){ //客户端关闭连接 break; } String requestscanner.next(); //2.根据请求计算响应 String responseprocess(request); //3.把响应写回给客户端 printWriter.println(response); printWriter.flush();//把缓冲区的内容快速写入 } } catch (IOException e) { throw new RuntimeException(e); }finally { socket.close();//否则存在文件资源泄露问题 } } private String process(String request) { return request; } public static void main(String[] args) throws IOException { TCPEchoServer tcpEchoServernew TCPEchoServer(9090); tcpEchoServer.start(); } }3长短连接三.网络原理计算机网络TCP/IP链接: https://pan.baidu.com/s/1RGxJgX2UpEoKnZsC39whHQ 提取码: xi42异常情况详细3异常关机一、基础场景无 NAT 参与的通信家用设备之间同局域网不涉及 NAT。直接通过局域网内的不同 IP 地址区分设备并通信。两个都带外网 IP 的设备之间不涉及 NAT。两个外网 IP 都是全局唯一的可直接互相访问。典型场景服务器、运营商直连路由器自带外网 IP。二、受限制的通信场景网络层原生不允许不同局域网中的两个设备尝试访问对方网络层不允许直接访问。举例你电脑上跑 UDP 服务器对方电脑用客户端无法直接访问。但应用层可以通过技术手段实现如 ToDesk 这类远程控制软件。外网 IP 设备尝试主动访问内网 IP 设备网络层不允许直接访问。内网设备没有公网路由无法被外网直接寻址。三、NAT网络地址转换的触发场景内网 IP 设备主动访问外网 IP 设备会触发NAT网络地址转换。路由器 / NAT 设备会修改 IP 数据报的源 IP把内网设备的私有 IP 地址替换成路由器的外网 IP 地址。这样外网设备收到的请求看起来就像是路由器发出来的。注意因为路由器的外网 IP 通常只有 1 个但内网有很多设备同时上网光靠 IP 区分不开。所以 NAT 会用「端口号」来区分不同的会话你的电脑内网 IP192.168.1.100访问百度发出去的包会先到路由器。路由器把包里的源 IP 和端口改成自己的公网 IP 和一个临时端口同时在「映射表」记下这个对应关系。百度的响应包回来时路由器再根据映射表把目的 IP 和端口改回你的电脑地址发给你。NAT 类型转换方式适用场景静态 NAT内网 IP ↔ 外网 IP 一对一固定映射企业服务器、需要固定公网 IP 的设备动态 NAT内网 IP ↔ 外网 IP 动态池分配早期企业网络公网 IP 数量有限PATNAPT内网 IP 端口 ↔ 外网 IP 端口 多对一映射家用路由器、运营商宽带最常见