网络编程概述
Java是 Internet 上的语言,它从语言级上提供了对网络应用程 序的支持,程序员能够很容易开发常见的网络应用程序。
Java提供的网络类库,可以实现无痛的网络连接,联网的底层 细节被隐藏在 Java 的本机安装系统里,由 JVM 进行控制。并 且 Java 实现了一个跨平台的网络库,程序员面对的是一个统一 的网络编程环境。
网络基础
计算机网络:
把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规 模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、 共享硬件、软件、数据信息等资源。
网络编程的目的:
- 直接或间接地通过网络协议与其它计算机实现数据交换,进行通讯。
- 网络编程中有两个主要的问题:
- 如何准确地定位网络上一台或多台主机;
- 定位主机上的特定的应用 找到主机后如何可靠高效地进行数据传输
网络通信
通信双方地址
a)IP
b)端口号
一定的规则(即:网络通信协议。有两套参考模型)
c)OSI参考模型:模型过于理想化,未能在因特网上进行广泛推广
d)TCP/IP参考模型(或TCP/IP协议):事实上的国际标准。
2.1.通信要素
1:IP地址
IP 地址:InetAddress
唯一的标识 Internet 上的计算机(通信实体)
本地回环地址(hostAddress):127.0.0.1 主机名(hostName):localhost
IP地址分类方式1:IPV4 和 IPV6
IPV4:4个字节组成,4个0-255。大概42亿,30亿都在北美,亚洲4亿。2011年初已经用尽。以点分十进制表示,如192.168.0.1
PV6:128位(16个字节),写成8个无符号整数,每个整数用四个十六进制位表示, 数之间用冒号(:)分开,如:3ffe:3201:1401:1280:c8ff:fe4d:db39:1984
IP地址分类方式2:公网地址(万维网使用)和私有地址(局域网使用)。192.168. 开头的就是私有址址,范围即为192.168.0.0–192.168.255.255,专门为组织机 构内部使用
特点:不易记忆
通信要素
2:端口号
端口号标识正在计算机上运行的进程(程序)
不同的进程有不同的端口号
被规定为一个 16 位的整数 0~65535。
端口分类:
公认端口:0~1023。被预先定义的服务通信占用(如:HTTP占用端口80,FTP占用端口21,Telnet占用端口23)
注册端口:1024~49151。分配给用户进程或应用程序。(如:Tomcat占用端口8080,MySQL占用端口3306,Oracle占用端口1521等)。
动态/私有端口:49152~65535。
端口号与IP地址的组合得出一个网络套接字:Socket。
网络协议
TCP/IP协议簇
传输层协议中有两个非常重要的协议:
传输控制协议TCP(Transmission Control Protocol)
用户数据报协议UDP(User Datagram Protocol)。
TCP/IP 以其两个主要协议:传输控制协议(TCP)和网络互联协议(IP)而得名,实际上是一组协议,包括多个具有不同功能且互为关联的协议。
IP(Internet Protocol)协议是网络层的主要协议,支持网间互连的数据通信。
TCP/IP协议模型从更实用的角度出发,形成了高效的四层体系结构,即物理链路层、IP层、传输层和应用层。
Socket
利用套接字(Socket)开发网络应用程序早已被广泛的采用,以至于成为事实 上的标准。
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标 识符套接字。
通信的两端都要有Socket,是两台机器间通信的端点。
网络通信其实就是Socket间的通信。
Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
一般主动发起通信的应用程序属客户端,等待通信请求的为服务端。
Socket分类:
流套接字(stream socket):使用TCP提供可依赖的字节流服务
数据报套接字(datagram socket):使用UDP提供“尽力而为”的数据报服务
public class TCPClient {
public static void main(String[] args) throws Exception {
// 创建对象
Socket skt = new Socket("127.0.0.1", 10000);
System.out.println("服务端连接成功\n");
// 调用核心方法
TCPManager.socket(skt);
}
}
public class TCPServer {
@SuppressWarnings("resource")
public static void main(String[] args) throws Exception {
// 开启端口号 10000
ServerSocket ss = new ServerSocket(10000);
System.out.println("服务器已启动,等待客户端链接......");
// accept 接收客户端链接,程序执行到这里,该线程就会等待客户端链接
Socket skt = ss.accept();
// getInetAddress : 获取客户端的IP地址
System.out.println("客户端已链接 : " + skt.getInetAddress());
// 调用核心方法
TCPManager.socket(skt);
}
}
public class TCPManager {
/**
* 实现了多线程阻塞式接收和写出
*
* @param skt
* socket对象
* @throws Exception
*/
public static void socket(Socket skt) throws Exception {
// 获取输出流,向客户端响应数据
OutputStream os = skt.getOutputStream();
// 转换为字符输出流
OutputStreamWriter osw = new OutputStreamWriter(os, "gbk");
PrintWriter out = new PrintWriter(osw);
// 接收服务端返回的数据
InputStream is = skt.getInputStream();
InputStreamReader isr = new InputStreamReader(is, "gbk");
BufferedReader br = new BufferedReader(isr);
// 匿名内部类实现多线程写出数据
Thread outThread = new Thread(new Runnable() {
@SuppressWarnings("resource")
@Override
public void run() {
// 接收控制台输入
Scanner scanner = new Scanner(System.in);
String msg = null;
// 阻塞式输入
while ((msg = scanner.nextLine()) != null) {
// 发送给客户端
out.println(msg);
out.flush();
// 如果是拜拜 就终止该会话,该窗口就不能再进行数据输入,但是可以接收数据
if (msg.equals("拜拜")) {
break;
}
}
System.out.println("聊天已结束");
}
});
// 启动线程
outThread.start();
// main实现多线程读取数据
String line = null;
while ((line = br.readLine()) != null) {
// getInetAddress 获取Socket的IP地址
// getHostName 获取主机名字
System.out.println(skt.getInetAddress().getHostName() + " : "
+ line);
if (line.equals("拜拜")) {
break;
}
}
// 合并线程,等待写出也执行完成后,再统一关闭资源
outThread.join();
// 关闭流 先开启 后关闭
out.close();
skt.close();
br.close();
skt.close();
}
}
因为是面向连接,所以必须先启动服务端才行
UDP
类 DatagramSocket 和 DatagramPacket 实现了基于 UDP 协议网络程序。
UDP数据包通过数据包套接字 DatagramSocket 发送和接收,系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。
DatagramPacket 对象封装了UDP数据报,在数据报中包含了发送端的IP 地址和端口号以及接收端的IP地址和端口号。
UDP协议中每个数据报都给出了完整的地址信息,因此无须建立发送方和 接收方的连接。如同发快递包裹一样。
public class UDPServer {
public static void main(String[] args) throws Exception {
// 打开UDP对象,并监听某个端口
DatagramSocket ds = new DatagramSocket(10000);
// 声明数组接收数据
byte[] buf = new byte[1024];
// 声明包接收器,把接收到的数据保存到数组中
DatagramPacket dp = new DatagramPacket(buf, buf.length);
// 阻塞式接收
while (true) {
// 通过开启的端口,接收数据
ds.receive(dp);
// 把字节数组中的值取出来
ByteArrayInputStream bais = new ByteArrayInputStream(buf);
// 转换为数据流
DataInputStream dis = new DataInputStream(bais);
// 读取数据
System.out.println(dis.readUTF());
}
}
}
public class UDPClient {
public static void main(String[] args) throws Exception{
// 接收用户输入
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要发送的数据 : \n");
// 接收数据
String string =null;
while ((string = scanner.nextLine()) != null ) {
// 发送数据
// 字节数组输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 目的 : 保存类型
DataOutputStream dos = new DataOutputStream(baos);
// 写出数据同时保存类型
dos.writeUTF(string);
// 把数据流转换为字节数组
byte[] buf = baos.toByteArray();
// 数据传输
// 把数据转换为数据包,然后打包发送,把目标IP和端口绑定上去
DatagramPacket dp = new DatagramPacket(buf, buf.length,new InetSocketAddress("127.0.0.1", 10000));
// 传输,通过指定端口把数据传输出去
DatagramSocket ds = new DatagramSocket(9999);
ds.send(dp);
ds.close();
if (string.equals("拜拜")) {
System.out.println("通话结束");
break;
}
}
}
}
TCP 和 UDP
TCP协议:
使用TCP协议前,须先建立TCP连接,形成传输数据通道
传输前,采用“三次握手”方式,点对点通信,是可靠的
TCP协议进行通信的两个应用进程:客户端、服务端。
在连接中可进行大数据量的传输
传输完毕,需释放已建立的连接,效率低
UDP协议:
将数据、源、目的封装成数据包,不需要建立连接
每个数据报的大小限制在64K内
发送不管对方是否准备好,接收方收到也不确认,故是不可靠的
可以广播发送
发送数据结束时无需释放资源,开销小,速度快
正则表达式
正则表达式定义了字符串的模式。
正则表达式可以用来搜索、编辑或处理文本。
* 正则表达式定义了字符串的匹配模式,可以用来搜索,编辑, 并且不仅限于某一种语言,每种语言中都有细微的差别
*
* 常用
* \ : 转移符,把有意义字符转换为无意义字符,
* java中 \ 也是转移符,所以在java 中操作正则表达式中的转义的时候,需要\\ 两个 \
* 字符的取值范围
* [abc] : 匹配 a或b或c中的任意一个
* [0-9] : 匹配一个数字
* [a-z] : 匹配一个小写字母
* [a-zA-Z] : 匹配一个大小写字母
*
* 简洁表示
* . : 匹配任意字符
* \d : 表示数字[0-9]
* \D : 表示非数字[^0-9]
* \s : 表示由空字符组成[ \t\n\r\x\f]
* \S : 表示非空
* \w : 表示 字母,数字,下划线
* \W : 表示非字母,数字,下划线
*
* 数量表示
* * : 表示出现0到n次
* ? : 表示 0到1次
* + : 表示1到n次
* {n} : 表示n次
* {n,} : 表示n次及n次以上
* {n,m} : 表示n到m次
*
* 匹配整数和小数(可以是负数)
* ^-?\d+(\.\d+)?$
*
* 校验电话
* \d{11}
* ^1[3456789]\d{9}$
*
* PatternSynctaxException : 正则表达式异常类
* Pattern : 创建正则表达式,正则表达式引擎,能做一些简单的匹配操作
* Matcher : 支持强大的正则表达式的匹配操作
*
* 有时候我们也会使用String里面的方法进行操作
* 验证 : boolean matches(String regex);
* 拆分 : String[] split(String regex);
* 替换 : String replaceAll(String regex,String replacement);
Pattern类
用于创建一个正则表达式,也可以说创建一个匹配模式,它的构造方法是私有的,不可以直接创建
可以通过Pattern.complie(String regex)创建一个正则表达式
只能做一些简单的匹配操作
//分割字符串
public static void test1(){
String str = "1,2,3,4,5";
// String
String[] arr1 = str.split(",");
// Pattern
// 创建对象
Pattern pattern = Pattern.compile(",");
String[] arr2 = pattern.split(str);
for (String string : arr2) {
System.out.println(string);
}
}
//全词匹配
public static void test2(){
String str = "-12345678910.2";
// 校验数字
String regex = "-?\\d+(\\.\\d+)?";
// String
System.out.println(str.matches(regex));
// Pattern
System.out.println(Pattern.matches(regex, str));
}
Matcher类
构造方法也是私有的,不能随意创建,只能通过Pattern.matcher(CharSequence input)方法得到该类的实例 Matcher m = p.matcher(“aaaaab”);
支持便捷强大的正则匹配操作,包括分组、多次匹配支持
public class MatcherTest {
public static void main(String[] args) {
String tel = "a13113113111a";
String regexTel = "\\d{11}";
// 创建引擎
Pattern pattern = Pattern.compile(regexTel);
// 创建匹配器
Matcher matcher = pattern.matcher(tel);
/**
* 提供了三种匹配方式
*
* matches : 全词匹配
*
* find : 在任意位置都可以
*
* lookingAt : 从前往后匹配
*
*/
// 全词
System.out.println(matcher.matches());
// 注意 一个matcher匹配器,只调用一个方法,可以调用同一个方法多次,不要混着调用,多次调用不同的方法 需要重新生成匹配器
matcher = pattern.matcher(tel);
// 任意位置
System.out.println(matcher.find());
matcher = pattern.matcher(tel);
// 从前往后(最前面的数据符合即可)
System.out.println(matcher.lookingAt());
/**
* find和group 连用 可以做到数据提取
*
* 可以使用 () 进行分组,一个() 就是一组
*
* 也可以不分组,不分组就只能获取匹配的整个数据,不能获取匹配数据中的某一部分
*/
regexTel = "((.{2,3})电话号码是(\\d{11}))";
tel="小明电话号码是13113113111张晓红电话号码是15115115111";
pattern = Pattern.compile(regexTel);
matcher = pattern.matcher(tel);
while (matcher.find()) {
// 参数是0 或者无参 都是获取匹配的整个数据
// 参数是1 表示获取第一组,2表示获取第二组...
// System.out.println(matcher.group(1));
String name = matcher.group(2);
String telString = matcher.group(3);
System.out.println(name+" : "+telString);
// 获取匹配元素的起始索引
System.out.println(matcher.start());
// 获取匹配元素的结束索引
System.out.println(matcher.end());
}
}
}