[TOC]

0x00 网络编程

1) 介绍概述

1.概述

  • 计算机网络:是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
  • 网络编程:就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换。

网络编程三要素:IP / 端口(Port) / 协议(protocol)
补充知识: 3G/4G 通过信号塔,有的信号塔连接的是卫星通信;


2.IP概述

  • 每个设备在网络中的唯一标识,每台网络终端在网络中都有一个独立的地址,我们在网络中传输数据就是使用这个地址。
  • 本地回路地址:127.0.0.1 255.255.255.255是广播地址
  • IPv4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。
  • IPv6:8组,每组4个16进制数。
  • 1a2b:0000:aaaa:0000:0000:0000:aabb:1f2f
  • 1a2b::aaaa:0000:0000:0000:aabb:1f2f
  • 1a2b:0000:aaaa::aabb:1f2f
  • 1a2b:0000:aaaa::0000:aabb:1f2f
  • 1a2b:0000:aaaa:0000::aabb:1f2f


3.端口号概述

  • 每个程序在设备上的唯一标识,每个网络程序都需要绑定一个端口号,传输数据的时候除了确定发到哪台机器上,还要明确发到哪个程序。类比于您在什么年级/什么班/位置号
  • 端口号范围从0-65535 (Windows )
  • 编写网络应用就需要绑定一个端口号,尽量使用1024以上的,1024以下的基本上都被系统程序占用了。

常用端口:

* mysql: 3306
* oracle: 1521
* web: 80
* tomcat: 8080
* QQ: 4000
* feiQ: 2425


4.协议概述
为计算机网络中进行数据交换而建立的规则、标准或约定的集合。

  • UDP
    • 面向无连接,数据不安全,速度快。不区分客户端与服务端。 (发短信)
  • TCP
    • 面向连接(三次握手),数据安全,速度略低。分为客户端和服务端。(打电话)
    • 三次握手: 客户端先向服务端发起请求, 服务端响应请求, 传输数据

2) Socket通信原理

Socket套接字概述:网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。

  • 通信的两端都有Socket。
  • 网络通信其实就是Socket间的通信。
  • 数据在两个Socket间通过IO流传输。

Socket在应用程序中创建,通过一种绑定机制与驱动程序建立关系,告诉自己所对应的IP和port。可以类比为码头和集装箱以及货运船只;


3) UDP传输与优化

使用的类:

public class DatagramSocket extends Object implements Closeable  #这类代表一个发送和接收数据包的插座。 
public final class DatagramPacket extends Object #这类表示一个数据报包。


UDP-Socket构建发送流程:
1.发送Send

  • 创建DatagramSocket, 随机端口号
  • 创建DatagramPacket, 指定数据, 长度, 地址, 端口
  • 使用DatagramSocket.send()发送DatagramPacket
  • 关闭DatagramSocket.close()

2.接收Receive

  • 创建DatagramSocket, 指定端口号
  • 创建DatagramPacket, 指定数组, 长度
  • 使用DatagramSocket.receive接收DatagramPacket
  • 关闭DatagramSocket.close()
  • 从DatagramPacket中获取数据

3.接收方获取ip和端口号

String ip = packet.getAddress().getHostAddress();
int port = packet.getPort();


实际案例1:

// 发送端1:
package com.weiyigeek.net;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class Demo1_UDPSend {
4public static void main(String[] args) throws Exception {
44// 1.网络编程之UDP协议 - socket 传输优化(发送端)
44Scanner sc = new Scanner(System.in); //创建键盘录入对象
44DatagramSocket udpsocket = new DatagramSocket(); //创建一个UDP-socket相当于是一个码头
44while(true) {
444String str = sc.nextLine(); //接收我们录入的字符串,如果等于quit则退出循环 相当于是货物
444//创建Packet相当于集装箱
444DatagramPacket pt = new DatagramPacket(str.getBytes(), str.getBytes().length,InetAddress.getByName("127.0.0.1"), 8888);
444udpsocket.send(pt); //从socket通道将数据发送出去,从码头发送货物;
if(str.equals("quit")) //双方都停止通信
4444break;
44}
44udpsocket.close();
4}
}

//接收端 - 接收端一般先启动
package com.weiyigeek.net;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Demo1_UDPReceive {
4public static void main(String[] args) throws Exception {
44//2.Socket 通信接收端
44DatagramSocket udpsocket = new DatagramSocket(8888); //创建socket相当于接收货物的码头
44DatagramPacket udppacket = new DatagramPacket(new byte[1024], 1024); //创建接收的集装箱
44while(true) {
444udpsocket.receive(udppacket); //接收货物实际就是数据
444byte[] arr = udppacket.getData(); // 获取数据
444int len = udppacket.getLength(); //获取有效的字节个数
444String ip = udppacket.getAddress().getHostAddress(); //获取IP地址
444int port = udppacket.getPort(); //获取端口号
444System.out.println(ip+":" + port + " = " + new String(arr,0,len));
444if(new String(arr,0,len).equals("quit")) {
4444break;
444}
44}
44udpsocket.close();
4}
}

执行结果:

whoami
Iloveyou
WeiyiGeek
quit #非常注意最有一个quit 夹杂有其他的不可见符号

WeiyiGeek.



实际案例2:多线程实现一个窗口接和发数据

package com.weiyigeek.net;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class Demo2_ReceSend {

4public static void main(String[] args) {
44// 多线程实现一个窗口了接和收信息
44new receive().start();
44new send().start();
4}

}

//接收端线程
class receive extends Thread {
4@Override
4public void run() {
44try {
444DatagramSocket ss = new DatagramSocket(8888); //监听端口
444DatagramPacket pt = new DatagramPacket(new byte[1024], 1024);
444while(true)
444{
4444ss.receive(pt);
4444byte[] arr = pt.getData();
4444int len = pt.getLength();
4444String ip = pt.getAddress().getHostAddress();
4444int port = pt.getPort();
4444String msg = new String(arr,0,len);
4444System.out.println(ip + ":" + port + "\n" + msg);
4444if(msg.equals("quit")) {
44444System.out.println("接收端停止-Recevice Stop");
44444break;
4444}
444}
444ss.close();
44} catch (Exception e) {
444// TODO: handle exception
44}
4}
4
}

//发送端线程
class send extends Thread {
4@Override
4public void run() {
44// TODO Auto-generated method stub
44Scanner sc = new Scanner(System.in);
44try {
444DatagramSocket ss = new DatagramSocket();
444while(true){
4444String msg = sc.nextLine();
4444DatagramPacket dp = new DatagramPacket(msg.getBytes(), msg.getBytes().length, InetAddress.getByName("127.0.0.1"), 8888);
4444ss.send(dp);
4444if(msg.equals("quit")) {
44444System.out.println("发送端停止-Send Stop");
44444break;
4444}
444}
444ss.close();
44} catch(Exception e) {
444System.out.println(e.getMessage());
44}
4}
}

执行结果:

quit
发送端停止-Send Stop
127.0.0.1:51090
quit
接收端停止-Recevice Stop


4) TCP传输与优化

1.客户端Client

  • 创建Socket连接服务端(指定ip地址,端口号)通过ip地址找对应的服务器
  • 调用Socket的getInputStream()和getOutputStream()方法获取和服务端相连的IO流
  • 输入流可以读取服务端输出流写出的数据
  • 输出流可以写出数据到服务端的输入流

2.服务端Server

  • 创建ServerSocket(需要指定端口号)
  • 调用ServerSocket的accept()方法接收一个客户端请求,得到一个Socket
  • 调用Socket的getInputStream()和getOutputStream()方法获取和客户端相连的IO流
  • 输出流可以写出数据到客户端的输入流
  • 输入流可以读取客户端输出流写出的数据

CS对应表:
客户端 C | 服务端 S
—|—
getInputStream | getOutputStream
getOutoutStream | getInputStream

实际案例:

//########## Client ############
package com.weiyigeek.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class Demo3_TcpSend {
4public static void main(String[] args) throws Exception {
44//1.tcp Socket 客户端
44Socket client = new Socket("127.0.0.1",9999); //创建Socket指定ip地址和端口号
44InputStream is = client.getInputStream(); // 获取服务端发送的输入流信息
44OutputStream os = client.getOutputStream(); //客户端向服务端发送信息
44
44//先接送服务端信息
44byte[] arr = new byte[1024];
44int len = is.read(arr);
44System.out.println(new String(arr,0,len));
44
44//在向服务端发送信息
44os.write("我是客户端发来得消息!".getBytes());
44
44//关闭客户端Socket
44client.close();
4}
}

//############ server ############
package com.weiyigeek.net;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Demo3_TcpReiceve {
4public static void main(String[] args) throws Exception {
44//2.Server服务端TCP socket 案例
44ServerSocket ss = new ServerSocket(9999); //服务端监听端口
44Socket socket = ss.accept(); //接受客服端得请求
44InputStream is = socket.getInputStream(); //获取客户端得输入流
44OutputStream os = socket.getOutputStream(); //客户端得输出流
44
44//向客户端发送信息
44os.write("这是从服务端发送得信息".getBytes());
44
44//打印出接收客户端发送得信息
44byte[] arr = new byte[1024];
44int len = is.read(arr);
44System.out.println(new String(arr,0,len));
44socket.close();
4}
}

WeiyiGeek.


实际案例: TCP服务端多线程及其优化

//客户端 - Client
package com.weiyigeek.net;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class Demo3_TcpSend {
4public static void main(String[] args) throws IOException {
44Socket socket = new Socket("127.0.0.1", 8888); //创建客户端连接到服务端的Tcp-socket = 港口
44//优化1:需要读的时候是字符串,写的时候也是字符串
44//优化2:服务器端应该是多线程的
44
44//这时候我们将一个字节流转换成为了字符流
44BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //获取服务端发送的信息,读取默认是以\r\n为结束符号
44//这里不建议使用BufferedWriter 一行一行写
44PrintStream ps = new PrintStream(socket.getOutputStream()); //PrintStream 中有写入换行的方法并且以\r\n为结束符号;
44//读取服务端的消息并且输出消息
44System.out.println(br.readLine());
44ps.println("2.我是客户端的反馈信息!");
44System.out.println(br.readLine());
44ps.println("4.客户端的通信结束!");
44//关闭客户端的socket
44socket.close();
4}
}


//服务端
package com.weiyigeek.net;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Demo3_TcpReceive {
4private static ServerSocket ss;
4public static void main(String[] args) throws Exception {
44ss = new ServerSocket(8888);
44while(true){
444final Socket socket = ss.accept(); // 循环的接收客户端的信息
444new Thread() {
4444public void run() {
44444try {
444444BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //接收的字节流转换成为字符流
444444PrintStream ps = new PrintStream(socket.getOutputStream()); //向客服端发送信息的对象
444444ps.println("1.我是服务端发出的信息!"); //注意这里不能采用print否则会一直卡输入界面,这是由于没有\r\n,客户端不知道什么时候结束就一直在读
444444System.out.println(br.readLine());
444444ps.println("3.服务端请求客户端关闭通信");
444444System.out.println(br.readLine());
444444socket.close();
44444} catch (IOException e) {
444444e.printStackTrace();
44444}
4444}
444}.start();
44}
4}
}

WeiyiGeek.


5) 网络编程练习

练习1:
客户端向服务器写字符串(键盘录入),服务器(多线程)将字符串反转后写回,客户端再次读取到是反转后的字符串;

客户端:

package com.weiyigeek.net;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Demo4_Client {
4public static void main(String[] args) throws Exception, IOException {
44//需求:客户端向服务器写字符串(键盘录入),服务器(多线程)将字符串反转后写回,客户端再次读取到是反转后的字符串
44//1.服务端创建socket (码头)
44Socket socket = new Socket("127.0.0.1",8888); //创建客端端连接的服务端的IP:prot
44Scanner sc = new Scanner(System.in); //创建键盘录入对象
44BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //获取输入流
44PrintStream ps = new PrintStream(socket.getOutputStream()); //获取输出流
44System.out.print("请输入您要发送的字符串:");
44ps.println(sc.nextLine() ); // 向服务端发送信息
44System.out.println("服务端翻转后的字符串:" + br.readLine());
44socket.close(); //关闭socket - 实际开发中非常重要 br / ps 也随之而关闭
4}
}

服务端:

package com.weiyigeek.net;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Demo4_Server {
4
4public static void main(String[] args) throws Exception, IOException {
44//创建 服务端 socket
44ServerSocket ss = new ServerSocket(8888);
44//不断的接收服务端的请求
44while(true){
444final Socket sc = ss.accept(); //接收到客端发送的信息
444//启动服务端多线程
444new Thread() {
4444public void run() {
44444try {
444444BufferedReader br = new BufferedReader(new InputStreamReader(sc.getInputStream())); //获取输入流
444444PrintStream ps = new PrintStream(sc.getOutputStream()); //获取输出流
444444StringBuffer sendMsg = new StringBuffer(br.readLine()).reverse(); //接收客户端的字符串并且进行翻转
444444System.out.println("服务端翻转客户端发送的字符串:" + sendMsg);
444444ps.println(sendMsg); //向客服端发送翻转轴的字符串
444444sc.close(); //关键接收客端端的socket
44444} catch (IOException e) {
444444e.printStackTrace();
44444}
4444}
444}.start();
44}
4}
}

WeiyiGeek.


练习2:
客户端向服务器上传文件

客户端:

package com.weiyigeek.net;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Demo5_Client {
4public static void main(String[] args) throws Exception, IOException {
44//(1) 客户端流程
44//1.录入上传文件的路径,并且验证该路径是否存在
44File upFile = getFile();
44//2.发送文件名称在服务端
44Socket client = new Socket("127.0.0.1",9999);
44BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); // 获取输入流
44PrintStream ps = new PrintStream(client.getOutputStream()); //获取输出流
44System.out.println("正在向服务端发送将要上传的文件名称:" + upFile.getName());
44ps.println(upFile.getName());
44
44//6.接收结果,判断服务端是否存在该文件
44 String res = br.readLine();
44 System.out.println(res);
44 if(res.equals("Found")) {
444 System.out.println("服务端已经存在该文件请不要重新上传,即将关闭Client Socket!");
444 client.close();
444 return;
44 }else {
444 System.out.println("服务端不存在该文件正在上传!");
44 }
44
44 //7.用字节流可以拷贝任何文件,使用FileInputStream读取文件并且写入到网络之中
44 FileInputStream fis = new FileInputStream(upFile);
44 byte[] arr = new byte[8192];
44 int len;
44
44 while((len = fis.read(arr)) != -1) {
444 //PrintStream 优点:可以写字符流也可以写字节流
444 //BufferedWriter : 只可以写字符流
444 ps.write(arr, 0, len);
44 }
44
44 //关闭IO文件和socket
44 fis.close();
44 client.close();
4}

4private static File getFile() {
44Scanner sc = new Scanner(System.in); //创建录入路径的对象
44System.out.print("请输入您要上传的文件路径: ");
44while(true){
444String line = sc.nextLine();
444File path = new File(line);
444if(!path.exists()) {
4444System.out.println("文件不存在请重新输入: ");
4444
444}else if (path.isDirectory()){
4444System.out.println("您输入的路径是目录不是文件请重新输入: ");
444}else {
4444return path;
444}
44}
4}
}

服务端:

package com.weiyigeek.net;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Demo5_Server {
4public static void main(String[] args) throws Exception {
44//(2) 服务端流程
44//3.创建多线程
44ServerSocket ss = new ServerSocket(9999);
44System.out.println("服务器已启动绑定端口 : 9999 --- run");
44
44//4.接收客服端的数据并读取文件名称
44while(true) {
444final Socket res = ss.accept(); //接收客服端发送的信息
444//多线程实例
444new Thread() {
4444public void run() {
44444try {
444444//需要抽取出来字节流
444444InputStream is = res.getInputStream();
444444BufferedReader br = new BufferedReader(new InputStreamReader(is)); // 获取输入流(字符流)
444444PrintStream ps = new PrintStream(res.getOutputStream()); //获取输出流
444444String fileName = br.readLine(); //读取发送的文件名
444444
444444//5.判断服务端里面文件是否存在将结果发送客户端
444444File dir = new File("update");
444444dir.mkdir();
444444File file = new File(dir,fileName); //封装成为File对象
444444if(file.exists()) {
4444444ps.println("Found"); //服务端存在!
4444444res.close();
444444}else {
4444444ps.println("NotFound"); //服务端不存在!
444444}
444444
444444//8.从网络中读取传输的文件数据,并且写入到本地之中
444444FileOutputStream fos = new FileOutputStream(file);
444444byte[] arr = new byte[8192];
444444int len;
444444while((len = is.read(arr)) != -1) {
4444444fos.write(arr,0,len);
444444}
444444System.out.println(file.getName() +"文件上传成功!");
444444//关闭文件IO和SocketIO
444444fos.close();
444444res.close();
44444} catch (IOException e) {
444444e.printStackTrace();
44444}
4444}
444}.start();
44}
4}
}

WeiyiGeek.