概述:
在网络通信协议下,不同计算机上运行的程序,可以进行数据传输
网络编程三要素:ip地址、端口、协议
计算机软件结构:
- C/S结构 :全称为Client/Server结构,是指客户端和服务器结构
- B/S结构 :全称为Browser/Server结构,是指浏览器和服务器结构。
IP地址:
- 要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号,也就是
设备唯一的标识
IP地址分为两大类:
IPv4
是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个IP地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是“11000000 10101000 00000001 01000010”,这么长的地址,处理起来也太费劲了。为了方便使用,IP地址经常被写成十进制的形式,中间使用符号“.”分隔不同的字节。于是,上面的IP地址可以表示为“192.168.1.66”。IP地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多
IPv6
由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP的分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题
冒分十六进制表示法:
如果计算出的16进制表示形式中间有多个连续的0,可以用两个冒号省略,计算机会识别出并自动补全省略的0
例如:FF01:0:0:0:0:0:0:1101可以压缩为FF01::1101
DOS常用命令:
ipconfig:查看本机IP地址
ping IP地址:检查网络是否连通
特殊IP地址:
- 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
InetAddress:
InetAddress:此类表示Internet协议(IP)地址
方法名 | 说明 |
---|---|
static InetAddress getByName(String host) | 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址 |
String getHostName() | 获取此IP地址的主机名 |
String getHostAddress() | 返回文本显示中的IP地址字符串 |
演示:
public static void main(String[] args) throws UnknownHostException {
// static InetAddress getByName(String host) 确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
// InetAddress address = InetAddress.getByName("127.0.0.1");
InetAddress address = InetAddress.getByName("itzhuzhu");
// String getHostName() 获取此IP地址的主机名
System.out.println("主机名: " + address.getHostName());
// String getHostAddress() 返回文本显示中的IP地址字符串
System.out.println("ip地址" + address.getHostAddress());
}
端口:
端口
网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。
也就是应用程序的标识
端口号
- 用两个字节表示的整数,它的取值范围是0 ~ 65535。其中,0 ~ 1023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。
- 一个端口号只能被一个程序使用,如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
协议:
通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。
常见的协议有UDP协议和TCP协议
UDP:
UDP是无连接通信协议,在数据传输时,数据的发送端和接收端不建立逻辑连接。
简单来说,当一台 计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。 由于使用UDP协议消耗系统资源小,通信效率高,所以通常都会用于音频、视频、视频会议和普通数据的传输采用UDP协议,即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
特点:
1.不需要建立连接
2.可能会丢失数据
3.速度块
4.每个包最大64kb
应用场景: 速度快的场合(直播、视频通话)
UDP发送数据:
构造方法
方法名 | 说明 |
---|---|
DatagramSocket() | 创建数据报套接字并将其绑定到本机地址上的任何可用端口 |
DatagramPacket(byte[] buf,int len,InetAddress add,int port) | 创建数据包,发送长度为len的数据包到指定主机的指定端口 |
相关方法
方法名 | 说明 |
---|---|
void send(DatagramPacket p) | 发送数据报包 |
void close() | 关闭数据报套接字 |
void receive(DatagramPacket p) | 从此套接字接受数据报包 |
UDP接收数据:
构造方法
方法名 | 说明 |
---|---|
DatagramPacket(byte[] buf, int len) | 创建一个DatagramPacket用于接收长度为len的数据包 |
相关方法
方法名 | 说明 |
---|---|
byte[] getData() | 返回数据缓冲区 |
int getLength() | 返回要发送的数据的长度或接收的数据的长度 |
发送端:
public class UdpService {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket();
Scanner sc = new Scanner(System.in);
while (true) {
// 输入结束就关闭发送
String s = sc.nextLine();
if ("结束".equals(s)) {
break;
} else {
// DatagramPacket (字节数组,发送的长度,接收端ip地址,接收端ip地址)
// DatagramPacket(byte[] buf,int len,InetAddress add,int port) 创建数据包,发送长度为len的数据包到指定主机的指定端口
byte[] bytes = s.getBytes();
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("127.0.0.1"), 1998);
// void send(DatagramPacket p) 发送数据报包
socket.send(dp);
}
}
// void close() 关闭发送端
socket.close();
}
}
接收端:
public class UdpClient {
public static void main(String[] args) throws IOException {
// 创建对象,参数是要接收的端口,不写就是接收随机的
DatagramSocket socket = new DatagramSocket(1998);
while (true) {
byte[] bytes = new byte[1024];
// DatagramPacket(byte[] buf, int len) 创建一个DatagramPacket用于接收长度为len的数据包
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
// receive:接收数据
socket.receive(dp);
// byte[] getData() 返回数据缓冲区
// int getLength() 返回要发送的数据的长度或接收的数据的长度
System.out.println(new String(dp.getData(), 0, dp.getLength()));
}
// socket.close();
}
}
UDP三种通信方式:
单播:
单播用于两个主机之间的端对端通信,上面的代码就是单播
组播:
组播用于对一组特定的主机进行通信
广播:
广播用于一个主机对整个局域网上所有主机上的数据通信
UDP组播:
组播地址范围:224.0.0.0—239.255.255.255
224.0.0.0—224.0.0.255是预留组播的地址
发送端:
public class ClientDemo {
public static void main(String[] args) throws IOException {
// 创建发送端对象
DatagramSocket socket = new DatagramSocket();
String s = "我是组播";
// 转成字节数组
byte[] bytes = s.getBytes();
// 组播地址
InetAddress address = InetAddress.getByName("224.0.0.1");
// 端口号
int port = 1998;
// 添加到加到数据包
DatagramPacket packet = new DatagramPacket(bytes,bytes.length,address,port);
// 发送数据包里的数据
socket.send(packet);
// 释放资源
socket.close();
}
}
接收端:
public class ServiceDemo {
public static void main(String[] args) throws IOException {
// 创建接收端Socket对象(MulticastSocket)
MulticastSocket socket = new MulticastSocket(1998);
// 创建一个数据包,用于接收数据
DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
// 把当前计算机绑定一个组播地址,表示添加到这一组中
socket.joinGroup(InetAddress.getByName("224.0.0.1"));
// 把数据接收到数据包里
socket.receive(packet);
// 解析数据并打印
byte[] data = packet.getData();
int length = packet.getLength();
System.out.println(new String(data, 0, length));
socket.close();
}
}
广播:
广播地址:255.255.255.255
和单播是一模一样的改一下ip就行了,IP是规定的
TCP:
TCP协议是比较安全的通信协议,输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务端发出连接请求,每次连接的创建都需要经过“三次握手”,断开连接的时候需要四次挥手
特点:
1.需要建立连接
2.不会丢失数据
3.速度慢
4.建立连接后以流的形式传输,没有大小限制
应用场景: 要数据安全的场合(聊天、支付)
三次握手:
TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠,由于这种面向连接的特性, TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等
第一次握手,客户端向服务器端发出连接请求,等待服务器确认(客:我就蹭蹭不进去)
第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求(服:行)
第三次握手,客户端再次向服务器端发送确认信息,确认连接 完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。(客:开始了哦)
四次挥手:
客户端向服务器发出取消连接请求
服务器向客户端返回一个响应,表示收到请求
服务器向客户端发出确认取消请求
客户端再次确认连接取消
为什么握手只有三次,挥手却要四次
因为握手的时候两端没有连接,只要确认连接就行
但是挥手的时候是已经连接了,这个时候服务器要在第二次握手以后处理最后的数据,处理完最后数据跟客户端确认了才能取消
TCP发送数据:
方法名 | 说明 |
---|---|
Socket(InetAddress address,int port) | 创建流套接字并将其连接到指定IP指定端口号 |
Socket(String host, int port) | 创建流套接字并将其连接到指定主机上的指定端口号 |
相关方法
方法名 | 说明 |
---|---|
InputStream getInputStream() | 返回此套接字的输入流 |
OutputStream getOutputStream() | 返回此套接字的输出流 |
TCP接收数据:
构造方法
方法名 | 说明 |
---|---|
ServletSocket(int port) | 创建绑定到指定端口的服务器套接字 |
相关方法
方法名 | 说明 |
---|---|
Socket accept() | 监听要连接到此的套接字并接受它(等待客户端的连接,等不到会一直等) |
结束标记:
方法名 | 说明 |
---|---|
void shutdownInput() | 将此套接字的输入流放置在“流的末尾” |
void shutdownOutput() | 禁止用此套接字的输出流 |
接收端(服务器):
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
// 等待客户端连接
System.out.println("连接前");
Socket accept = ss.accept();
System.out.println("连接后");
InputStream is = accept.getInputStream();
int b;
while((b = is.read())!=-1){
System.out.println((char) b);
}
System.out.println("看看我执行了吗?");
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("你谁啊?");
bw.newLine();
bw.flush();
bw.close();
is.close();
accept.close();
ss.close();
}
}
发送端(客户端):
public class ClientDemo {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",10000);
OutputStream os = socket.getOutputStream();
os.write("hello".getBytes());
// os.close();如果在这里关流,会导致整个socket都无法使用
socket.shutdownOutput();//仅仅关闭输出流.并写一个结束标记,对socket没有任何影响
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while((line = br.readLine())!=null){
System.out.println(line);
}
br.close();
os.close();
socket.close();
}
}
文件上传:
客户端:
public class Client {
public static void main(String[] args) throws IOException {
// 创建Socket对象
Socket socket = new Socket("127.0.0.1", 1998);
// 读文件用 本地流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("/Users/itzhuzhu/Desktop/Java/ideaTest/io/a.txt"));
// 写到服务器 用网络流
OutputStream ops = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(ops);
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
}
bos.flush();
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String s;
while ((s = br.readLine()) != null) {
System.out.println(s);
}
socket.close();
bis.close();
bos.close();
}
}
多线程服务端:
public class ThreadSocket implements Runnable {
private Socket socket;
public ThreadSocket(Socket accept) {
this.socket = accept;
}
@Override
public void run() {
try {
// 本地流用来读取数据
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
// 把数据写到本地
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/Users/itzhuzhu/Desktop/Java/ideaTest/io/" + UUID.randomUUID().
toString() + "txt"));
int len;
while ((len = bis.read()) != -1) {
bos.write(len);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("上传成功");
bw.newLine();
bw.flush();
// 释放资源
bis.close();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务端:
public class Server {
public static void main(String[] args) throws IOException {
// 创建Socket对象
ServerSocket socket = new ServerSocket(1998);
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 60,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
while (true) {
// 等待客户端连接
Socket accept = socket.accept();
ThreadSocket threadSocket = new ThreadSocket(accept);
pool.submit(threadSocket);
}
}
}
UUID:
生成唯一的标识符
public static void main(String[] args) {
// 生成一个随机且唯一的字符串
System.out.println(UUID.randomUUID().toString());
// 替换字符串的符号,可以把_换成字符长度为0的空字符串
System.out.println(UUID.randomUUID().toString().replace("_",""));
}