个人主页:Hello Code. 本文专栏:Java基础知识 更多相关内容请点击前往Java基础知识总结【目录】查看 如有问题,欢迎指正,一起学习~~
网络编程入门
网络编程:在网络通信协议下,不同计算机上运行的程序,可以进行数据传输 网络编程三要素
- IP地址:设备在网络中的地址,是唯一的标识
- 端口:应用程序在设备中唯一的标识
- 协议:数据在网络中传输的规则,常见的协议有UDP和TCP协议
IP
IP:全称“互联网协议地址”,也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6
通过域名访问服务器->域名通过DNS服务器解析为IP地址传递回去->计算机再通过解析好的IP地址访问相应的服务器->服务器返回数据展示在浏览器上
IPv4
- 32bit(4字节):1 100000000 10101000 00000001 01000010
点分十进制表示法:192.168.1.66
IPv6
- 由于互联网的蓬勃发展,IP地址的需求量愈来愈大,而IPv4的模式下IP的总数是有限的。
采用128位地址长度,分成8组,每16位分为一组
- 冒分十六进制表示法:2001:0DB8:0000:0023:0008:0800:200C:417A
省略前面的0:2001:DB8:0:23:8:800:200C:417A
- 特殊情况:如果计算出的16进制表示形式中间有多个连续的0(FF01:0:0:0:0:0:0:1101)
采用0位压缩表示法:FF01::1101
常用命令
ipconfig :查看本机IP地址ping IP地址 :检查网络是否连通
InetAddress的使用
为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用 InetAddress:此类表示Internet协议(IP)地址
常用方法
static InetAddress getByName(String host) :确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址String getHostName() :获取此IP地址的主机名String getHostAddress() :返回文本显示中的IP地址字符串
端口
端口:应用程序在设备中唯一的标识
端口号:用两个字节表示的整数,它的取值范围是0~65535
其中0~1023之间的端口号用于一些知名的网络服务或者应用 在自己使用时使用1024以上的端口号就可以了
注意:一个端口号只能被一个应用程序使用
协议
在计算机网络中,连接和通信的规则被称为网络通信协议
UDP协议 用户数据报协议(User Datagram Protocol)
速度快,有大小限制,一次最多发送64K,数据不安全,容易丢失数据
TCP协议
- 传输控制协议(Transmission Control Protocol)
- TCP协议是面向连接的通信协议
速度慢,没有大小限制,数据安全
UDP通讯程序
UDP发送端
发送数据步骤
- 创建发送端的DatagramSocket对象
- 创建数据,并把数据打包(DatagramPacket)
- 调用DatagramSocket对象的方法发送数据
- 释放资源
代码实现
public class Demo{
public static void main(String[] args) throws IOException{
DatagramSocket ds = new DatagramSocket();
String s = "需要发送的数据";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.send(dp);
ds.close();
}
}
UDP接收端
步骤
- 创建接收端的DatagramSocket对象
- 创建一个箱子,用于接收数据
- 调用DatagramSocket的方法接收数据并将数据放入箱子中
- 解析数据包,并把数据在控制台显示
- 释放资源
代码
public class Demo{
public static void main(String[] args) throws{
DatagramSocket ds = new DatagramSocket(10000);
byte[] bytes = new bytes[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
System.out.println(new String(data));
ds.close();
}
}
在这里运行时必须先运行接收端再运行发送端(否则都发送完了才运行接收端就接收不到了)
如果接收端在启动之后没有接收到数据,会阻塞
在接收数据的时候,需要调用一个getLength方法,表示接收到了多少字节
UDP练习
- UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束
- UDP接收端:因为不知道发送端什么时候停止发送,就死循环接收
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Scanner;
public class ClientDemo {
public static void main(String[] args) throws IOException{
Scanner sc = new Scanner(System.in);
DatagramSocket ds = new DatagramSocket();
while(true){
String s = sc.nextLine();
if("886".equals(s)) break;
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("127.0.0.1");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.send(dp);
}
ds.close();
}
}
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
public class ServerDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(10000);
while(true){
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data, 0, length));
}
}
}
三种通信方式
单播:一个发送端通过一个路由器只发送给一个接收端(一对一)
组播:一个发送端通过一个路由器发送给一组接收端(一对多)
- 组播地址:224.0.0.0~239.255.255.255
其中224.0.0.0~224.0.0.255为预留的组播地址,不可用
- 组播的发送端在调用DatagramSocket对象的方法发送数据时,单播是发送给指定IP的电脑,组播是发送给组播地址
- 组播的接收端创建的是MulticastSocket对象
在创建箱子后,还要把当前电脑添加到这一组中
public static void main(String[] args) throws IOException{
DatagramSocket ds = new DatagramSocket();
String s = "Hello Word";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("224.0.1.0");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.send(dp);
ds.close();
}
public static void main(String[] args) throws IOException{
MulticastSocket ms = new MulticastSocket(10000);
DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);
ms.joinGroup(InetAddress.getByName("224.0.1.0"));
ms.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data, 0, length));
ms.close();
}
广播:一个发送端通过一个路由器发送给该局域网下所有接收端(一对所有)
public static void main(String[] args) throws IOException{
DatagramSocket ds = new DatagramSocket();
String s = "广播发送端";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getByName("255.255.255.255");
int port = 10000;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.sent(dp);
ds.close();
}
public class ServerDemo {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(10000);
while(true){
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
ds.receive(dp);
byte[] data = dp.getData();
int length = dp.getLength();
System.out.println(new String(data, 0, length));
}
ds.close();
}
}
TCP通讯程序
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象
发送端:Socket 接收端:ServerSocket
通信之前要保证连接已经建立 通过Socket产生IO流来进行网络通信
发送数据
- 创建客户端的Socket对象(Socket)与指定服务端连接
Socket(String host, int port) - 获取输出流,写数据
OutputStream getOutputStream() - 释放资源
void close()
代码实现
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.close();
}
接收数据
- 创建服务端的Socket对象(ServerSocket)
ServerSocket(int port) - 监听客户端连接,返回一个Socket对象
Socket accept() - 获得输入流,读数据,并把数据显示在控制台
InputStream getInputStream() - 释放资源
void close()
代码实现
public static void main(String[] args){
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
InputStream is = accept.getInputStream();
int b;
while((b = is.read()) != -1){
System.out.print((char) b);
}
is.close();
ss.close();
}
原理分析
- 不能先运行客户端(先运行客户端会没有服务端可连,就会报错)
- 先运行服务端,代码会依次执行到accept方法,然后就阻塞,等待客户端连接
- 运行客户端,在对象创建完毕之后,客户端与服务端的连接通道就已经建立
客户端写数据,服务端读数据
客户端创建对象并连接服务器,此时是通过三次握手协议保证服务器之间的连接
针对客户端,是往外写的,所以是输出流;而服务端是往进读的,所以是输入流
read方法也是阻塞的
在关流的时候,还多了一个往服务器写结束标记的动作
最后一步断开连接,会通过四次挥手协议保证连接终止
三次握手
保证客户端与服务器之间建立连接
- 第一次:客户端向服务器发出连接请求(等待服务器确认)
- 第二次:服务器向客户端返回一个响应(告诉客户端收到了请求)
- 第三次:客户端向服务端再次发出确认信息(连接建立)
四次挥手
保证客户端与服务端取消连接,成功终止连接
- 第一次:客户端向服务器发出取消连接请求
- 第二次:服务器向客户端返回一个响应,表示收到客户端取消请求
服务器将最后的数据处理完毕 - 第三次:服务器向客户端发出确认取消信息
- 第四次:客户端再次发送确认消息(连接取消)
练习
练习一
- 客户端:发送数据,接收服务器反馈
- 服务器:接收数据,给出反馈
- 代码
package TCP;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.io.OutputStream;
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());
socket.shutdownOutput();
InputStream is = socket.getInputStream();
int b;
while ((b = is.read()) != -1) {
System.out.print((char) b);
}
is.close();
os.close();
socket.close();
}
}
package TCP;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.InputStream;
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
InputStream is = (InputStream) accept.getInputStream();
int b;
while ((b = is.read()) != -1) {
System.out.print((char) b);
}
OutputStream os = accept.getOutputStream();
os.write("who?".getBytes());
os.close();
is.close();
accept.close();
ss.close();
}
}
练习二
- 客户端:将本地文件上传到服务器,接收服务器的反馈
- 服务器:接收客户上传的文件,上传完毕之后给出反馈
- 代码
package TCP;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
public class ClientDemo2 {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 10000);
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.png"));
OutputStream os = socket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
socket.close();
bis.close();
}
}
package TCP;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo2 {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10000);
Socket accept = ss.accept();
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("src\\copy.png"));
int b;
while ((b = bis.read()) != -1) {
bos.write(b);
}
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
bw.write("上传成功!");
bw.newLine();
bw.flush();
accept.close();
ss.close();
bos.close();
}
}
服务端优化
- 第一个弊端:服务器只能处理一个客户端请求,接受完一个文件之后,服务器就关闭了
改进方式:循环 - UUID
UUID uuid = UUID.randomUUID(); 生成一个随机且唯一的uiduuid.toString(); 转换为字符串- 防止多次上传文件时,后上传的文件覆盖掉先上传的文件
- 仅仅使用while循环,无法同时处理多个客户端的请求
采用多线程改进 - 使用多线程虽然可以让服务器同时处理多个客户端请求,但是资源消耗太大
采用线程池改进
|