网络编程
网络通讯协议
- 位于同一网络的计算机在进行连接和通讯时需要遵循一定的规则。这些连接和通讯的规则被称为网络通讯协议。
- 网络通讯协议:对数据的传输格式、传输速率,传输步骤都做了统一规定,通讯双方必须同时遵守才可以完成数据交换。
- TCP/IP协议:
- 应用层(HTTP,FTP,DNS)
- 主要负责应用程序的协议,
- 传输层(TCP,UDP)
- 主要使网络程序进行通讯,在进行网络通讯时,可以采用TCP协议,也可以采用UDP协议
- 网络层(IP,ICMP,IGMP)
- 网络层是整个TCP/IP的核心,它主要用于将传输数据进行分组,将分组数据发送到目标计算机或者网络
- 链路层(驱动程序,接口)
- 用于定义物理传输通道,通常是对某网络连接设备的驱动协议,例如针对光纤,网络提供的驱动
- 应用层(HTTP,FTP,DNS)
- IP地址和端口号
- TCP/IP协议中,IP地址唯一标识一台计算机
- IP地址广泛使用IPv4--4个字节大小的二进制数来表示--为了便于记忆,将每一个字节写成十进制数来表示(每个字节0~255),数字间用.分隔开
- 随着计算机网络的不断扩大,对IP地址的需求不断扩大,IPv4的资源面临枯竭,因此IPv6应运而生。IPv6使用了16个字节来表示IP地址,他所拥有的的容量是IPv4的8*1028倍,达到2128个(算上全0的)
- 通过IP地址可以连接到指定的计算机,但是要访问计算机的某个应用程序,还需要指定端口号。在计算机中,不同的应用程序是通过端口号区分的,端口号是用两个字节(16位)表示的,它的取值是0~65535。0~1023之间的端口号用于一些知名的网络服务和应用,用户的普通应用程序用1024以上的端口,从而避免被另外一个应用或服务所占用。
- 【源计算机】—(IP地址)—>【目标计算机】—(端口号)—>【应用程序】
- TCP/IP协议中,IP地址唯一标识一台计算机
- InetAddress
- 该类用于封装一个IP地址,并提供了一系列的与IP地址相关的方法
- getByName(String host)给定主机名获取主机的IP地址
- getLocalHost()返回本地主机
- getHostName()返回此IP地址的主机名
- getHostAddress()返回IP地址字符串
- 实例
//获取InetAddress对象 InetAddress local=InetAddress.getLocalHost(); InetAddress remote=InetAddress.getByName("www.itcast.cn"); //获取IP地址 local.getHostAddress(); remote.getHostAddress(); //获取主机名 remote.getHostName();
- 该类用于封装一个IP地址,并提供了一系列的与IP地址相关的方法
TCP/UDP协议
-
UDP协议
- User Datagram Protocol用户数据报协议
- 无连接通讯协议--数据的发送端和接收端不建立逻辑连接:发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
- 优点:消耗资源小,通讯效率高
- 适用:音频,视频,普通数据---丢失几个数据不会产生太大的影响
- 缺点:UDP面向无连接性,不能保证数据的完整性--在传输重要数据时,不建议使用UDP协议。
-
Tcp协议
- Transmission Control Protocol传输控制协议
- TCP是面向连接的通讯协议,即在传输数据前先在发送端和接收端建立逻辑连接,然后再传输数据。
- 无差错数据传输
- 每次创建连接--三次握手
- 第一次握手:客户端向服务器发出连接请求,等待服务器的确认
- 第二次握手:服务器向客户端发送一个响应,通知客户端接收到了请求
- 第三次握手:客户端再次向服务器端发送确认信息,确认连接
- TCP面向连接的特性,它可以保证传输数据的安全性,所以是一个被广泛采用的协议--下载文件时必须采用TCP协议
UDP通讯
- 生活小例子
- 货运公司发送货物,集装箱来装载货物---这里的集装箱在程序中用DatagramPacket----码头DatagramSocket
- 构造函数--发送方和接收方不同
- 发送方:字节数组,IP地址,端口号DatagramPacket(byte[] buf,int length)
- 接收方:只需一个字节数组DatagramPacket(byte[] buf,int length,InetAddress address,int port)
- DatagramPacket常用方法
- getAddress()
- 返回某台机器的IP地址,此数据报包要发往该机器或者是从该机器接收到的
- getPort()
- 返回某台远程主机的端口号,此数据包将要发往该主机或者是从该主机接收到的
- getData()
- 返回数据缓冲区
- getLength()
- 返回将要发送或接受到的数据的长度
- getAddress()
- DatagramSocket
- 运货的码头
- 接受和发送DatagramPacket数据包
- 构造函数--发送方和接收方不同
- 发送端:DatagramSocket(),不指明端口,此时系统会分配一个没有被其他网络程序所使用的端口
- 接收端(或接收端):DatagramSocket(int port),即可以创建接收端,又可以创建发送端
- 常用方法
- receive(DatagramPacket p)--此套接字用于接收数据包
- send(DatagramPacket p)--从套接字发送数据包
-
UDP网络程序
- 通讯时,只有接收端程序先运行才可以避免因发送端发送的数据而无法接收,而造成数据丢失
-
发送端
package com.peng.demo; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; /** * @author kungfu~peng * @data 2017年9月28日 * @description 接收端 */ public class SocketDemo { public static void main(String[] args) throws Exception { // 1.创建DatagramSocket对象,并指定端口号 DatagramSocket receiveSocket = new DatagramSocket(12307); // 2.创建DatagramPacket对象,创建一个空的仓库 byte[] buffer = new byte[1024]; DatagramPacket dp = new DatagramPacket(buffer, 1024); // 3.接收数据存储到DatagramPacket对象中 receiveSocket.receive(dp); // 4.获取DatagramPacket对象的内容 InetAddress ipAddress = dp.getAddress();// 谁发来的数据 String ip = ipAddress.getHostAddress();// 获取ip地址 byte[] data = dp.getData();// 发来了什么数据 int length = dp.getLength();// 数据的长度 String dataStr = new String(data, 0, length);// 将数据转化为字符串 System.out.println("ip地址:" + ip + ",数据时:" + dataStr); // 释放流资源 receiveSocket.close(); } }
- 发送端
package com.peng.demo; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; /** * @author kungfu~peng * @data 2017年9月28日 * @description 发送端 */ public class SocketDemo1 { public static void main(String[] args) throws Exception { // 1.创建DatagramSocket对象 DatagramSocket sendSocket = new DatagramSocket(); // 2.创建DatagramPacket对象,并封装数据--构造数据包,用长度为length的包发送到主机上的指定端口号 byte[] buffer = "Hello,UDP,你好!".getBytes(); DatagramPacket dp = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("192.168.43.242"), 12307); // 发送数据 sendSocket.send(dp); // 释放流资源 sendSocket.close(); } }
TCP通讯
- TCP严格区分客户端与服务端,在通讯中,必须先由客户端去连接服务器端才能实现通讯,服务端不可以主动连接客户端,并且服务器端程序需要事先启动,等待客户的连接。
- ServerSocket表示服务器
- Socket表示客户端
- 通信
1. 首先创建代表服务器的ServerSocket对象,该对象相当于开启一个服务,并等待客户的连接
2. 然后创建代表客户端的Socket对象向服务器端发出连接请求
3. 服务器端响应请求,两者建立连接,开始通信
- ServerSocket
- 构造方法ServerSocket(int port)
- 常用方法
- accept()侦听并接收此套接字的连接
- getInetAddress()返回此套接字的本地地址
- 流程
1. ServerSocket对象负责监听计算机的某个端口号 2. 调用对象的accept()方法,接收来自客户端的请求-----服务端程序会发生阻塞 3. 直到客户端发出连接请求,accept()方法才会返回一个Socket对象用于和客户端实现通信
- Socket
- TCP实现TCP客户端程序
- 构造函数
- Socket(String host,int port)
- Socket(IpnetAddress address,int port)
- 常用方法
- getPort()Socket对象与服务器连接的端口号
- getLocalAddress()获取Socket对象绑定的本地IP地址并封装成InetAddress对象
- close()该方法关闭Socket连接,
- getInputStream()该方法返回输入流对象-----服务器和客户端都可以有,用于读取数据
- getOutputStream该方法返回输出流对象-----服务器和客户端都可以有,用于写入数据
-
简单的TCP程序
-
服务端
package com.peng.demo; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; /** * @author kungfu~peng * @data 2017年9月28日 * @description tcp服务端 */ public class TcpServer { public static void main(String[] args) throws Exception { // 1.创建服务器ServerSocket对象(指定服务器端口号) ServerSocket ss = new ServerSocket(8888); // 2.开启服务器,等待客户端的连接,当客户端连接后,可以获取到连接到服务器端的的客户端Socket对象 Socket s = ss.accept(); // 3.给客户端反馈信息--获取客户端的输出流,写数据给客户端 OutputStream out = s.getOutputStream(); out.write("已经连接到客户端!".getBytes()); // 4.关闭流资源 out.close(); s.close(); // ss.close();//通常服务器是不关闭的 } }
-
客户端
package com.peng.demo; import java.io.InputStream; import java.net.Socket; /** * @author kungfu~peng * @data 2017年9月28日 * @description tcp客户端 */ public class TcpClient { public static void main(String[] args) throws Exception { // 1.创建客户端Socket对象,指定服务器地址与端口号 Socket s = new Socket("192.168.43.242", 8888); // 2.获取服务器端反馈回来的数据 InputStream in = s.getInputStream(); // 3.获取流中的数据 byte[] buffer = new byte[1024]; // 把流中的数据存储到数组中,并记录读取字节的个数 int length = in.read(buffer); // 显示数据 System.out.println(new String(buffer, 0, length)); // 关闭流 in.close(); s.close(); } }
-
补充
- 套接字
- 实际上是一套用于网络通讯的API,本质上是一套基于网络传输数据的流
- IP地址--IPv4--由四组组成的IP地址,每组范围0-255(232)
- IP地址--IPv6--由6组组成的IP地址,每组范围0000-ffff(296)
- 端口--用于和外界进行信息交互的媒介--0~65535,其中0~1024是已经被计算机内部和常用应用占用;访问网站的默认端口为80
- 255.255.255.255广播地址,只要在同一个网段中,都可以接收到
- UDP每次发送数据不超64k,TCP每次方式理论上无限制
- TCP中:
- socket.connect(new InetSocketAddress(String address,int port)//连接
- socket.shutdownOutput();//给一个数据发送结束标志
- 阻塞:receive;connect;accept;read;write;
- 扩展:BIO阻塞式IO,同步式阻塞式IO--BlockingIO
- 扩展:NIO--JDK1.4--非阻塞式IO--NonBlockingIO
- 扩展:AIO--AsynchronousIO--异步式非阻塞IO---JDK1.8
-
UDP命令式输入
package com.peng.threaddemo; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.util.Scanner; /** * @author kungfu~peng * @data 2017年11月11日 * @description */ public class ThreadDemo { public static void main(String[] args) { new Thread(new Udp_Server()).start(); new Thread(new Udp_Client()).start(); } } class Udp_Client implements Runnable { // 输入流 Scanner scanner = new Scanner(System.in); // 命令 String order = "over"; // 端口 DatagramSocket ds = null; // 数据包 DatagramPacket dp = null; // 聊天内容 String temp = "hello!"; // 退出标志 boolean flag_exit = false; @Override public void run() { try { // 创建端口 ds = new DatagramSocket(); while (!flag_exit) { System.out.println("请输入命令:退出--over,进入--in"); order = scanner.nextLine(); // 获得命令 if ("over".equals(order)) { System.out.println("Byebye~~"); flag_exit = true; ds.close(); } else if ("in".equals(order)) { while (true) { System.out.println("请输入具体内容,over结束"); temp = scanner.nextLine(); // 判断输入的内容 if ("over".equals(temp)) { System.out.println("Byebye~~"); flag_exit = true; ds.close(); break; } // 制作数据包 dp = new DatagramPacket(temp.getBytes(), temp.getBytes().length, new InetSocketAddress( "127.0.0.1", 6666)); // 发送数据包 ds.send(dp); } } } } catch (Exception e) { e.printStackTrace(); } } } class Udp_Server implements Runnable { DatagramSocket ds = null; DatagramPacket dp = null; @Override public void run() { try { // 套接字 ds = new DatagramSocket(6666); // 常开服务器,接收数据 while (true) { // 数据包 dp = new DatagramPacket(new byte[1024], 1024); // 接收数据 ds.receive(dp); // 打印数据 System.out.println("Client Say:" + new String(dp.getData(), 0, dp.getLength())); } } catch (Exception e) { e.printStackTrace(); } } }
文件的上传
- 文件名一致
- 合并:文件名+数据-->和
- 分开:和-->文件名+数据
package com.peng.threaddemo;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author kungfu~peng
* @data 2017年11月11日
* @description 客户端文件的上传与下载
*/
public class FileSocket {
public static void main(String[] args) {
new Thread(new FileServer()).start();
new Thread(new FileClient(new File(
"C:\\Users\\Administrator.PC-20160710IJPJ\\Desktop\\校招.md")))
.start();
}
}
/**
*
* @author kungfu~peng
* @data 2017年11月11日
* @description文件上传
*/
class FileClient implements Runnable {
File file = null;
// 构造函数--将上传的文件传入
public FileClient(File file) {
this.file = file;
}
@Override
public void run() {
try {
// 准备端口
Socket socket = new Socket("127.0.0.1", 6666);
// 获得文件的输入流进行读取文件
InputStream is = new FileInputStream(file);
// 获取端口输出流
OutputStream os = socket.getOutputStream();
// 加入文件名和特殊字符【文件名***】
os.write((file.getName() + "666").getBytes(), 0,
(file.getName() + "666").getBytes().length);
// 将文件写出
byte[] data = new byte[1024];
int len = -1;
// 开始写到文件到流中
while ((len = is.read(data)) != -1) {
os.write(data, 0, len);
}
// 文件名的上传
socket.shutdownOutput();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
*
* @author kungfu~peng
* @data 2017年11月11日
* @description服务端文件的获取
*/
class FileServer implements Runnable {
@Override
public void run() {
try {
// 获得服务器端的端口
ServerSocket ss = new ServerSocket(6666);
// 获得发来数据的端口
Socket s = ss.accept();
// 获得输入流进行读取数据
InputStream is = s.getInputStream();
// 准备数据
byte[] data = new byte[1024];
int len = -1;
String temp = "";
// 获得文件的数据字符串
while ((len = is.read(data)) != -1) {
temp += new String(data, 0, len);
}
// 拆分文件名和数据
String[] data22 = temp.split("666");
System.out.println(data22[0]);
// 写数据到该文件
File file = new File("E:\\" + data22[0]);
// 将文件写出到特定的位置--获取文件名+获取具体的数据
OutputStream os = new FileOutputStream(file);
os.write(data22[1].getBytes());
} catch (Exception e) {
e.printStackTrace();
}
}
}