端口
用于标识计算机上某个特定的网络程序
以整数的形式,端口范围 0 ~ 2^16-1
0 ~ 1024端口已经被占用 如 ssh: 22 ftp: 21 smtp: 25 http: 80
常见的网络端口号:
- tomcat:8080
- mysql:3306
- oracle:1521
- sqlserver:1433
网络协议
网络传输数据的组织形式
TCP/IP 传输控制协议 因特网互联协议
TCP和UDP的区别:
TCP协议:
- 使用TCP协议之前 必须先建立TCP连接 形成传输数据通道
- 传输前 采用“三次握手”方式 是可靠的
- TCP协议进行通信的两个应用进程: 客户端 服务端
- 在连接中可进行大数据量的传输
- 传输完毕 需释放已建立的连接 效率低
UDP协议:
- 将数据 源 目的封装成数据包 不需要建立连接
- 每个数据报的大小限制在64K内 不适合传输大量数据
- 因无需建立连接 所以是不可靠的
- 发送数据结束时无需释放资源 因为不是面向连接的 速度快
InetAddress类
public class InetAddressDemo01 {
public static void main(String[] args) throws UnknownHostException {
// 1.获取本机InetAddress对象
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost); // LAPTOP-KORV82EM/10.108.38.215
// 2.根据指定主机名 获取对象
InetAddress host1 = InetAddress.getByName("LAPTOP-KORV82EM");
System.out.println(host1); //LAPTOP-KORV82EM/10.108.38.215
// 3.根据域名返回对象
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println(host2); //www.baidu.com/14.215.177.38
// 4.通过InetAddress对象获取对应地址
String ip = host2.getHostAddress();
System.out.println(ip); //14.215.177.38
// 5.通过InetAddress对象获取主机名或域名
String hostName = host2.getHostName();
System.out.println(hostName); //www.baidu.com
}
}
Socket
Socket开发网络应用程序被广泛采用 以至于成为事实上的标准
通信的两端都要有Socket 是两台机器通信的端点
网络通信其实就是Socket之间的通信
Socket允许程序把网络连接当成是一个流 数据在两个Socket之间通过IO传输
一般主动发起通信的应用程序是客户端 等待通信请求的为服务端
TCP编程
客户端
public static void main(String[] args) throws IOException {
// 客户端
// 1. 连接服务器(ip 端口)
// 连接本地主机的9999端口 如果连接成功会返回一个Socket对象
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("连接端口成功" + socket.getClass());
// 2. 得到Socket对象关联的输出流对象 socket.getOutputStream()
OutputStream outputStream = socket.getOutputStream();
// 3. 通过输出流 写入数据到数据通道 设置结束标记
outputStream.write("hello, server".getBytes());
socket.shutdownOutput();
// 4. 通过输入流 获取通道数据
InputStream inputStream = socket.getInputStream();
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));
}
// 5. 关闭相关流和Socket
outputStream.close();
inputStream.close();
socket.close();
}
服务端
public static void main(String[] args) throws IOException {
// 服务端
// 1. 在本机9999端口监听 等待连接 要求9999端口没有被占用
// ServerSocket 可以通过accept返回多个Socket[多个客户端连接服务器的并发]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务端等待连接...");
// 2. 当没有客户端连接9999端口时 程序会阻塞 等待连接
// 如果有客户端连接 则会返回一个Socket对象 程序继续
Socket socket = serverSocket.accept();
System.out.println("服务端完成连接...");
// 3.通过socket.getInputStream() 读取客户端写入到数据通道的数据
InputStream inputStream = socket.getInputStream();
// 4. IO读取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen)); // 根据读取到的实际长度显示字符串
}
// 5. 写入数据 设置结束标记
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello, client".getBytes());
socket.shutdownOutput();
// 6. 关闭流
inputStream.close();
outputStream.close();
socket.close();
serverSocket.close();
}
注:
-
当服务端监听9999端口未开启时 客户端进行访问 会出现异常
-
当服务端开启 但客户端并未访问时 socket会阻塞
-
当客户端对服务端访问后 阻塞结束
-
服务端结束时 需关闭 serverSocket
-
发送数据时 要设置结束标记 否则客户端和服务端都会阻塞
-
字符流可以使用 write.newLine() 设置结束标记 但需要使用 redLine() 来读取
public static void main(String[] args) throws IOException { // 服务端 ServerSocket serverSocket = new ServerSocket(9999); Socket socket = serverSocket.accept(); InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); String input = br.readLine(); System.out.println(input); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream)); bw.write("hello, client"); bw.newLine(); bw.flush(); bw.close(); br.close(); socket.close(); serverSocket.close(); }
public static void main(String[] args) throws IOException { // 客户端 Socket socket = new Socket(InetAddress.getLocalHost(), 9999); OutputStream outputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream(); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(outputStream)); bw.write("hello, server"); bw.newLine(); // 插入一个换行符 表示写入内容结束 要求对方使用readLine() bw.flush(); // 字符流需要手动刷新 BufferedReader br = new BufferedReader(new InputStreamReader(inputStream)); String input = br.readLine(); System.out.println(input); br.close(); bw.close(); socket.close(); }
拷贝文件
思路:
- 读取本地文件信息 将信息转为字节数组
- 通过客户端 将字节数组发送给服务端
- 服务端接收数据
- 服务端将数据写入指定路径
- 服务端返回信息
- 客户端收取信息
服务端
public class FileCopyServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
// 1. 读取客户端发送的数据
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = FileCopyUtils.streamTOByteArray(bis);
// 2. 将得到的bytes数组 写入到指定路径
String path = "d:\\wx.jpg";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(path));
bos.write(bytes);
// 3. 向客户端回复请求
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("收到文件~");
bw.flush(); // 刷新内容到数据通道
socket.shutdownOutput(); // 设置结束标记
bw.close();
bos.close();
bis.close();
socket.close();
serverSocket.close();
}
}
客户端
public class FileCopyClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
// 1. 读取磁盘文件
String path = "C:\\Users\\81288\\Pictures\\wx.jpg";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
byte[] bytes = FileCopyUtils.streamTOByteArray(bis);
// 2. 获取Socket输出流 发送byte[]给服务端
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(bytes); // 将文件对应的字节数组写入数据通道
socket.shutdownOutput(); // 结束标记
// 3. 获取服务器传送的信息
InputStream inputStream = socket.getInputStream();
String s = FileCopyUtils.streamTOString(inputStream);
System.out.println(s);
inputStream.close();
bis.close();
bos.close();
socket.close();
}
}
工具类
public class FileCopyUtils {
// 通过ByteArrayOutputStream输出流 将输入流的信息转换为字节数组
public static byte[] streamTOByteArray(InputStream is) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int len;
while((len = is.read(b)) != -1) {
bos.write(b, 0, len);
}
byte[] array = bos.toByteArray();
bos.close();
return array;
}
// 将输入流的信息转换为字符串
public static String streamTOString(InputStream is) throws Exception {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line + "\r\n");
}
return builder.toString();
}
}
natstat
netstat -an 可以查看当前主机网络情况 包括端口监听情况和网络连接情况
netstat -an | more 可以分页显示
要求在dos控制台下执行
netstat -anb 可以查看使用应用程序信息 需管理员身份运行
TCP细节
当客户端连接服务端后 实际上客户端也是通过一个端口和服务端进行通讯的 这个端口是TCP/IP来分配的 是不确定的 是随机的 当传输完毕后 端口会被释放
UDP编程
类 DatagramSocket 和 DatagramPacket 实现了基于UDP协议网络程序
UDP数据报通过数据报套接字 DatagramSocket 发送和接收 系统不保证UDP数据报一定能够安全送到目的地 也不能确定什么时候可以抵达
DatagramPacket 对象封装了UDP的数据报 在数据报中包含了发送端的IP地址和端口号以及接收端的IP地址和端口号
UDP协议中每个数据报都给出了完整的地址信息 因此无须建立发送方和接收方的连接
UDP没有明确的服务端和客户端 会演变成数据的发送端和接收端 发送和接收数据都是通过 DatagramSocket 对象来完成 将数据封装到 DatagramPacket对象中 DatagramSocket可以指定在哪个端口接收数据
// 发送端
public class UDPSend01 {
public static void main(String[] args) throws IOException {
// 1. 创建一个DatagramSocket对象 准备在9998端口接收数据
DatagramSocket socket = new DatagramSocket(9998);
// 2. 将需要发送的数据封装到 DatagramPacket对象中
byte[] bytes = "hello world".getBytes();
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("10.108.105.38"), 9999);
// 3. 发送数据
socket.send(packet);
// 接收数据
byte[] buf = new byte[1024 * 64];
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
socket.receive(receivePacket);
int len = receivePacket.getLength();
byte[] data = receivePacket.getData();
System.out.println(new String(buf, 0, len));
// 4. 关闭资源
socket.close();
}
}
// 接收端
public class UDPReceive01 {
public static void main(String[] args) throws IOException {
// 1. 创建一个DatagramSocket对象 准备在9999端口接收数据
DatagramSocket socket = new DatagramSocket(9999);
// 2. 构建一个DatagramPacket对象 准备接收数据
// UDP 数据包最大是64K
byte[] bytes = new byte[1024 * 64];
DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
System.out.println("接收端等待数据..");
// 3. 调用接收方法 将通过网络传输的packet对象接收
// 有数据包发送到9999端口时会接收数据 否则会阻塞等待
socket.receive(packet);
System.out.println("接收端收到数据...");
// 4. 可以把packet进行拆包 取出数据
int length = packet.getLength(); // 实际接收到的数据长度
byte[] data = packet.getData(); // 接收到的数据
System.out.println(new String(data, 0, length));
// 发送数据
byte[] buf = "hello hello".getBytes();
DatagramPacket sendPacket = new DatagramPacket(buf, buf.length, InetAddress.getByName("10.108.105.38"), 9998);
socket.send(sendPacket);
// 5. 关闭资源
socket.close();
}
}