JavaSE总结(四)网络编程
一:网络编程概述
1.什么是网络:
分存在不同区域计算机使用专业通信线路连接起来,实现资源共享;
网络编程目的:通过通讯协议实现数据传输
2.通讯协议的约定
TCP:传输控制协议
UDP:用户数据报协议
2.通讯要素(三要素:ip、端口、协议)
1.ip地址和端口
ip:Internet网络唯一标识,端口:计算机中运行的一个进程
InetAddress类:此类表示Internet协议(ip)地址
常用方法:
方法 | 作用 |
---|---|
getByname(String host) | 确定主机名称的ip地址;//解析域名得到ip |
getAddress() | 返回InetAddress对象的原始ip地址,是InetAddress类型的 |
getLocalHost() | 返回本机主机的地址 |
getHostName() | 获取此ip地址的主机名 |
getHostAddress() | 获得字符串类型的ip地址 |
端口号:0~65535 系统预留:0~1023 自定义:1024~65535
如:mysql默认端口号:3306
@Test
public void test1() throws UnknownHostException {
//确定主机名称的ip地址
InetAddress in=InetAddress.getByName("www.baidu.com");
//InetAddress in2=InetAddress.getByName("182.");
System.out.println(in);
System.out.println(in.getHostName());
System.out.println(in.getHostAddress());
//本机 localhost 127.0.0回环地址(用于测试本机)
//ipconfig----查询本机ip
//ping ip-----检查当前网络是否畅通
in=InetAddress.getByName("127.0.0");
System.out.println(in.getHostName());
System.out.println(in.getHostAddress());
//本机端口号
InetAddress in3=InetAddress.getLocalHost();
//获取本机名
System.out.println(in3.getHostName());
//获取本机ip地址
System.out.println(in3.getHostAddress());
}
2.TCP通讯协议
TCP有6种标示:
- SYN(建立联机)
- ACK(确认)
- PSH(传送)
- FIN(结束)
- RST(重置)
- URG(紧急)
1)三次握手
第一次握手
客户端向服务器发出连接请求报文,这时报文首部中的同部位SYN=1,同时随机生成初始序列号 seq=x,此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。这个三次握手中的开始。表示客户端想要和服务端建立连接。
第二次握手
TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己随机初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。这个报文带有SYN(建立连接)和ACK(确认)标志,询问客户端是否准备好。
第三次握手
TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。
TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。这里客户端表示我已经准备好。
2)四次挥手
第一次挥手
TCP客户端发送一个FIN(结束),用来关闭客户到服务端的连接。客户端进程发出连接释放报文,并且停止发送数据。
释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
第二次挥手
服务端收到这个FIN,他发回一个ACK(确认),确认收到序号为收到序号+1,和SYN一样,一个FIN将占用一个序号。
服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。
TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
第三次挥手
服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。
服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
第四次挥手
客户端发送ACK(确认)报文确认,并将确认的序号+1,这样关闭完成。
客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
3.UDP协议传输
Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据报的方法。
二:Java中基于TCP的编程
传递的是socket套接字(ip地址和端口组合socket)
网络通信其实就是Socket间的通信,以流的方式进行传输
一般主动发去通信应用程序的叫客户端,等待接收数据的叫服务器
客户端:Socket
- Socket(InteAddress address,int port):创建字节流套接字,并且将其连接到指定的ip地址的指定端口号
- getInputStream():得到套接字里面的输入流
- getOutputStream():得到套接字里面的输出流
服务端:ServerSocket
- ServerSocket(int port):创建指定端口的服务器套接字
- Socket=accept():侦听连接到次套接字并且接受它
- getInputStream():得到次套接字的输入流
- getOutputStream():得到次套接字的输出流
public class TestTcp2 {
@Test
/**
* 客户端
*/
public void client() throws UnknownHostException, IOException {
//创建客户端Socket对象
Socket socket=new Socket(InetAddress.getByName("127.0.0.1"),8888);
//获取输出流
OutputStream out=socket.getOutputStream();
out.write("你好,我是客户端".getBytes());
//获取输入流
InputStream input=socket.getInputStream();
int size=input.available();
byte[] array=new byte[size];
input.read(array);
String client=new String(array);
System.out.println(client);
//关闭资源
socket.close();
}
/**
* 服务器端
*/
@Test
public void server() throws IOException {
//创建ServerSock对象
ServerSocket ssocket=new ServerSocket(8888);
//监听客户端的连接
Socket mon=ssocket.accept();
//创建输出流
OutputStream out=mon.getOutputStream();
out.write("你好,我是服务端".getBytes());
//获取输入流
InputStream input=mon.getInputStream();
int size=input.available();//估算流的长度
byte[] array=new byte[size];//获得固定长度的字节数组
input.read(array);
String server=new String(array);
System.out.println(server);
//关闭资源
ssocket.close();
}
}
实践一:实现客户端和服务端的通信:
踩坑:1.读取字节流要用到byte[]数组,一般可以使用available(0方法估测流的长度,从而动态创建数组长度;但是在这里却不行,因为read()是一个阻塞式方法,如果读不到数据就会阻塞线程
//服务端
public class server {
public static void main(String[] args) throws IOException {
//建立连接
ServerSocket ss=new ServerSocket(9000);
//对客户端连接进行监听,有连接就会获取
Socket socket=ss.accept();
//创建输入流具体任务,传入套接字
ReciveImpl res=new ReciveImpl(socket);
//分配任务,开启输入流线程
new Thread(res).start();
//创建输出流任务,传入套接字
SendImpl sen=new SendImpl(socket);
//分配任务,开启输出流线程
new Thread(sen).start();
}
}
//客户端
public class client {
public static void main(String[] args) throws UnknownHostException, IOException {
//建立连接
Socket socket=new Socket(InetAddress.getLocalHost(),9000);
//创建输入流任务
ReciveImpl rec=new ReciveImpl(socket);
//分配并且启动输入流任务
new Thread(rec).start();
//创建输出流任务
SendImpl sen=new SendImpl(socket);
//分配并且启动输出流任务
new Thread(sen).start();
}
}
//输入流的线程类
public class ReciveImpl implements Runnable{
Socket socket;
public ReciveImpl(Socket socket) {
this.socket=socket;
}
@Override
public void run() {
while(true) {
try {
InputStream is=socket.getInputStream();
byte[] array=new byte[1024];
//第一种读取方式:
// is.read(array);
// String str=new String(array);
// System.out.println("收到消息:"+str);
//第二种读取方式:
int len = is.read(array);
System.out.println("收到的消息:"+new String(array, 0 ,len));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//输出流的线程类
public class SendImpl implements Runnable{
Socket socket;
public SendImpl(Socket socket) {
this.socket=socket;
}
@Override
public void run() {
System.out.println("请输入信息:");
Scanner input=new Scanner(System.in);
while(true) {
try {
OutputStream os = socket.getOutputStream();
String line=input.nextLine();
os.write(line.getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
实践二:实现图片的上传和下载
//服务器端
public class UpLoadServer {
public static void main(String[] args) throws IOException {
//建立连接
ServerSocket se=new ServerSocket(10088);
//监听
Socket s=se.accept();
//获取输入流
InputStream in=s.getInputStream();
//处理输入流
BufferedInputStream bi=new BufferedInputStream(in);
//创建输出流
BufferedOutputStream bu=new BufferedOutputStream(new FileOutputStream("C:\\Users\\Administrator\\Desktop\\gg.jpg"));
byte[] byt=new byte[bi.available()];
bi.read(byt);
bu.write(byt);
BufferedWriter out=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
out.write("文件上传成功");
out.newLine();//写一行分隔符
out.flush();
// OutputStream out=s.getOutputStream();
// byte[] byts="00".getBytes();
// out.write(byts);
bu.close();
s.close();
}
}
//客户端
ublic class UpLoadClien {
public static void main(String[] args) throws UnknownHostException, IOException {
//创建连接
Socket se=new Socket(InetAddress.getLocalHost(), 10088);
//获取要上传图片的地址
BufferedInputStream bu=new BufferedInputStream(new FileInputStream("C:\\Users\\Administrator\\Desktop\\picture.jpg"));
//获取连接通道中的输出流
BufferedOutputStream bo=new BufferedOutputStream(se.getOutputStream());
//将输入流中的数据写到输出流中
byte[] byt=new byte[bu.available()];
bu.read(byt);
bo.write(byt);
//获取服务端的反馈
BufferedReader inp=new BufferedReader(new InputStreamReader(se.getInputStream()));
String str=inp.readLine();
System.out.println("马上读");
System.out.println(str);
System.out.println("读了");
// InputStream inp=se.getInputStream();
// byte[] byts=new byte[inp.available()];
// inp.read(byts);
// String str=new String(byts);
// System.out.println("马上读");
// System.out.println(str);
// System.out.println("读了");
//关闭资源和连接
bu.close();
se.close();
}
}
三:Java中基于UDP的编程
DatagramSocket(数据报套接字):此类表示用于发送和接收数据包套接字
DatagramPacket(数据报包):封装UDP数据包,数据包中包含了发送端的ip和端口号,UDP协议中给出了完整的地址信息,因此不需要像TCP协议一样建立连接
注:DatagramSocket的接收数据包的方法receive()是一个阻塞式的方法:没有接收到数据,就会阻塞主线程;
接收端:
public class ReceiveDemo {
public static void main(String[] args) throws Exception {
//创建接收端Socket对象
DatagramSocket ds=new DatagramSocket(10086);
//创建一个数据包(接收容器)
byte[] bys=new byte[1024];
int len=bys.length;
DatagramPacket dp=new DatagramPacket(bys, len);
//调用Socket对象的接收方法接收数据
ds.receive(dp);//阻塞式
System.out.println("你好");
//获取接受的数据包的发送端ip
InetAddress address=dp.getAddress();
String ip=address.getHostAddress();
//解析数据包
byte[] bys2=dp.getData();
int len2=dp.getLength();
String s=new String(bys2, 0, len2);
System.out.println(ip+"====="+s);
ds.close();
}
}
发送端:
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端Socket对象
DatagramSocket ds=new DatagramSocket();
//创建数据,并且把数据打包
byte[] bys="你好,世界,我是UDP协议传输的".getBytes();
//数据长度
int len=bys.length;
//Ip地址对象
InetAddress address=InetAddress.getLocalHost();
//等价于InetAddress address=InetAddress.getByName("192.168.10.22")
//端口
int part=10086;
//打包数据
DatagramPacket dp=new DatagramPacket(bys, len, address,part);
//调用Socket对象的发送方法将数据包发送
ds.send(dp);
//关闭连接
ds.close();
}
}
实践一:实现键盘录入,在线聊天(多线程)
//发送端线程
public class SendThread implements Runnable{
DatagramSocket ds;
public SendThread(DatagramSocket ds) {
this.ds=ds;
}
//发送端线程
@Override
public void run() {
while(true) {
try {
//构建数据包所需要的数据
Scanner input=new Scanner(System.in);
String buf=input.nextLine();
int len=buf.length();
int part=10066;
//创建数据包
DatagramPacket dp=new DatagramPacket(buf.getBytes(), len,InetAddress.getLocalHost(), part);
//将数据包发出
ds.send(dp);
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//接收端线程
public class ReceiveThread implements Runnable{
//创建DatagramSocket对象,建立连接
DatagramSocket ds;
public ReceiveThread(DatagramSocket ds) {
this.ds=ds;
}
@Override
public void run() {
while(true) {
try {
//创建一个合适大小的接收容器
byte[] byt=new byte[1024];//只起到一个缓存的作用
int len=byt.length;
DatagramPacket dp=new DatagramPacket(byt, len);
//接收数据包到容器中
ds.receive(dp);
//调用getData()方法获取解析这个数据包
byte[] bty2=dp.getData();
int len2=bty2.length;
String s=new String(bty2, 0, len2);
//得到数据包中的ip
String ip=(dp.getAddress()).getHostAddress();
System.out.println(ip+"========"+s);
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//控制类
public class InterFace {
public static void main(String[] args) throws SocketException {
DatagramSocket dssend=new DatagramSocket();
DatagramSocket dsreceive=new DatagramSocket(10066);
SendThread send=new SendThread(dssend);
ReceiveThread receive=new ReceiveThread(dsreceive);
new Thread(send).start();
new Thread(receive).start();
}
}
实践一升级:使用UDP协议传输实现接收端反馈
实现反馈,又重新定义了一个端口号,否则会报错,端口号被占用;
//接收端
public class ReceiveDemo {
public static void main(String[] args) throws Exception {
//创建接收端Socket对象
DatagramSocket ds=new DatagramSocket(10086);
//创建一个数据包(接收容器)
byte[] bys=new byte[1024];
int len=bys.length;
DatagramPacket dp=new DatagramPacket(bys, len);
//调用Socket对象的接收方法接收数据
ds.receive(dp);//阻塞式
System.out.println("你好");
//获取接受的数据包的发送端ip
InetAddress address=dp.getAddress();
String ip=address.getHostAddress();
//解析数据包
byte[] bys2=dp.getData();
int len2=dp.getLength();
String s=new String(bys2, 0, len2);
System.out.println(ip+"====="+s);
ds.close();
}
}
//发送端
public class SendThread implements Runnable{
DatagramSocket ds1;
DatagramSocket ds2;
public SendThread(DatagramSocket ds1,DatagramSocket ds2) {
this.ds1=ds1;
this.ds2=ds2;
}
//发送端线程
@Override
public void run() {
while(true) {
try {
//构建数据包所需要的数据
System.out.println("小帅:");
Scanner input=new Scanner(System.in);
String buf1=input.nextLine();
byte[] buf=buf1.getBytes();
int len=buf.length;
int part=10066;
//创建数据包
DatagramPacket dp=new DatagramPacket(buf, len,InetAddress.getLocalHost(), part);
//将数据包发出
ds1.send(dp);
//==================================
//接收接收端的反馈信息
DatagramPacket at=new DatagramPacket(new byte[1024], new byte[1024].length);
ds2.receive(at);//阻塞
System.out.println("小帅:");
byte[] byts=at.getData();
String s=new String(byts, 0, byts.length);
System.out.println(s);
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//控制类
public class InterFace {
public static void main(String[] args) throws SocketException {
DatagramSocket dssend=new DatagramSocket();
DatagramSocket dssend1=new DatagramSocket(10010);
DatagramSocket dsreceive=new DatagramSocket(10066);
DatagramSocket dsreceive1=new DatagramSocket();
SendThread send=new SendThread(dssend,dssend1);
ReceiveThread receive=new ReceiveThread(dsreceive,dsreceive1);
new Thread(send).start();
new Thread(receive).start();
}
}
四:Java中基于URL的编程
url"统一资源定位符
组成:<传输协议>://<主机名>:<端口号 http 默认80>/<文件名>
URL类:需要给定具体传输协议为参数
URL类的openStream()方法:返回一个输入流
实践一:开启Tomcat服务器:读取examples/app.txt文件
public static void main(String[] args) throws IOException {
URL url=new URL("http://127.0.0.1:8080/examples/app.txt");
InputStream is=url.openStream();
BufferedInputStream bs=new BufferedInputStream(is);
byte[] byt=new byte[1024];
int len;
while((len=bs.read(byt))!=-1) {
String str=new String(byt,0,len);
System.out.println(str);
}
实践二:读取网页上文件,存入本地内存
public static void main(String[] args) throws IOException {
URL url=new URL("http://pic51.nipic.com/file/20141025/8649940_220505558734_2.jpg");
InputStream is=url.openStream();
BufferedInputStream buf=new BufferedInputStream(is);
BufferedOutputStream out=new BufferedOutputStream(new FileOutputStream(new File("C:\\Users\\Administrator\\Desktop\\a.jpg")));
//方式一读取
// byte[] byt=new byte[buf.available()];
// buf.read(byt);
// out.write(byt);
// is.close();
// buf.close();
// out.close();
//方式二读取
byte[] byt=new byte[1024];
int len;
while((len=buf.read(byt))!=-1) {
out.write(byt, 0, len);
}
}
方式一读取,文件会失真,所以在读取非文本文件最好还是使用第二种方式读取
五:总结
网络通信总结:
一:基于TCP协议:
1.客户端和服务端单程通信:
客户端:
- 创建Socket()对象,要写入ip地址和端口号
- 通过Socket()的getOutputStream()方法获取输出流对象;
- 将数据转成字节数组(或者字符数组),调用write()方法,将数据写入输入流
- 通过Socket()的getInputStream()方法获取输入流对象;
- 解析输入流,获得数据并且输出;
- 释放连接
服务端:
- 创建ServerSocket()对象,写入端口号;
- 调用ServerSocket()的accept()方法对连接到服务器的客户端进行监听(accept是一个阻塞式方法,如果没有监听到连接,就会阻塞主线程);
- 通过Socket()的getOutputStream()方法获取输出流对象;
- 将数据转成字节数组(或者字符数组),调用write()方法,将数据写入输入流
- 通过Socket()的getInputStream()方法获取输入流对象;
- 解析输入流,获得数据并且输出;
- 释放连接
2.基于多线程的服务端客户端实时通信
输入流线程:输入流线程任务,构造器要传入Socket()实例对象,需要端口号
输出流线程:输出流线程任务,构造器要传入Socket socket=ss.accept()的实例对象,需要端口号和ip地址;
客户端:创建Socket()套接字对象,开启Thread
服务端:创建ServerSocket()套接字,并且调用accept()方法监听连接,开启Thread
二:基于UDP协议:
1.接收端向发送端发送数据
接收端:
- 创建DatagramSocket()对象,需要传入端口号(这里的端口号其实就是一个 标识)
- 创建一个数据报包DatagramSocket()用来缓存,DatagramSocket()有两个参数,一个是字节数组,一个是长度;这个需要自己确定;
- 调用数据包套接字DatagramSocket()的receive()方法,用来将数据报中的数据读取到缓存容器中,(为什么要有一个缓存区呢?这是因为我们读取数据只能在数据报中读取,而不是在数据包套接字中读取,所以需要将数据报套接字的数据通过receive()方法保存到数据报包中)
- 解析数据包,通过getData()方法获取数据报包缓存容器中的数据;
- 关闭连接;
发送端:
- 创建一个DatagramSocket()对象,不需要传入任何参数;
- 创建一个数据报包,DatagramSocket(),需要传入参数byte[]数组(存放数据)、数据长度、接收端ip、端口号;
- 调用DatagramSocket()的send()方法将数据报包发送出去;
- 关闭连接