Java 网络编程 学习笔记
1.基本概念
- 网络:将不同区域的计算级链接到一起 局域网 城域网 互联网
- 地址:IP地址,确定网络上的一个绝对地址,位置。
- 端口号:区分计算级软件的,2个字节,范围 0~65535,一共65536个
- 在同一个协议下,端口号不能重复,不同的协议下,可以重复
- 1024以下的端口不要使用
端口冲突解决所用命令:
- 查看所有端口: netstat –ano
- 查看指定端口: netstat –ano|findstr "8080"
- 查看指定进程: tasklist|findstr "2356"
- 查看具体程序:使用任务管理器查看PID
- 资源定位:URL:统一资源定位符(包含资源+定位) URI:统一资源(仅资源)
- 数据的传输
- 协议:TCP 和 UDP
- TCP:电话,类似于三次握手,面向连接的安全可靠的效率相对低下的协议
- UDP:短息,非面向连接,效率高
- 传输
- 先封装
- 后解封
2.常用类操作 API
- InetAddress 封装计算机的ip地址,没有端口
- 成员方法
- getHostAddress:返回地址
- getHostName:返回计算机名
- InetSocketAddress 包含端口,用于socket通信
- 构造器
- new InetSocketAddress(地址|域名,端口)
- 方法
- get Address():返回地址信息
- getPort():返回端口号
- getHostName():返回主机名
- URL 定位资源 互联网三大基石之一(html | http)
- 在www上,每一信息资源都有统一且唯一的地址,即统一资源定位符 Uniform Resource Locator。如:http://www.google.com:80/index.html。由四部分组成,分别为:
- 协议
- 存放资源的主机域名
- 端口号
- 资源文件名
1 package www.wangxiaoyue.net; 2 3 import java.net.MalformedURLException; 4 import java.net.URL; 5 6 /** 7 * URL:统一资源定位器,互联网三大基石之一(html , http) ,作用是区分资源 8 * 1、协议 9 * 2、域名、计算机 10 * 3、端口:默认80 11 * 4、请求资源 12 * http://www.baidu.com:80/index.html?uname=shsxt&age=18#a 13 */ 14 public class URLTest01 { 15 public static void main(String[] args) throws MalformedURLException { 16 URL url = new URL("http://www.baidu.com:80/index.html?uname=shsxt&age=18#a"); 17 //获取四个值 18 System.out.println("协议:" + url.getProtocol()); 19 System.out.println("域名 | ip:" + url.getHost()); 20 System.out.println("端口:" + url.getPort()); 21 System.out.println("请求资源1:" + url.getFile()); 22 System.out.println("请求资源2:" + url.getPath()); 23 //获取参数 24 System.out.println("参数:" + url.getQuery()); 25 System.out.println("锚点:" + url.getRef()); 26 } 27 } 28
3.传输协议 UDP 、TCP
- UDP(User DatagramProtocol)传输协议
- 一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务
- 特点
- 非面向连接,传输不可靠,可能丢失
- 发送不管对方是否准备好,接收方收到也不确认
- 可以广播发送
- 非常简单的协议,开销小
- TCP(transfer control protocol)
- 一种面向连接(连接导向)的、可靠的、基于字节流的运输层(Transport layer)通信协议
- 特点
- 面向连接
- 点到点的通信
- 高可靠性
- 占用系统资源多,效率低
- 套接字Socket
- 我们开发的网络应用程序位于应用层,TCP和UDP属于传输层协议。在应用层和传输层之间,则使用套接字进行分离
4.UDP编程
需求:完成在线咨询功能:
- 学生和咨询师在线一对一交流
分析:
- 使用基于UDP协议的Socket网络编程实现
- 不需要利用IO流实现数据的传输
- 每个数据发送单元被统一封装成数据包的方式,发送方将数据包发送到网络中,数据包在网络中去寻找他的目的地
UDP基本概念
- DatagramSocket:用于发送或接收数据包的套接字
- DatagramPacket:数据包
注意:同一协议下,端口号禁止重复,否则报端口冲突异常
发送端
1 package com.wangxiaoyue.udp; 2 3 import java.io.IOException; 4 import java.net.DatagramPacket; 5 import java.net.DatagramSocket; 6 import java.net.InetSocketAddress; 7 8 /** 9 * 发送端 10 * 1、使用DatagramSocket 指定端口 创建发送端 11 * 2、准备数据,一定转成字节数组 12 * 3、封装成DatagramPacket 包裹,需要指定目的地 13 * 4、发送包裹 send(DatagramPacket p) 14 * 5、释放资源 15 */ 16 public class UdpClient { 17 18 public static void main(String[] args) throws IOException { 19 System.out.println("发送方启动中。。。"); 20 //1、使用DatagramSocket 指定端口 创建发送端 21 DatagramSocket client = new DatagramSocket(); 22 //2.准备数据,一定转成字节数组 23 String data = "这是一条没什么用但是能够测试程序的数据"; 24 byte[] datas = data.getBytes(); 25 //3.封装成DatagramPacket 包裹,需要指定目的地 26 DatagramPacket datagramPacket = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress("localhost", 9999)); 27 //4、发送包裹 send(DatagramPacket p) 28 client.send(datagramPacket); 29 //5、释放资源 30 client.close(); 31 } 32 } 33
接收端
1 package com.wangxiaoyue.udp; 2 3 import java.io.IOException; 4 import java.net.DatagramPacket; 5 import java.net.DatagramSocket; 6 import java.net.SocketException; 7 8 /** 9 * 接收端 10 * 1、使用DatagramSocket 指定端口 创建接收端 11 * 2、准备容器,封装成DatagramPacket 包裹 12 * 3、阻塞式接受包裹receive(DatagramPacket p) 13 * 4、分析数据 14 * byte[] getData() 15 * getLength() 16 * 5、释放资源 17 */ 18 public class UdpServer { 19 public static void main(String[] args) throws IOException { 20 System.out.println("接收端启动中。。。"); 21 //1、使用DatagramSocket 指定端口 创建接收端 22 DatagramSocket server = new DatagramSocket(9999); 23 //2、准备容器,封装成DatagramPacket 包裹 24 byte[] container = new byte[1024]; 25 DatagramPacket datagramPacket = new DatagramPacket(container,0,container.length); 26 //3、阻塞式接受包裹receive(DatagramPacket p) 27 server.receive(datagramPacket); 28 //4、分析数据 byte[] getData() getLength() 29 byte[] datas = datagramPacket.getData(); 30 int length = datagramPacket.getLength(); 31 //5、释放资源 32 server.close(); 33 System.out.println("数据: "+new String(datas,0,length)+" 长度: "+length); 34 } 35 } 36
重点需要关注字节数组 byte[] datas.
操作基本类型,需要使用DataInputStream DataOutputStream
操作对象类型,需要使用ObjectInputStream ObjectOutputStream
操作文件类型,需要使用FileInputStream FileOutputStream
可以看出的是,以上使用的都是字节流,因为我们传输的数据是存放在字节数组中的。同时,可以加上BufferedOutputStream和BufferedInoputStream 缓冲流,可以显著提高效率。不过工作中一般使用Commons IO 这个工具类。
使用面向对象思想,对接收端和发送端进行封装
1 package com.wangxiaoyue.udp; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import java.net.DatagramPacket; 7 import java.net.DatagramSocket; 8 import java.net.InetSocketAddress; 9 import java.net.SocketException; 10 11 /** 12 * 发送端 13 */ 14 public class TalkSend implements Runnable { 15 16 //1、使用DatagramSocket 指定端口 创建发送端 17 private DatagramSocket client; 18 private String toIp; 19 private int toPort; 20 21 TalkSend(int port, String toIp, int toPort) { 22 this.toPort = toPort; 23 this.toIp = toIp; 24 try { 25 client = new DatagramSocket(port); 26 } catch (SocketException e) { 27 e.printStackTrace(); 28 } 29 } 30 31 @Override 32 public void run() { 33 while (true) { 34 //2.准备数据,一定转成字节数组 35 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); 36 String data = null; 37 try { 38 data = reader.readLine(); 39 } catch (IOException e) { 40 e.printStackTrace(); 41 } 42 byte[] datas = data.getBytes(); 43 //3.封装成DatagramPacket 包裹,需要指定目的地 44 DatagramPacket datagramPacket = new DatagramPacket(datas, 0, datas.length, new InetSocketAddress(this.toIp, this.toPort)); 45 //4、发送包裹 send(DatagramPacket p) 46 try { 47 client.send(datagramPacket); 48 } catch (IOException e) { 49 e.printStackTrace(); 50 } 51 if ("bye".equals(data)) { 52 break; 53 } 54 } 55 //5、释放资源 56 client.close(); 57 } 58 } 59
1 package com.wangxiaoyue.udp; 2 3 import java.io.IOException; 4 import java.net.DatagramPacket; 5 import java.net.DatagramSocket; 6 import java.net.SocketException; 7 8 /** 9 * 接收端:使用面向对象封装 10 */ 11 public class TalkReceive implements Runnable { 12 private DatagramSocket server; 13 private String from; 14 TalkReceive(int port,String from) { 15 this.from = from; 16 try { 17 this.server = new DatagramSocket(port); 18 } catch (SocketException e) { 19 e.printStackTrace(); 20 } 21 } 22 23 @Override 24 public void run() { 25 while (true) { 26 //2、准备容器,封装成DatagramPacket 包裹 27 byte[] container = new byte[1024]; 28 DatagramPacket datagramPacket = new DatagramPacket(container,0,container.length); 29 //3、阻塞式接受包裹receive(DatagramPacket p) 30 try { 31 server.receive(datagramPacket); 32 } catch (IOException e) { 33 e.printStackTrace(); 34 } 35 //4、分析数据 byte[] getData() getLength() 36 byte[] datas = datagramPacket.getData(); 37 int length = datagramPacket.getLength(); 38 String data = new String(datas, 0, length); 39 System.out.println(this.from+":"+data); 40 if ("bye".equals(data)) { 41 break; 42 } 43 } 44 //5、释放资源 45 server.close(); 46 } 47 48 } 49
模拟学生和老师两个客户端,启用多线程进行通讯
1 package com.wangxiaoyue.udp; 2 3 /** 4 * 学生端 5 */ 6 public class TalkStudent { 7 public static void main(String[] args) { 8 new Thread(new TalkReceive(7777,"教师")).start(); 9 new Thread(new TalkSend(5555,"localhost",8888)).start(); 10 } 11 } 12
1 package com.wangxiaoyue.udp; 2 3 /** 4 * 教师端 5 */ 6 public class TalkTeacher { 7 public static void main(String[] args) { 8 new Thread(new TalkReceive(8888,"学生")).start(); 9 new Thread(new TalkSend(6666,"localhost",7777)).start(); 10 } 11 } 12
注意发送和接收消息的端口一定要相同:
即:1.首先每个端是两个线程,也就是启动两个服务。两个端一共就是四个端口。由于我们启动在一台机器上,故这四个服务的启动端口首先不能有重复。
2.发送端需要指定的除了发送服务启动在哪个端口上,还需要接收方的IP地址+端口号。此时,发送方的端口号就是接收端启动的端口号。
5.TCP编程
需求:完成网络登录功能:
- 用户输入用户名密码,服务器给出登录成功或失败的提示
分析
- 使用基于CP协议的Socket网络编程实现
- TCP协议基于请求-相应模式
- 在网络通讯中,第一次主动发起通讯的程序被称作客户端(Client)程序
- 第一次通讯中等待连接的程序被称作服务器端(Server)程序
- 利用IO流实现数据的传输
详细步骤(通讯原理)
- 服务端创建ServerSocket,在指定端口监听并处理请求
- 客户端创建Socket,向服务端发送请求
网络登录功能分解
- 单向:客户端向服务器发送字符串,服务器获取字符串并输出
- 双向:服务器给出客户端反馈,客户端得到反馈并输出
- 文件:客户端向服务器上传文件,服务器端获取文件并反馈结果
- 多线程:服务器接收多个客户端的请求,并给出反馈,每个客户请求开启一个线程
基础功能实现(基础流程代码)
1 package com.wangxiaoyue.tcp; 2 3 import java.io.DataOutputStream; 4 import java.io.IOException; 5 import java.net.Socket; 6 7 /** 8 * 流程 9 * 创建客户端 10 * 1、使用Socket创建客户端,指定服务的IP和端口号进行连接 11 * 2、操作:输入输出流操作 12 * 3、释放资源 13 */ 14 public class Client { 15 public static void main(String[] args) throws IOException { 16 System.out.println("客户端启动中。。。"); 17 //1、使用Socket创建客户端,指定服务的IP和端口号进行连接 18 Socket client = new Socket("localhost", 8888); 19 //2、操作:输入输出流操作 20 DataOutputStream dos = new DataOutputStream(client.getOutputStream()); 21 String data = "hello"; 22 dos.writeUTF(data); 23 //3、释放资源 24 dos.close(); 25 client.close(); 26 } 27 } 28
1 package com.wangxiaoyue.tcp; 2 3 import java.io.DataInputStream; 4 import java.io.IOException; 5 import java.net.ServerSocket; 6 import java.net.Socket; 7 8 /** 9 * 流程 10 * 创建服务器 11 * 1、指定端口 使用ServerSocket创建服务器 12 * 2、阻塞式等待连接accept 13 * 3、操作:输入输出流操作 14 * 4、释放资源 15 */ 16 public class Server { 17 public static void main(String[] args) throws IOException { 18 System.out.println("服务端启动中。。。"); 19 //1、指定端口 使用ServerSocket创建服务器 20 ServerSocket server = new ServerSocket(8888); 21 //2、阻塞式等待连接accept 22 Socket client = server.accept(); 23 System.out.println("一个客户端建立了连接"); 24 //3、操作:输入输出流操作 25 DataInputStream dis = new DataInputStream(client.getInputStream()); 26 String data = dis.readUTF(); 27 System.out.println(data); 28 //4、释放资源 29 dis.close(); 30 client.close(); 31 } 32 } 33
支持多个客户端连接同一服务器
使用面向对象的思想对代码进行封装
客户端 (封装初始化、发送数据、接收数据、关闭资源)
1 package com.wangxiaoyue.tcp; 2 3 import com.sun.org.apache.bcel.internal.generic.InstructionConstants; 4 5 import javax.sound.midi.Soundbank; 6 import java.io.*; 7 import java.net.Socket; 8 9 /** 10 * 创建客户端 使用面向对象思想封装 11 * 1、使用Socket创建客户端,指定服务的IP和端口号进行连接 12 * 2、操作:输入输出流操作 13 * 3、释放资源 14 */ 15 public class LoginMultiClient { 16 public static void main(String[] args) throws IOException { 17 System.out.println("客户端启动中。。。"); 18 String msg = Client.init("localhost", 8888); 19 //2、操作:输入输出流操作 20 Client.send(msg); 21 String result = Client.read(); 22 System.out.println(result); 23 Client.close(); 24 //3、释放资源 25 26 } 27 28 private static class Client { 29 private static Socket client; 30 private static DataOutputStream dos; 31 private static DataInputStream dis; 32 private static BufferedReader br; 33 34 private static void send(String msg) { 35 try { 36 dos = new DataOutputStream(client.getOutputStream()); 37 dos.writeUTF(msg); 38 dos.flush(); 39 } catch (IOException e) { 40 e.printStackTrace(); 41 } 42 } 43 44 private static String read() { 45 try { 46 dis = new DataInputStream(client.getInputStream()); 47 String msg = dis.readUTF(); 48 return msg; 49 } catch (IOException e) { 50 e.printStackTrace(); 51 return null; 52 } 53 } 54 55 private static String init(String IP,int port) { 56 try { 57 br = new BufferedReader(new InputStreamReader(System.in)); 58 //1、使用Socket创建客户端,指定服务的IP和端口号进行连接 59 client = new Socket(IP, port); 60 System.out.println("请输入用户名:"); 61 String uname = br.readLine(); 62 System.out.println("请输入密码:"); 63 String upwd = br.readLine(); 64 return ("uname=" + uname + "&upwd=" + upwd); 65 } catch (IOException e) { 66 e.printStackTrace(); 67 return null; 68 } 69 } 70 private static void close() { 71 try { 72 br.close(); 73 dis.close(); 74 dos.close(); 75 } catch (IOException e) { 76 e.printStackTrace(); 77 } 78 } 79 } 80 81 82 83 } 84
服务器端 (封装客户端连接后的一些操作,使之可以开启多线程同时响应多个客户端)
1 package com.wangxiaoyue.tcp; 2 3 import java.io.*; 4 import java.net.Socket; 5 6 /** 7 * 创建客户端 使用面向对象思想封装 8 * 1、使用Socket创建客户端,指定服务的IP和端口号进行连接 9 * 2、操作:输入输出流操作 10 * 3、释放资源 11 */ 12 public class LoginMultiClient { 13 public static void main(String[] args) throws IOException { 14 System.out.println("客户端启动中。。。"); 15 Clientdo client = new Clientdo(); 16 String msg = client.init("localhost", 8888); 17 //2、操作:输入输出流操作 18 client.send(msg); 19 String result = client.read(); 20 System.out.println(result); 21 client.close(); 22 //3、释放资源 23 24 } 25 26 private static class Clientdo { 27 private Socket client; 28 private DataOutputStream dos; 29 private DataInputStream dis; 30 private BufferedReader br; 31 32 private void send(String msg) { 33 try { 34 dos = new DataOutputStream(client.getOutputStream()); 35 dos.writeUTF(msg); 36 dos.flush(); 37 } catch (IOException e) { 38 e.printStackTrace(); 39 } 40 } 41 42 private String read() { 43 try { 44 dis = new DataInputStream(client.getInputStream()); 45 String msg = dis.readUTF(); 46 return msg; 47 } catch (IOException e) { 48 e.printStackTrace(); 49 return null; 50 } 51 } 52 53 private String init(String IP,int port) { 54 try { 55 br = new BufferedReader(new InputStreamReader(System.in)); 56 //1、使用Socket创建客户端,指定服务的IP和端口号进行连接 57 client = new Socket(IP, port); 58 System.out.println("请输入用户名:"); 59 String uname = br.readLine(); 60 System.out.println("请输入密码:"); 61 String upwd = br.readLine(); 62 return ("uname=" + uname + "&upwd=" + upwd); 63 } catch (IOException e) { 64 e.printStackTrace(); 65 return null; 66 } 67 } 68 private void close() { 69 try { 70 br.close(); 71 dis.close(); 72 dos.close(); 73 } catch (IOException e) { 74 e.printStackTrace(); 75 } 76 } 77 } 78 } 79