网络编程的三要素
协议 ip地址 端口号
举例:
1)找到美女---->ip地址
2)对它说话 ----(耳朵说)----端口号
3)找到她了,对她说: ---->协议
i love you(比如:不懂英语,说中文)
网络协议 UDP和TCP
UDP和TCP协议的区别:
1)是否需要建立连接通道
UDP:不需要建立通道 (QQ聊天,发短信)
TCP:需要建立连接通道(打电话…)
2)是否是可靠连接(是否安全)
UDP:是一种不可靠连接,不安全–执行效率高
TCP:是一种可靠连接,服务器端一直阻塞状态(同步的—安全性),执行效率低
三次握手,四次挥手!
3)共同点
UDP/TCP —两端都需要有Socket(Socket编程)
应用协议
http协议
https协议(比上http协议)
第一要素: ip地址
192.168.138.1: 使用点分十进制法
A类IP地址:第一段号码为网络号码,剩下的三段号码为本地计算机的号码 (政府部门)
B类IP地址:前二段号码为网络号码,剩下的二段号码为本地计算机的号码 (大学校园)
C类IP地址:前三段号码为网络号码,剩下的一段号码为本地计算机的号码(私人地址)
127.0.0.1:回环地址:表示本机 ---- 域名:localhost
xxx.xxx.xxx.255 广播地址
第二个要素:端口号
使用360软件—查看当前计算机中每个软件 的端口号
有效端口号:0-65535
0-1024:保留端口号
http://www.baidu.com
http://192.168.25.1:80(可以不写)/xx
一般:80端口号:是不写的(省略)
常见端口号
tomcat: 8080
redis:6575.. (数据库---非关系型数据库 key-value )
mysql软件:3306
java.net.InetAddress类:互联网ip地址统称
这个类没有构造方法,不能直接创建对象!,提供一些成员方法使用:静态的
Runtime类: 单例模式
- 提供静态方法,返回该类本身
- public static InetAddress getByName(String host):
- 参数为:主机名称:
- 成员方法
- public String getHostAddress()返回 IP 地址字符串(以文本表现形式)。
- public String getHostName():获取主机名
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
//获取ip地址:字符串形式
//public static InetAddress getByName(String host)
// InetAddress inetAddress = InetAddress.getByName("LAPTOP-BHL76J0S") ;
InetAddress inetAddress = InetAddress.getByName("10.12.156.36") ;
// * public String getHostAddress()
String ip = inetAddress.getHostAddress() ;
System.out.println(ip);//10.12.156.36
//public String getHostName()
String name = inetAddress.getHostName() ;
System.out.println(name);//APTOP-BHL76J0S
}
}
实例:使用UDP实现客户端和服务端
UDP协议发送端的步骤
- 1)创建发送端的Socket对象
- 2)数据数据报包对象:DatagramPacket
- 3)发送数据
- 4)关闭资源
public class SendDemo {
public static void main(String[] args) throws IOException {
//1)创建发送端的Socket对象
//DatagramSocket
//此类表示用来发送和接收数据报包的套接字。
//public DatagramSocket()
DatagramSocket ds = new DatagramSocket() ;
//2)创建数据数据报包对象:DatagramPacket
//数据报包用来实现无连接包投递服务
//public DatagramPacket(byte[] buf,
// int length,
//InetAddress address,
//int port)
//参数1:当前发送数据的字节数组
//参数2:当前数据的实际长度
//参数3:ip地址对象
//参数4:端口号:0-1024保留端口 (0-65535)
String s = "hello,udp,我来了" ;
byte[] bytes = s.getBytes() ;
int length = bytes.length ;
DatagramPacket dp = new DatagramPacket(bytes, length,
InetAddress.getByName("10.12.156.36"), 10086) ;
//3)发送数据报包
//public void send(DatagramPacket p)
ds.send(dp);
//释放资源
ds.close();
}
}
UDP协议接收端的实现步骤
- 1)创建接收端的Socket对象,绑定端口号
- 2)创建一个数据报包—DatagramPacket:当前接收容器
- public DatagramPacket(byte[] buf,int length)
- 3)接收数据
- 4)从接收容器中解析实际数据
- 5)展示数据
public class ReceiveDemo {
public static void main(String[] args) throws IOException{
//1)创建接收端的Socket对象,绑定端口号
// public DatagramSocket(int port)
DatagramSocket ds = new DatagramSocket(10086) ;
//2)创建一个数据报包---DatagramPacket:当前接收容器
// public DatagramPacket(byte[] buf,int length)
byte[] bytes = new byte[1024] ;
int length = bytes.length ;
DatagramPacket dp = new DatagramPacket(bytes, length) ;
//3)接收数据
// public void receive(DatagramPacket p)
ds.receive(dp);
//4)解析当前接收容器中的实际数据
//public byte[] getData():获取缓冲区数据中实际字节数组
//public int getLength():获取缓冲区中实际长度
byte[] buf = dp.getData() ;
int length2 = dp.getLength() ;
//获取接收端发送的数据:ip地址
// public InetAddress getAddress()
InetAddress address = dp.getAddress() ;
String ip = address.getHostAddress() ;
//展示数据
String s = new String(buf,0,length2) ;
System.out.println("data is: "+s+",from "+ip);
//关闭资源
ds.close();
}
}
客户端与服务器端升级版:
需求
- UDP
- 发送端键盘录入数据,接收端不断接收数据(不关闭)
分析
键盘录入数据
- Scanner
- 使用字符流的方式
- BufferedReader(new InputStreamReader(System.in))
接收端不断接收收据,并解析
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建一个接收端的Socket
DatagramSocket ds = new DatagramSocket(10000) ;
//不断接收数据
while(true) {
//创建接收容器
byte[] bytes = new byte[1024] ;
int length = bytes.length ;
DatagramPacket dp = new DatagramPacket(bytes , length) ;
//接收
ds.receive(dp);
//解析真实数据
String str = new String(dp.getData(), 0, dp.getLength()) ;
//获取ip地址
String ip = dp.getAddress().getHostAddress() ;
//展示数据
System.out.println("data is :"+str+"from "+ip);
}
//接收端不关闭
}
}
发送端
public class SendDemo {
public static void main(String[] args) throws IOException {
//1)创建发送端的Socket对象
DatagramSocket ds = new DatagramSocket() ;
//2)键盘录入数据
//创建bufferedReader类对象
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in)) ;
//一次读取一行内容
String line = null ;
while((line=br.readLine())!=null) {
//自定义结束条件
if(line.equals("886")) {
break ;
}
//line:发送的数据
byte[] bytes = line.getBytes() ;
int length = bytes.length ;
//创建数据报包对象
DatagramPacket dp = new DatagramPacket(
bytes,
length,
InetAddress.getByName("10.12.156.36"),
10000) ;
//发送数据
ds.send(dp);
}
//释放资源
ds.close();
}
}
UDP和线程
需要在一个窗口下进行聊天,发送和接收数据
- 发送端会开启发送端的线程!
- 接收端开启接收端的线程!
多线程的实现方式:
- 1)Thread类:继承自它
- 2)实现Runnable接口(静态代理)
- 3)线程池
分析:
- 1)发送端和接收端都需要有Socket 都需要在当前用户线程(main)中创建出来
- 2)多线程实现方式2
- SendThread /ReceiveThread 实现Runnable接口重写run方法 (这两个作为资源类)
- 这两个资源类对象需要将上面Socket对象传递进来
- 3)创建Thread类对象,将上面的SendThread和ReceiveThread作为参数传递
- 4)启动线程
接收端的资源类
public class ReceiveThread implements Runnable {
private DatagramSocket ds ;
public ReceiveThread(DatagramSocket ds) {
this.ds = ds ;
}
@Override
public void run() {
try {
// 不断接收数据
while (true) {
// 创建接收容器
byte[] bytes = new byte[1024];
int length = bytes.length;
DatagramPacket dp = new DatagramPacket(bytes, length);
// 接收
ds.receive(dp);
// 解析真实数据
String str = new String(dp.getData(), 0, dp.getLength());
// 获取ip地址
String ip = dp.getAddress().getHostAddress();
// 展示数据
System.out.println("data is :" + str + "from " + ip);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
发送端的资源类
public class SendThread implements Runnable {
private DatagramSocket ds ;
public SendThread(DatagramSocket ds) {
this.ds = ds;
}
@Override
public void run() {
try {
// 键盘录入数据
// 创建bufferedReader类对象
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
// 一次读取一行内容
String line = null;
while ((line = br.readLine()) != null) {
// 自定义结束条件
if (line.equals("886")) {
break;
}
// line:发送的数据
byte[] bytes = line.getBytes();
int length = bytes.length;
// 创建数据报包对象
DatagramPacket dp = new DatagramPacket(bytes, length,
InetAddress.getByName("10.12.156.36"), 12306);
// 发送数据
ds.send(dp);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(ds!=null) {
ds.close();
}
}
}
}
测试类
public class ChatRoom {
public static void main(String[] args) {
try {
//发送端的Scoket
DatagramSocket sendDs = new DatagramSocket() ;
//接收端Socket
DatagramSocket receDs = new DatagramSocket(12306) ;
//创建资源类对象:多线程的方式2
SendThread st = new SendThread(sendDs) ;
ReceiveThread rt = new ReceiveThread(receDs) ;
//创建Thread类对象
Thread t1 = new Thread(st) ;
Thread t2 = new Thread(rt) ;
t1.start();
t2.start();
} catch (SocketException e) {
e.printStackTrace();
}
}
}
TCP协议
基本使用:
客户端的实现步骤
- 1)创建客户端的Socket对象 java.net.Socket(套接字)
- public Socket(String host,int port)
- 2)获取客户端通道内输出流,写入内容
- 3)关闭资源
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端的Socket对象
//public Socket(String host,int port)
Socket s = new Socket("10.12.156.36", 8888) ;
//获取客户端通道内输出流,写入内容
//public OutputStream getOutputStream()
OutputStream out = s.getOutputStream() ;
out.write("hello,TCP,我来了".getBytes());
//释放资源
s.close();
}
}
服务器端基本使用
步骤
- 1)创建服务器端的Socket对象,绑定端口
- 2)进入阻塞状态,监听客户端连接!
- 3)获取动态内输入流,读数据
- 4)展示数据
- 5)释放资源
public class ServerDemo {
public static void main(String[] args) throws IOException {
//1)创建服务器端的Socket对象,绑定端口
//public ServerSocket(int port)
ServerSocket ss = new ServerSocket(8888) ;
//2)进入阻塞状态,监听客户端连接!
//public Socket accept()
Socket s = ss.accept() ;
//3)获取动态内输入流,读数据
//public InputStream getInputStream()
InputStream in = s.getInputStream() ;
//读数据
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = in.read(bytes) ;
//展示数据
String clienStr = new String(bytes,0,len) ;
//public InetAddress getInetAddress()
String ip = s.getInetAddress().getHostAddress() ;
//输出
System.out.println("data is:"+clienStr+",from :"+ip);
//释放资源
ss.close();
}
}
加入反馈
客户端发送数据"hello,Server,我来了"
- 服务器端将数据读取出来,展示出来
- 服务器端还需要加入反馈操作,“我收到了”
- 客户端需要将反馈信息读取 出来
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端的socket
Socket socket = new Socket("10.12.156.36",6666) ;
//获取通道内的输出流
OutputStream out = socket.getOutputStream() ;
out.write("hello,Server,我来了".getBytes());
//读取服务器端的反馈数据
//获取通道内的输入流对象
InputStream in = socket.getInputStream() ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = in.read(bytes) ;
//展示数据
String serverStr = new String(bytes, 0, len) ;
System.out.println(serverStr);
//释放资源
socket.close();
}
}
服务器端还需要加入反馈操作,“我收到了”
- 客户端需要将反馈信息读取 出来
- 注意事项:
- 服务器端不要开启多次,就会出现BindException:绑定异常: 端口号被占用!
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket ss = new ServerSocket(6666) ;
//监听客户端的链接
Socket socket = ss.accept() ;
//获取通道输入流,读取数据
InputStream in = socket.getInputStream() ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = in.read(bytes) ;
//展示数据:客户端发来的数据
String clientStr = new String(bytes, 0, len) ;
System.out.println(clientStr);
//服务器端反馈给客户端
//获取通道内的输出流对象
OutputStream out = socket.getOutputStream() ;
out.write("数据已经收到了".getBytes());
//释放资源
ss.close();
}
}
加入键盘输入
需求
- 键盘录入:BufferedReader
- 1)客户端不断键盘录入数据,服务器端不断将数据展示在控制台上
public class ClientTest {
public static void main(String[] args) throws IOException {
//创建客户端Socket
Socket socket = new Socket("10.12.156.36", 2222) ;
//)客户端不断键盘录入数据
//创建字符输入流:BufferedReader
BufferedReader br =
new BufferedReader(new InputStreamReader(System.in)) ;
//OutputStream getOutputStream():通道内的字节输出流
//输出流需要和BufferedReader对应: BufferedWriter:字符输出流
//将节输出流封装成BuffferedWriter :字符流通向字节流的桥梁 OutputStreamWriter
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())) ;
//一次读取一行数据:键盘录入 数据,写入到BufferedWriter中
String line = null ;
while((line=br.readLine())!=null) {
//结束条件
if(line.equals("over")) {
break ;
}
//录入一行,写入到bw流中
bw.write(line);
bw.newLine();
bw.flush();
}
//释放资源
socket.close();
}
}
服务器端不断将数据展示在控制台上
public class ServerTest {
public static void main(String[] args) throws IOException {
//创建服务器端的Socket对象
ServerSocket ss = new ServerSocket(2222) ;
//监听客户端连接
Socket socket = ss.accept() ;
//不断的去读取数据
//获取通道内输入流:将封装 成BufferedReader
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())) ;
String line = null ;
while((line=br.readLine())!=null) {
//输出数据
System.out.println(line);
}
//服务器端不关闭
}
}
文件操作
客户端将当前项目下ReceiveDemo.java文件 写入到通道内的流中
服务器端将客户端的文本进行复制:到当前项目Copy.java文件中
加入服务器端的反馈?
问题:
服务器端和客户端程序没有结束,但是文件已经复制完毕!
String readLine() :返回为null,仅仅表示文件已经读完了,
但是服务器端不知道客户端的文件是否已经写入到通道流中(是否应写入完毕),就等待着客户端告诉服务器端"已经写入完毕"
当前客户端没有告诉服务器端,是否写入完毕,那么客户端也一直等待着服务器的反馈,就出现互相等待了!
解决方案
1)在客户端自定义一个结束标记
通道输出流(BufferedWriter)写入"over",服务器端读取到"over",直接结束!
2)在客户端Socket中:结束的方法 “告诉服务器,这里面没有内容写入”
public void shutdownOutput()
public class UploadClient {
public static void main(String[] args) throws IOException {
//创建Scoket对象
Socket s = new Socket("10.12.156.36", 5555) ;
//创建字符输入流BufferedReader
BufferedReader br = new BufferedReader
(new FileReader("ReceiveDemo.java")) ;
//封装通道内的字节输出流
BufferedWriter bw =
new BufferedWriter(
new OutputStreamWriter(s.getOutputStream())) ;
//将文件的内容写到bw流对象中
String line = null ;
while((line=br.readLine())!=null) { //readLine():阻塞式方法
//写入
bw.write(line);
bw.newLine();
bw.flush();
}
/**
* 方案1
* //自定义一个结束标记
bw.write("over");
bw.newLine();
bw.flush();
*/
//方案2
// public void shutdownOutput()://禁用此套接字的输出流:告诉服务器端,没有内容写入到通道流中
s.shutdownOutput();
//获取通道内的字节输入流,读取服务器端的反馈
//将字节输入流---封装BufferedReader
BufferedReader br2 = new BufferedReader
(new InputStreamReader(s.getInputStream())) ;
//readLine()
String serverMsg = br2.readLine() ;
System.out.println(serverMsg);
//关闭
br.close();
s.close();
}
}
public class UploadServer {
public static void main(String[] args) throws IOException{
//创建ServerSocket对象
ServerSocket ss = new ServerSocket(5555) ;
//监听链接
Socket socket = ss.accept() ;
//封装通道内的字节输入流
BufferedReader br = new BufferedReader
(new InputStreamReader(socket.getInputStream())) ;
//创建BufferedWriter将流中数据进行复制
BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java")) ;
String line = null ;
while((line=br.readLine())!=null) {//阻塞式方法
/*
//读取客户端的自定义标记
if("over".equals(line)) {
break ;
}
*/
bw.write(line);
bw.newLine();
bw.flush();
}
//客户端的文本文件,服务器端输出到另一个文件中,如果复制完毕
//加入服务器端的反馈?
//获取通道内的字节输出流---封装成BufferedWriter
BufferedWriter bw2 = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())) ;
bw2.write("文件复制完毕");
bw2.newLine();
bw2.flush();
//关闭
bw.close();
socket.close();
}
}
图片的复制:BufferedInputStream/BufferedOutputStream
一次读取一个字节数组
- 客户端的一个图片文件:当前项目下xxx.jpg
- 服务器端将图片文件:进行复制:mv.jpg
- 加入服务器端的反馈
- 发现问题:
- 图片文件:没有复制完整,图片文件缺失(少字节数)
- 图片文件本身在内存中:缓存数据!
- 字节缓冲输出流中:public void flush():强制将缓冲的字节数输出到流中!
public class UploadImageClient {
public static void main(String[] args) throws IOException {
//创建Scoket
Socket s = new Socket("10.12.156.36",6666) ;
//创建字节输入流:封装图片文件
BufferedInputStream bis =
new BufferedInputStream(new FileInputStream("高圆圆.jpg")) ;
//封装通过的字节输出流
BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream()) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len=bis.read(bytes))!=-1) {
bos.write(bytes, 0, len);
//刷新
bos.flush();
}
//告诉服务器端,图片文件已经全部写入到输出流中,不要等待了
s.shutdownOutput();
//读取服务端反馈数据
//获取通道内的字节输入流
InputStream in = s.getInputStream() ;
//一次读取一个字节数组
byte[] bytes2 = new byte[1024] ;
int len2 = in.read(bytes2) ;
String fkMsg = new String(bytes2, 0, len2) ;
System.out.println("fkMsg:"+fkMsg);
//释放资源
bis.close();
s.close();
}
}
public class UploadImageServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(6666) ;
Socket socket = ss.accept() ;
//封装通道内在字节输入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()) ;
//输出到指定文件
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("mv.jpg")) ;
//一次读取一个字节数组
byte[] bytes = new byte[1024] ;
int len = 0 ;
while((len = bis.read(bytes))!=-1) {
//写入
bos.write(bytes, 0, len);
//强制刷新
bos.flush();
}
//加入反馈
//获取通道内的输出流
OutputStream out = socket.getOutputStream() ;
out.write("图片已经复制完毕".getBytes());
//刷新
out.flush();
//释放资源
bos.close();
socket.close();
}
}