一、概述
1.计算机网络
是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,
在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。
2.网络编程
就是用来实现网络互连的不同计算机上运行的程序间可以进行数据交换。
3.网络模型
计算机网络之间以何种规则进行通信,就是网络模型研究问题。
网络模型一般是指
OSI(Open System Interconnection开放系统互连)参考模型
TCP/IP参考模型
OSI参考模型 TCP/IP参考模型
应用层 应用层
表示层
会话层
传输层 传输层
网络层 网际层
数据链路层 主机至网络层
物理层
4.网络通信三要素
IP地址:InetAddress
网络中设备的标识,不易记忆,可用主机名
要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接受数据的计算机和识别发送的计算机,在TCP/IP协议中,这个标识号就是IP地址。
那么,我们如果获取和操作IP地址呢?
为了方便我们对IP地址的获取和操作,java提供了一个类InetAddress 供我们使用。
public static InetAddress getByName(String host):根据主机名或者IP地址的字符串表示得到IP地址对象
public String getHostName():获取主机名称。
public String getHostAddress():获取主机地址。
例如:
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
InetAddress address = InetAddress.getByName("127.0.0.1");
// 获取两个东西:主机名,IP地址
// 获取主机名称
String name = address.getHostName();
// 获取主机IP地址
String ip = address.getHostAddress();
System.out.println(name + "---" + ip);
}
}
端口号
用于标识进程的逻辑地址,不同进程的标识
物理端口 网卡口
逻辑端口 我们指的就是逻辑端口
A:每个网络程序都会至少有一个逻辑端口
B:用于标识进程的逻辑地址,不同进程的标识
C:有效端口:0~65535,其中0~1024系统使用或保留端口。
通过360可以查看端口号
传输协议
通讯的规则
常见协议:TCP,UDP
UDP
将数据源和目的封装成数据包中,
不需要建立连接;
每个数据报的大小在限制在64k;
因无连接,是不可靠协议;
不需要建立连接,速度快
TCP
建立连接,形成传输数据的通道;
在连接中进行大数据量传输;
通过三次握手完成连接,是可靠协议;
必须建立连接,效率会稍低
二、Socket
Socket套接字:
网络上具有唯一标识的IP地址和端口号组合在一起才能构成唯一能识别的标识符套接字。
Socket原理机制:
通信的两端都有Socket。
网络通信其实就是Socket间的通信。
数据在两个Socket间通过IO传输。
三、UDP传输
在Java中提供DatagramSocket,此类表示用发送和接收数据包的套接字,即Socket。
发送思路:
1.建立UDPSocket服务
2.提供数据,并将数据封装到数据包中(DatagramPacket)
3.通过Socket服务的发送功能,将数据包发出去
4.关闭资源
步骤:
1.创建UDP服务,通过DatagramSocket对象。
2.确定数据,并封装成数据包,通过DatagramPacket对象。
3.通过socket服务,将已有的数据包发送出去,通过send()方法。
4.关闭资源
接收思路:
1.定义UDPSocket服务,通常会监听一个端口,
其实就是给这个接收网络应用程序定义数字标识,方便与明确那些数据过来该应用程序可以处理
2.定义一个数据包,因为要存储接收到的字节数据,因为数据包对象中有更多的功能可以提取字节数据中的不同数据信息。
3.通过socket服务的receive方法将收到的数据存入已定义好的数据包中。
4.通过数据包对象中特有的方法,将这些不同的数据取出。
5.关闭资源
步骤:
1.创建UDP服务,还是通过DatagramSocket对象,并指定端口
2.定义数据包,用于储存数据。
3.通过服务的receive()方法接收到的数据存入数据包中。
4.通过数据包中的方法获取其中的数据。
5.关闭资源。
例如:
//接收端 将发送过来的数据接收,并解析。
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
// 创建接收端Socket对象,并监听一个端口
// DatagramSocket(int port)
DatagramSocket ds = new DatagramSocket(10086);
// 创建一个数据包(接收容器)
// DatagramPacket(byte[] buf, int length)
byte[] bys = new byte[1024];
int length = bys.length;
DatagramPacket dp = new DatagramPacket(bys, length);
// 调用Socket对象的接收方法接收数据
// public void receive(DatagramPacket p)
ds.receive(dp); // 阻塞式
// 解析数据包,
// 获取对方的ip
// public InetAddress getAddress()
InetAddress address = dp.getAddress();
String ip = address.getHostAddress();
// public byte[] getData():获取数据缓冲区
// public int getLength():获取数据的实际长度
byte[] bys2 = dp.getData();
int len = dp.getLength();
String s = new String(bys2, 0, len);
System.out.println(ip + "传递的数据是:" + s);
// 释放资源
ds.close();
}
}
//发送端 将一段数据发送出去。
public class SendDemo {
public static void main(String[] args) throws IOException {
// 创建发送端Socket对象
// DatagramSocket()
DatagramSocket ds = new DatagramSocket();
// 创建数据,并把数据打包
// DatagramPacket(byte[] buf, int length, InetAddress address, int port)
// 创建数据
byte[] bys = "hello,udp".getBytes();
// 长度
int length = bys.length;
// IP地址对象
InetAddress address = InetAddress.getByName("127.0.0.1");
// 端口
int port = 10086;
DatagramPacket dp = new DatagramPacket(bys, length, address, port);
// 调用Socket对象的发送方法发送数据包
// public void send(DatagramPacket p)
ds.send(dp);
// 释放资源
ds.close();
}
}
//改进版
public class SendDemo {
public static void main(String[] args) throws IOException {
// 创建发送端的Socket对象
DatagramSocket ds = new DatagramSocket();
// 创建数据并打包
byte[] bys = "helloworld".getBytes();
DatagramPacket dp = new DatagramPacket(bys, bys.length,
InetAddress.getByName("127.0.0.1"), 12345);
// 发送数据
ds.send(dp);
// 释放资源
ds.close();
}
}
多次启动接收端:
java.net.BindException: Address already in use: Cannot bind
端口被占用。
即如果出现这个异常,表示监听的这个端口已被占用,换个端口就好了。
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
// 创建接收端的Socket对象
DatagramSocket ds = new DatagramSocket(12345);
// 创建一个数据包
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
// 接收数据
ds.receive(dp);
// 解析数据
String ip = dp.getAddress().getHostAddress();
String s = new String(dp.getData(), 0, dp.getLength());
System.out.println("from " + ip + " data is : " + s);
// 释放资源
ds.close();
}
}
练习一:
/*
数据来自于键盘录入
键盘录入数据要自己控制录入结束。
*/
/*
思路分析:
1.创建socket对象
2.键盘录入,在读取到的数据进行判断是否有自定义的结束标记。
3.将数据打包
4.发送数据
5.关闭资源
*/
public class SendDemo {
public static void main(String[] args) throws IOException {
// 创建发送端的Socket对象
DatagramSocket ds = new DatagramSocket();
// 封装键盘录入数据
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
while ((line = br.readLine()) != null) {
if ("886".equals(line)) {
break;
}
// 创建数据并打包
byte[] bys = line.getBytes();
DatagramPacket dp = new DatagramPacket(bys, bys.length,
InetAddress.getByName("127.0.0.1"), 12345);
// 发送数据
ds.send(dp);
}
// 释放资源
ds.close();
}
}
/*
思路分析
1.创建socket对象
2.因为不知道有多少数据,所以用while循环包起来。
3.建立数据包对象
4.接收数据。解析数据。
*/
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
// 创建接收端的Socket对象
DatagramSocket ds = new DatagramSocket(12345);
while (true) {
// 创建一个数据包
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
// 接收数据
ds.receive(dp);
// 解析数据
String ip = dp.getAddress().getHostAddress();
String s = new String(dp.getData(), 0, dp.getLength());
System.out.println("from " + ip + " data is : " + s);
}
// 释放资源
// 接收端应该一直开着等待接收数据,是不需要关闭
// ds.close();
}
}
练习二:
编写一个聊天程序,有接收部分,和发送部分,
这两个部分需要同时执行,
就需要用到多线程技术。
一个线程控制接收,一个线程控制发送。
因为收发动作是不一致的,所以要定义两个run方法,而且这两个方法要封装到不同的类中。
public class ChatRoom {
public static void main(String[] args) throws IOException {
//建立发送和接收对象
DatagramSocket dsSend = new DatagramSocket();
DatagramSocket dsReceive = new DatagramSocket(12306);
SendThread st = new SendThread(dsSend);
ReceiveThread rt = new ReceiveThread(dsReceive);
Thread t1 = new Thread(st);
Thread t2 = new Thread(rt);
t1.start();
t2.start();
}
}
public class ReceiveThread implements Runnable {
private DatagramSocket ds;
public ReceiveThread(DatagramSocket ds) {
this.ds = ds;
}
@Override
public void run() {
try {
while (true) {
// 创建一个数据包
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys, bys.length);
// 接收数据
ds.receive(dp);
// 解析数据
String ip = dp.getAddress().getHostAddress();
String s = new String(dp.getData(), 0, dp.getLength());
System.out.println("from " + ip + " data is : " + s);
}
} 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 br = new BufferedReader(new InputStreamReader(
System.in));
String line = null;
while ((line = br.readLine()) != null) {
if ("886".equals(line)) {
break;
}
// 创建数据并打包
byte[] bys = line.getBytes();
DatagramPacket dp = new DatagramPacket(bys, bys.length,
InetAddress.getByName("127.0.0.1"), 12306);
// 发送数据
ds.send(dp);
}
// 释放资源
ds.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
四、TCP传输
在Java中提供Socket和ServerSocket对象使用
建立客户端和服务器端
建立连接后,通过Socket中的IO流进行数据的传输
关闭socket
同样,客户端与服务器端是两个独立的应用程序。
TCP客户端思路:
1:建立客户端的Socket服务,并明确要连接的服务器。
2:如果连接建立成功,就表明,已经建立了数据传输的通道.
就可以在该通道通过IO进行数据的读取和写入.该通道称为Socket流,Socket流中既有读取流,也有写入流.
3:通过Socket对象的方法,可以获取这两个流
4:通过流的对象可以对数据进行传输
5:如果传输数据完毕,关闭资源
步骤:
1.创建Socket服务,并制定要链接的主机和端口。
2.为了发送数据,应该获取socket流中输出流getOutputStream();
3.写入数据
4.关闭资源
TCP服务端思路:
1:建立服务器端的socket服务,需要一个端口
2:服务端没有直接流的操作,而是通过accept方法获取客户端对象,
在通过获取到的客户端对象的流和客户端进行通信
3:通过客户端的获取流对象的方法,读取数据或者写入数据
4:如果服务完成,需要关闭客户端,然后关闭服务器,
但是,一般会关闭客户端,不会关闭服务器,因为服务端是一直提供服务的
步骤:
1.建立服务端Socket服务,并监听一个端口。(ServerSocket)
2.获取连接过来的客户端对象,通过ServerSocket对象的accept()方法,没有连接就会等待,阻塞式方法。
3.获取客户端发送过来的数据,那么要使用客户端对象的读取流来读取数据。
4.关闭客户端。
例如:
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 创建接收端的Socket对象
// ServerSocket(int port)
ServerSocket ss = new ServerSocket(8888);
// 监听客户端连接。返回一个对应的Socket对象
// public Socket accept()
Socket s = ss.accept(); // 侦听并接受到此套接字的连接。此方法在连接传入之前一直阻塞。
// 获取输入流,读取数据显示在控制台
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys); // 阻塞式方法
String str = new String(bys, 0, len);
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip + "---" + str);
// 释放资源
s.close();
// ss.close(); //这个不应该关闭
}
}
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建Socket对象
Socket s = new Socket("127.0.0.1", 8888);
// 获取输出流,写数据
// public OutputStream getOutputStream()
OutputStream os = s.getOutputStream();
os.write("hello,tcp".getBytes());
// 释放资源
s.close();
}
}
练习一:
//客户端给服务端发送数据,服务端接收到后,给客户端反馈信息。
public class ClientDemo {
public static void main(String[] args) throws IOException {
// 创建客户端Socket对象
Socket s = new Socket("127.0.0.1", 9999);
// 获取输出流
OutputStream os = s.getOutputStream();
os.write("今天天气很好,适合睡觉".getBytes());
// 获取输入流
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys);// 阻塞
String client = new String(bys, 0, len);
System.out.println("client:" + client);
// 释放资源
s.close();
}
}
public class ServerDemo {
public static void main(String[] args) throws IOException {
// 创建服务器Socket对象
ServerSocket ss = new ServerSocket(9999);
// 监听客户端的连接
Socket s = ss.accept(); // 阻塞
// 获取输入流
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len = is.read(bys); // 阻塞
String server = new String(bys, 0, len);
System.out.println("server:" + server);
// 获取输出流
OutputStream os = s.getOutputStream();
os.write("数据已经收到".getBytes());
// 释放资源
s.close();
// ss.close();
}
}
练习二:
客户端发送文本文件,服务器输出文本文件,并给出反馈
/*
出现问题
* 按照我们正常的思路加入反馈信息,结果却没反应。为什么呢?
* 读取文本文件是可以以null作为结束信息的,但是呢,通道内是不能这样结束信息的。
* 所以,服务器根本就不知道你结束了。而你还想服务器给你反馈。所以,就相互等待了。
*
* 如何解决呢?
* A:在多写一条数据,告诉服务器,读取到这条数据说明我就结束,你也结束吧。
* 这样做可以解决问题,但是不好。
* B:Socket对象提供了一种解决方案
* public void shutdownOutput()
*/
public class UploadServer {
public static void main(String[] args) throws IOException {
// 创建服务器端的Socket对象
ServerSocket ss = new ServerSocket(11111);
// 监听客户端连接
Socket s = ss.accept();// 阻塞
// 封装通道内的流
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
// 封装文本文件
BufferedWriter bw = new BufferedWriter(new FileWriter("Copy.java"));
String line = null;
while ((line = br.readLine()) != null) { // 阻塞
bw.write(line);
bw.newLine();
bw.flush();
}
// 给出反馈
BufferedWriter bwServer = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
bwServer.write("文件上传成功");
bwServer.newLine();
bwServer.flush();
// 释放资源
bw.close();
s.close();
}
}
public class UploadClient {
public static void main(String[] args) throws IOException {
// 创建客户端Socket对象
Socket s = new Socket("127.0.0.1", 11111);
// 封装文本文件
BufferedReader br = new BufferedReader(new FileReader(
"InetAddressDemo.java"));
// 封装通道内流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
String line = null;
while ((line = br.readLine()) != null) { // 阻塞
bw.write(line);
bw.newLine();
bw.flush();
}
//Socket提供了一个终止,它会通知服务器你别等了,我没有数据过来了
s.shutdownOutput();
// 接收反馈
BufferedReader brClient = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String client = brClient.readLine(); // 阻塞
System.out.println(client);
// 释放资源
br.close();
s.close();
}
}
练习三:
//上传图片
public class UploadServer {
public static void main(String[] args) throws IOException {
// 创建服务器Socket对象
ServerSocket ss = new ServerSocket(19191);
// 监听客户端连接
Socket s = ss.accept();
// 封装通道内流
BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
// 封装图片文件
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream("mm.jpg"));
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
bos.flush();
}
// 给一个反馈
OutputStream os = s.getOutputStream();
os.write("图片上传成功".getBytes());
bos.close();
s.close();
}
}
public class UploadClient {
public static void main(String[] args) throws IOException {
// 创建客户端Socket对象
Socket s = new Socket("127.0.0.1", 19191);
// 封装图片文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(
"aa.jpg"));
// 封装通道内的流
BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
byte[] bys = new byte[1024];
int len = 0;
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
bos.flush();
}
s.shutdownOutput();
// 读取反馈
InputStream is = s.getInputStream();
byte[] bys2 = new byte[1024];
int len2 = is.read(bys2);
String client = new String(bys2, 0, len2);
System.out.println(client);
// 释放资源
bis.close();
s.close();
}
}
练习四:
//通过多线程改进客户端发送文本文件,服务器输出文本文件,并给出反馈
public class UploadServer {
public static void main(String[] args) throws IOException {
// 创建服务器Socket对象
ServerSocket ss = new ServerSocket(11111);
while (true) {
Socket s = ss.accept();
new Thread(new UserThread(s)).start();
}
}
}
public class UploadClient {
public static void main(String[] args) throws IOException {
// 创建客户端Socket对象
Socket s = new Socket("127.0.0.1", 11111);
BufferedReader br = new BufferedReader(new FileReader(
"Demo.java"));
// 封装通道内流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(
s.getOutputStream()));
String line = null;
while ((line = br.readLine()) != null) { // 阻塞
bw.write(line);
bw.newLine();
bw.flush();
}
// Socket提供了一个终止,它会通知服务器你别等了,我没有数据过来了
s.shutdownOutput();
// 接收反馈
BufferedReader brClient = new BufferedReader(new InputStreamReader(
s.getInputStream()));
String client = brClient.readLine(); // 阻塞
System.out.println(client);
// 释放资源
br.close();
s.close();
}
}
public class UserThread implements Runnable {
private Socket s;
public UserThread(Socket s) {
this.s = s;
}
@Override
public void run() {
try {
// 封装通道内的流
BufferedReader br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
// 为了防止名称冲突
String newName = System.currentTimeMillis() + ".java";
BufferedWriter bw = new BufferedWriter(new FileWriter(newName));
String line = null;
while ((line = br.readLine()) != null) { // 阻塞
bw.write(line);
bw.newLine();
bw.flush();
}
// 给出反馈
BufferedWriter bwServer = new BufferedWriter(
new OutputStreamWriter(s.getOutputStream()));
bwServer.write("文件上传成功");
bwServer.newLine();
bwServer.flush();
// 释放资源
bw.close();
s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}