一阶段:第15天:网络编程(8.12)
一.概述
- 所谓网络编程(不是网站编程),指的就是在同一个网络中不同机器之间的通信。
- 网络参考模型
(1)OSI参考模型:包括七层:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层
(2)TCP/IP参考模型:包括四层:
a.链路层(数据链路层/物理层):包括操作系统中的设备驱动程序、计算机中对应的网络接口卡
b.网络层:处理分组在网络中的活动,比如分组的选路。
c.传输层:主要为两台主机上的应用提供端到端的通信。
d.应用层(应用层/表示层/会话层):负责处理特定的应用程序细节。 - 通信协议:
a.TCP协议:TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。数据大小无限制。建立连接的过程叫三次握手,断开叫四次断开。
b.UDP协议:UDP 是User Datagram Protocol的简称, 中文名是用户数据报协议,是TCP/IP参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,每个包的大小64Kb。
c.IP协议:[Internet Protocol]网际协议,能使连接到网上的所有计算机网络实现相互通信的一套规则,规定了计算机在因特网上进行通信时应当遵守的规则。IP协议中包含一块非常重要的内容就是为计算机分配了一个唯一标识即IP地址。
二.相关类的使用
2.1 InetAddress类
Java提供了InetAddress类来代表ip地址,是对ip地址的抽取和封装,有两个子类:Inet4Address,Inet6Address,分别表示IPv4和IPv6
public class Demo1 {
public static void main(String[] args) throws Exception{
//1.创建IP地址对象
//1.1获取本地IP地址
InetAddress ia=InetAddress.getByName("10.9.21.154");
InetAddress ia1=InetAddress.getByName("localhost");
InetAddress ia2=InetAddress.getByName("127.0.0.1");
InetAddress ia3=InetAddress.getByName("Lenovo-PC");
InetAddress ia4=InetAddress.getLocalHost();
System.out.println("主机地址"+ia.getHostAddress()+"主机名"+ia.getHostName());
System.out.println("主机地址"+ia1.getHostAddress()+"主机名"+ia1.getHostName());
System.out.println("主机地址"+ia2.getHostAddress()+"主机名"+ia2.getHostName());
System.out.println("主机地址"+ia3.getHostAddress()+"主机名"+ia3.getHostName());
System.out.println("主机地址"+ia4.getHostAddress()+"主机名"+ia4.getHostName());
//获取局域网IP
System.out.println("---局域网----");
InetAddress ia5=InetAddress.getByName("10.9.21.149");
System.out.println("主机地址"+ia5.getHostAddress()+"主机名"+ia5.getHostName());
//获取Internet
System.out.println("---Internet---");
InetAddress ia6=InetAddress.getByName("www.baidu.com");
InetAddress[] ia7=InetAddress.getAllByName("www.baidu.com");
System.out.println("主机地址"+ia6.getHostAddress()+"主机名"+ia6.getHostName());
for (InetAddress inetAddress : ia7) {
System.out.println(inetAddress);
}
}
}
运行结果:
主机地址10.9.21.154主机名Lenovo-PC
主机地址127.0.0.1主机名localhost
主机地址127.0.0.1主机名127.0.0.1
主机地址10.9.21.154主机名Lenovo-PC
主机地址10.9.21.154主机名Lenovo-PC
---局域网----
主机地址10.9.21.149主机名10.9.21.149
---Internet---
主机地址61.135.169.121主机名www.baidu.com
2.2 URLEncoder类和URLDecoder类
把中文字符转成可以网络传输的一种数据格式:URL编码
public class Demo2 {
public static void main(String[] args) throws Exception{
String say="好久不见";
//URL编码
String encode=URLEncoder.encode(say, "utf-8");
System.out.println(encode);
//URL解码
String say2=URLDecoder.decode(encode, "utf-8");
System.out.println(say2);
}
}
三.基于TCP的网络编程
TCP:基于字节流的传输层通信协议。
3.1 Socket通信模型
TCP的服务器端步骤:
- (1)创建ServerSocket(服务器套接字对象)
- (2)侦听或监听,返回Socket(客户端套接字)
- (3)获取输入或输出流
- (4)处理数据
- (5)关闭资源
Tcp客户端编程步骤:
- (1)创建客户端套接字Socket,并制定服务器的ip和端口号
- (2)获取输入,输出流
- (3)发送数据
- (4)关闭
3.1.1客户端发送消息,服务端接收消息
服务器端:
public class TcpServer {
public static void main(String[] args) throws Exception{
//(1)创建ServerSocket(服务器套接字对象),并指定端口号
ServerSocket listener=new ServerSocket(10086);
//(2)侦听或监听,返回Socket(客户端套接字)
System.out.println("服务器已启动。。。");
Socket socket=listener.accept();
//(3)获取输入或输出流
InputStream is=socket.getInputStream();
//(4)处理数据
InputStreamReader isr=new InputStreamReader(is);
BufferedReader br=new BufferedReader(isr);
String data=br.readLine();
System.out.println("客户端说:"+data);
//(5)关闭资源
br.close();
socket.close();
listener.close();
}
}
客户端:
public class TcpClient {
public static void main(String[] args) throws Exception{
//(1)创建客户端套接字Socket,并指定服务器的ip和端口号
Socket socket=new Socket("10.9.21.221",10086);
// (2)获取输入,输出流
OutputStream os=socket.getOutputStream();
//(3)发送数据
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(os));
bw.write("好久不见");
// (4)关闭
bw.close();
socket.close();
}
}
3.1.2客户端发送消息,服务端回复消息
大步骤不变,注意细节
服务器端:
public class TcpServer {
public static void main(String[] args) throws Exception{
//1.服务器套接字
ServerSocket listener =new ServerSocket(10010);
//2.监听,并返回客户端套接字
System.out.println("服务器端已启动");
Socket socket=listener.accept();
//3.获取输入输出流
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//4.处理数据
String data=br.readLine();//读取到返回符才返回结果
System.out.println("客户端说:"+data);
bw.write("还好吗");
bw.newLine();
//5.关闭
bw.close();
br.close();
socket.close();
}
}
客户端:
public class TcpClient {
public static void main(String[] args) throws Exception{
//创建Socket
Socket socket=new Socket("10.9.21.154",10010);
//获取输入输出
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//处理数据
bw.write("好久不见");
bw.newLine();//换新行
bw.flush();//刷新缓冲,不刷新会读取不到"好久不见"
String replay=br.readLine();
System.out.println("服务器端回复:"+replay);
//关闭
bw.close();
br.close();
socket.close();
}
}
3.1.3客户端上传文件到服务端【以图片为例】
大步骤不变,注意客户端和服务器端的输入输出和读取写入
Tcp的服务器,接受文件:
public class FileServer {
public static void main(String[] args) throws Exception{
//1.创建ServerSocket,并指定端口号
ServerSocket listener=new ServerSocket(8888);
//2.侦听,返回客户端套接字
System.out.println("服务器已启动");
Socket socket=listener.accept();
//3.获取输入流
InputStream is=socket.getInputStream();
FileOutputStream fos=new FileOutputStream("e://zuoye//aaa.jpg");
byte[] buf=new byte[1024];
int len=-1;
//4.读取数据
while ((len=is.read(buf))!=-1){
fos.write(buf,0,len);
}
//5.关闭
fos.close();
is.close();
socket.close();
listener.close();
System.out.println("接受完毕");
}
}
文件客户端:
public class FileClient {
public static void main(String[] args) throws Exception{
//1.创建socket
Socket socket=new Socket("10.9.21.154",8888);
//2.获取输出流
OutputStream os=socket.getOutputStream();
//3.读取文件并输出
FileInputStream fis=new FileInputStream("copy.jpg");
byte[] buf=new byte[1024];
int len=-1;
while ((len=fis.read(buf))!=-1){
os.write(buf,0,len);
}
//4.关闭
fis.close();
os.close();
socket.close();
System.out.println("发送完毕");
}
}
3.1.3聊天小程序1(重点)
多个客户端和一个服务端单方面交流【多线程版本】
服务器端会一直处于启动状态
服务器:
public class ChatServer {
public static void main(String[] args) throws Exception{
//创建
ServerSocket listener=new ServerSocket(9999);
//2.监听
System.out.println("聊天服务器已启动");
try {
while (true) {
Socket socket = listener.accept();
new ChatThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
listener.close();
}
}
}
客户端:
public class ChatClient {
public static void main(String[] args) throws Exception{
Socket socket=new Socket("10.9.21.154",9999);
OutputStream os=socket.getOutputStream();
BufferedWriter bw=new BufferedWriter( new OutputStreamWriter(os));
Scanner input=new Scanner(System.in);
while (true){
String data=input.next();
bw.write(data);
bw.newLine();
bw.flush();
if (data.equals("baibai")){
break;
}
}
//关闭
bw.close();
socket.close();
}
}
线程:
public class ChatThread extends Thread{
private Socket socket;
public ChatThread(Socket socket) {
this.socket = socket;
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//接受数据
BufferedReader br=null;
if (socket!=null) {
try {
InputStream is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
while (true) {
String data = br.readLine();
System.out.println(socket.getInetAddress().getHostAddress() + "说" + data);
if (data.equals("baibai")) {
System.out.println(socket.getInetAddress().getHostAddress()+"退出了");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("异常退出了");
} finally {
try {
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.1.4聊天小程序2(重点)
双方连接同一个服务器,通过服务器将双方的数据进行转发
聊天服务器:
public class ChatServer {
public static void main(String[] args) throws Exception{
//创建
ServerSocket listener=new ServerSocket(9999);
//实现双向连接
HashMap<String,Socket> maps=new HashMap<>();//线程不安全的
Collections.synchronizedMap(maps);//线程安全的,加了个互斥锁,效率低
//2.监听
System.out.println("聊天服务器已启动");
try {
while (true) {
Socket socket = listener.accept();
System.out.println("连接成功"+socket.getInetAddress().getHostAddress());
maps.put(socket.getInetAddress().getHostAddress(), socket);
new ChatThread(socket,maps).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
listener.close();
}
}
}
public class ChatClient {
public static void main(String[] args) throws Exception{
//1.创建socket
Socket socket=new Socket("10.9.21.246",9999);
//-----
//2.发送线程
new Thread(new Runnable(){
@Override
public void run() {
BufferedWriter bw=null;
try {
OutputStream os=socket.getOutputStream();
bw=new BufferedWriter( new OutputStreamWriter(os));
Scanner input=new Scanner(System.in);
while (true){
String data=input.next();
bw.write(data);
bw.newLine();
bw.flush();
if (data.equals("baibai")){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bw.close();
if (!socket.isClosed()){
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
//接受数据线程
new Thread(new Runnable() {
@Override
public void run() {
BufferedReader br=null;
try {
InputStream is = socket.getInputStream();
br=new BufferedReader(new InputStreamReader(is));
while (true) {
String data = br.readLine();
System.out.println(data+socket.getInetAddress().getHostAddress());//如果格式不正确,为什么死循环了
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
if(!socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
3.1.5 TCP实现注册登录(重点)
public class TcpServer {
public static void main(String[] args) {
//创建属性文件
//Properties就是用来创建属性文件的,且其实现了HashTable,是线程安全的
//Properties是固定键值对类型的map集合,所以并不需要定义泛型类型,它的固定类型并不是泛型,而是<String,String>
Properties properties=new Properties();
String file="userinfo.properties";
File file1=new File(file);
if (!file1.exists()){
try {
file1.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
//加载
try {
properties.load(new FileReader(file1));
} catch (IOException e) {
e.printStackTrace();
}
new LoginThread(properties).start();
new RegThread(properties).start();
}
}
public class TcpClient {
public static void main(String[] args) throws Exception {
reg();
login();
}
public static void reg() throws Exception{
doAction(6666);
}
public static void login() throws Exception{
doAction(7777);
}
public static void doAction(int port) throws Exception{
//1创建套接字
Socket socket=new Socket("10.9.21.154", port);
//2获取输入输出流
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
//3处理
Scanner input=new Scanner(System.in);
System.out.println("请输入用户名");
String username=input.next();
System.out.println("请输入密码");
String password=input.next();
//4发送
bw.write(username+"#"+password);
bw.newLine();
bw.flush();
//5接受
String reply = br.readLine();
System.out.println("服务器回复:"+reply);
//6关闭
bw.close();
br.close();
socket.close();
}
}
注册:
public class RegThread extends Thread{
private Properties properties;
public RegThread(Properties properties){
this.properties=properties;
}
@Override
public void run() {
//注册
//1.创建ServerSocket
BufferedReader br=null;
BufferedWriter bw=null;
ServerSocket listener=null;
Socket socket=null;
try {
listener=new ServerSocket(6666);
//监听
System.out.println("注册线程已经启动");
socket=listener.accept();
//3.接受客户端发送的数据
InputStream is=socket.getInputStream();
br=new BufferedReader(new InputStreamReader(is));
OutputStream os=socket.getOutputStream();
bw=new BufferedWriter(new OutputStreamWriter(os));
//接受数据
String data=br.readLine();//"zhangsan#123456"
//处理
String[] infos= data.split("#");
if (infos!=null){
/*String usename=infos[0];
String password=infos[1];*/
//判断属性集合中是否已存在
if (!properties.containsKey(infos[0])){
properties.setProperty(infos[0],infos[1]);
bw.write("注册成功");
bw.newLine();
bw.flush();
//保存
FileWriter fw=new FileWriter("userinfo.properties");
properties.store(fw, "");
fw.flush();
fw.close();
}else{
bw.write("用户名已存在");
bw.newLine();
bw.flush();
}
//存到文件里
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
bw.close();
socket.close();
listener.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
登录:
public class LoginThread extends Thread{
private Properties properties;
public LoginThread(Properties properties){
this.properties=properties;
}
@Override
public void run() {
BufferedReader br=null;
BufferedWriter bw=null;
ServerSocket listener=null;
Socket socket=null;
//登录
//1.创建ServerSocket
try {
listener=new ServerSocket(7777);
//监听
System.out.println("登陆线程已启动");
socket=listener.accept();
//3.接受客户端发送的数据
InputStream is=socket.getInputStream();
br=new BufferedReader(new InputStreamReader(is));
OutputStream os=socket.getOutputStream();
bw=new BufferedWriter(new OutputStreamWriter(os));
//接受数据
String data=br.readLine();//"zhangsan#123456"
//处理
String[] infos= data.split("#");
if (infos!=null){
//判断属性集合中是否已存在
String usename=infos[0];
String password=infos[1];
if (!properties.containsKey(usename)){
//properties.setProperty(infos[0],infos[1]);
bw.write("登陆失败,用户名不存在");
bw.newLine();
bw.flush();
}else{
//再判断密码
String pwd=(String)properties.get(usename);
if (pwd.equals(infos[1])) {
bw.write("登陆成功");
bw.newLine();
bw.flush();
}else {
bw.write("密码错误");
bw.newLine();
bw.flush();
}
}
//存到文件里
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
bw.close();
socket.close();
listener.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.1.6其它:龟兔赛跑(重点)
多线程模拟龟兔赛跑:
乌龟和兔子进行1000米赛跑,兔子前进5米,乌龟只能前进1米。
但兔子每20米要休息500毫秒,而乌龟是每100米休息500毫秒。
谁先到终点就结束程序,并显示获胜方
public class Demo1 {
public static void main(String[] args) throws Exception{
Tortoise tortoise=new Tortoise("乌龟");
Rabbit rabbit=new Rabbit("玉兔");
tortoise.start();
rabbit.start();
//如果想在主线程中创建打印方法,需要阻塞主线程
//否则获取到的时间是此时线程开始运行的时间,
//而不是线程运行完成后得到的最终时间
//线程只是在主线程中start(),之后他们就是并列的关系了
// tortoise.join();
// rabbit.join();
//
// System.out.println("兔子用时:"+rabbit.getTime());
// System.out.println("乌龟用时:"+tortoise.getTime());
// if(tortoise.getTime()>rabbit.getTime()){
// System.out.println("兔子赢了");
// }else if(tortoise.getTime()<rabbit.getTime()){
// System.out.println("乌龟赢了");
// }else{
// System.out.println("平局");
// }
}
}
兔子:
package inclass.task;
public class Rabbit extends Thread {
public volatile static boolean isEnd=false;
public Rabbit(String name){
super(name);
}
private int length=0;
private long time;
public static long time1;
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
@Override
public void run() {
long start=System.currentTimeMillis();
while(true){
//判断乌龟有没有结束
if(Tortoise.isEnd){
System.out.println("乌龟跑完了...乌龟赢了"+Tortoise.time1);
break;
}
length+=5;
if(length==1000){
break;
}
if(length%20==0){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
long end=System.currentTimeMillis();
time=end-start;
time1=end-start;
isEnd=true;
}
}
乌龟:
public class Tortoise extends Thread{
public volatile static boolean isEnd=false;
public Tortoise(String name){
super(name);
}
private int length=0;
private long time;
public volatile static long time1;
public long getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
@Override
public void run() {
long start=System.currentTimeMillis();
while(true){
//判断兔子有没有结束
if(Rabbit.isEnd){
System.out.println("兔子跑完了...兔子赢了"+Rabbit.time1);
break;
}
length+=1;
if(length==1000){
break;
}
if(length%100==0){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
long end=System.currentTimeMillis();
time1=end-start;
time=end-start;
isEnd=true;//设置true
}
}
四.UDP编程
- User Datagram Protocol的简称,用户数据包协议,提供面向事务的简单不可靠信息传送服务。
- 特点:a.不安全 b.无连接 c.效率高 d.传输数据有大小限制,每个被传输的数据报必须限定在64KB之内
- DatagramSocket: 数据报套接字,表示用来发送和接收数据报包的套接字。
- DatagramPacket:此类表示数据报包。每个包最大64kb。
* 接收方
* 先开启接收,再发送
public class Receiver {
public static void main(String[] args) throws Exception{
//1.创建DatagramSocket,指定端口号
DatagramSocket ds=new DatagramSocket(9999);
//2.创建接收的数据报包
byte[] buf=new byte[1024*8];
DatagramPacket dp=new DatagramPacket(buf,buf.length);//length---从哪开始放
//3.接收(阻塞方法,如果没有数据就阻塞,有数据则接收)
System.out.println("准备接收");
ds.receive(dp);
//4.处理数据
String data=new String(dp.getData(),0,dp.getLength());
System.out.println(dp.getAddress().getHostAddress()+"对方说:"+data);
dp.getLength();
/*//5.回复
String reply="十分想念";
DatagramPacket dp2=new DatagramPacket(reply.getBytes(),reply.getBytes().length );
ds.send(dp2);*/
//5.关闭
ds.close();
}
}
* 发送方
public class Sender {
public static void main(String[] args) throws Exception{
//1.创建快递点DatagramSocket,不用指定端口号,使用随机的端口号。也可以指定
DatagramSocket ds=new DatagramSocket();
//2.创建发送数据报包
String say="好久不见";
DatagramPacket dp=new DatagramPacket(say.getBytes(),say.getBytes().length, InetAddress.getLocalHost(),9999);
//3.发送
ds.send(dp);
/*//4.接收回复
byte[] buf=new byte[1024*8];
DatagramPacket dp2=new DatagramPacket(buf, buf.length);
ds.receive(dp2);
//5.处理
String d=new String(dp.getData(),0,dp.getLength());
System.out.println("回复:"+d);*/
//4.关闭
ds.close();
}
}
public class ChatReriver {
public static void main(String[] args){
//创建
new Thread(new Runnable(){
@Override
public void run(){
DatagramSocket ds=null;
try {
ds=new DatagramSocket(9999);
byte[] buf=new byte[1024*20];
DatagramPacket dp=new DatagramPacket(buf,buf.length);
while (true){
try {
ds.receive(dp);
String data=new String(dp.getData(),0,dp.getLength());
System.out.println(dp.getAddress().getHostAddress()+"说"+data);
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (SocketException e) {
e.printStackTrace();
}
finally {
ds.close();
}
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
DatagramSocket ds=null;
try {
ds=new DatagramSocket();
Scanner input=new Scanner(System.in);
while (true){
String data=input.next();
DatagramPacket dp= null;
try {
dp = new DatagramPacket(data.getBytes(), data.getBytes().length, InetAddress.getByName("10.9.21.255"),9999);
ds.send(dp);
} catch (IOException e) {
e.printStackTrace();
}
if (data.equals("baibai")){
System.out.println(dp.getAddress()+"退出了");
}
}
} catch (SocketException e) {
e.printStackTrace();
}finally {
ds.close();
}
}
});
}
}