网络编程
概述
网络编程三要素:IP地址、端口号、协议
InetAddress类:为了方便我们对ip地址的获取和操作,Java提供了一个InetAddress类供我们使用
- static InetAddress getByName(String host) 确定主机名称的ip地址,主机名称可以是机器名称或ip地址
- String getHostName() 获取此ip地址的主机名
- String getHostAddress() 返回文本显示中的ip地址字符串
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* @author: 小码农
* @create: 2021-08-05 16:33
**/
public class Demo8 {
public static void main(String[] args) {
//InetAddress类
try {
InetAddress address = InetAddress.getByName("192.168.249.67");
String hostName = address.getHostName();
String ipAddress = address.getHostAddress();
System.out.println("主机名:"+hostName);
System.out.println("ip地址:"+ipAddress);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
/*
主机名:DESKTOP-7CFI8Q7
ip地址:192.168.249.67
*/
端口:设备上应用程序的唯一标识
端口号:用两个字节表示的整数,取值范围065535。其中01023用于一些知名的网络服务。普通应用程序使用1024~65535之间的端口号。如果端口号被另外一个服务占用,会导致当前程序启动失败
TCP协议:可靠,三次握手
- 客户端向服务器端发送连接请求,等待服务器确认
- 服务器端向客户端回复一个响应,通知客户端收到了连接请求
- 客户端再次向服务器端发送确认信息,确认连接
UDP协议:不可靠传输,它在通信两端各建立一个Socket对象。Java提供了DatagramSocket类作为UDP协议的Socket
UDP
UDP发送数据
步骤:
-
使用DatagramSocket创建发送端对象,发送端对象不用带端口号参数
DatagramSocket ds = new DatagramSocket();
-
构建存放发送数据的字节数组
byte[] data = new byte[1024];
-
使用DatagramPacket创建数据包对象,参数包括:数据,数据长度,InetAddress主机对象,端口号
DatagramPacket dp = new DatagramPacket(data,data.length,InetAddress.getByName("192.168.249.67"),10086);
-
调用DatagramSocket对象的send方法发送数据,参数为数据包对象
ds.send(dp);
-
关闭发送端
ds.close();
DatagramSocket类
DatagramPacket(byte[] b,int len,InetAddress add,int port)
构造一个数据包,发送长度为length的数据包到指定主机上的指定端口号
import java.io.IOException;
import java.net.*;
/**UDP发送端
* @author: 小码农
* @create: 2021-08-05 17:12
**/
public class Demo9 {
//创建Socket对象
DatagramSocket ds;
{
try {
ds = new DatagramSocket();
//创建数据
byte[] bytes = "Hello World".getBytes();
//调用send方法发送数据
DatagramPacket dp = new DatagramPacket(bytes,bytes.length,InetAddress.getByName("192.168.249.67"),10086);
ds.send(dp);
//关闭发送端
ds.close();
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
UDP接收数据
步骤:
-
创建接收端的Socket对象(DatagramSocket),UDP接收端的Socket对象需要传入端口号参数
DatagramSocket ds = new DatagramSocket(10086);
-
创建一个字节数组,用于接收数据
byte[] bytes = new byte[1024];
-
调用DatagramSocket对象的方法并接收数据,构建DatagramPacket对象传入参数:容器,容器长度
DatagramPacket dp = new DatagramPacket(bytes,bytes.length); ds.receive(dp);
-
解析数据包,并把数据包在控制台显示
String str = new String(dp.getData(),0,dp.getLength()); System.out.println("接收到的数据:"+str);
-
关闭接收端
ds.close();
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/**
* @author: 小码农
* @create: 2021-08-05 17:24
**/
public class Demo10 {
public static void main(String[] args) throws IOException {
//创建接收端对象
DatagramSocket ds = new DatagramSocket(10086);
byte[] bytes = new byte[1024];
//创建数据包
DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
//接收数据
ds.receive(dp);
//解析数据包,得到接收到的整体的数据包,有可能包含空字符
// byte[] bytes1 = dp.getData();
//使用getLength方法的到接受到的数据长度
String str = new String(dp.getData(),0,dp.getLength());
System.out.println("接收到的数据:"+str);
//关闭接收端
ds.close();
}
}
练习:UDP通信程序
UDP发送数据:数据来自于键盘录入,知道输入的数据是886,发送数据结束
UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
TCP
TCP通信原理
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以铜鼓哦虚拟链路进行通信
TCP发送数据
发送数据步骤:
- 创建客户端的Socket对象(Socket)
Socket socket = new Socket(InetAddress.getByName("192.168.249.67"),10010);
Socket socket2 = new Socket("192.168.249.67",10010);//参数一可以直接使用接收方IP地址
- 获取输入流,写数据
//获取输出流,写数据
OutputStream os = socket.getOutputStream();
byte[] bytes = "Hello World".getBytes();
os.write(bytes);
- 释放资源
//释放资源
socket.close();
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* @author: 小码农
* @create: 2021-08-05 19:43
**/
public class Demo_TCP_Send {
public static void main(String[] args) throws IOException {
//构造方法1 Socket(主机名,端口)
Socket socket = new Socket(InetAddress.getByName("192.168.249.67"),10010);
//构造方法2 Socket(IP,端口)
Socket socket2 = new Socket("192.168.249.67",10010);
//获取输出流,写数据
OutputStream os = socket.getOutputStream();
byte[] bytes = "Hello World".getBytes();
os.write(bytes);
//释放资源
socket.close();
socket2.close();
}
}
TCP接收数据
接收数据步骤:
- 创建服务器端的Socket对象(ServerSocket)
//创建服务器端ServerSocket 构造方法:ServerSocket(端口)
ServerSocket ss = new ServerSocket(10010);
- 监听客户端连接,返回一个Socket对象
//Socket accept()监听要连接到此套接字并接受它
Socket s = ss.accept();
- 获取输入流,读数据,并输出
//获取输入流,读数据,并把数据显示在控制台
InputStream is = s.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
String str = new String(bytes,0,len);
System.out.println("接收数据:"+str);
- 释放资源
ss.close();
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author: 小码农
* @create: 2021-08-05 19:51
**/
public class Demo_TCP_receive {
public static void main(String[] args) throws IOException {
//创建服务器端ServerSocket 构造方法:ServerSocket(端口)
ServerSocket ss = new ServerSocket(10010);
//Socket accept()监听要连接到此套接字并接受它
Socket s = ss.accept();
//获取输入流,读数据,并把数据显示在控制台
InputStream is = s.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes);
String str = new String(bytes,0,len);
System.out.println("接收数据:"+str);
//释放资源
s.close();
ss.close();
}
}
中断输入输出流的方法:
中断输出流:Socket对象.shutdownOutput();
中断输入流:Socket对象.shutdownInput();
练习1:
- 客户端:发送数据,接收服务器反馈。先写数据,再读数据
- 服务器:接收数据,发送反馈。先读数据,再写数据
客户端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/**
* 客户端程序
* @author: 小码农
* @create: 2021-08-05 20:06
**/
public class Demo12_Send {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s = new Socket("192.168.249.67",10101);
//获取输入流,写数据
OutputStream os = s.getOutputStream();
os.write("来自客户端的一条消息".getBytes());
//接收服务器端反馈
InputStream is = s.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b);
String str = new String(b,0,len);
System.out.println("服务端反馈:"+str);
//释放资源
s.close();
}
}
服务器端
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务器端程序
* @author: 小码农
* @create: 2021-08-05 20:06
**/
public class Demo12_Receive {
public static void main(String[] args) throws IOException {
//创建服务器端ServerSocket对象
ServerSocket ss = new ServerSocket(10101);
//监听客户端连接,返回一个Socket对象
Socket s = ss.accept();
//接收数据
InputStream is = s.getInputStream();
byte[] b = new byte[1024];
int len = is.read(b);
String str = new String(b,0,len);
System.out.println("接收数据:"+str);
//向客户端发送反馈
OutputStream os = s.getOutputStream();
os.write("接收完成".getBytes());
//释放资源
ss.close();
}
}
练习2:
- 客户端:将键盘数据数据发送到服务端,输入886时停止
- 服务器:将接收到的数据打印在控制台
客户端
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
/**
* @author: 小码农
* @create: 2021-08-05 20:19
**/
public class Demo13_send {
public static void main(String[] args) throws IOException {
Socket s = new Socket("192.168.249.67",10110);
//数据来自键盘录入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
//封装输出流对象,利用转换流OSW将获取的输入字节流,转化为字符输出流
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String str;
while ((str = br.readLine())!=null){
if (str.equals("886")){
break;
}
bw.write(str);
bw.newLine();
bw.flush();
}
s.close();
}
}
服务器端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author: 小码农
* @create: 2021-08-05 20:19
**/
public class Demo13_receive {
public static void main(String[] args) throws IOException {
//创建服务器端ServerSocket对象
ServerSocket ss = new ServerSocket(10110);
//监听客户端连接,返回一个对应的Socket对象
Socket s = ss.accept();
//使用字符输入流来读取数据,利用转换流ISR将输入的字节转化为字符
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str;
while ((str=br.readLine())!=null){
if (str.equals("886")){
break;
}
System.out.println(str);
}
ss.close();
}
}
练习3:
- 客户端:将键盘数据数据发送到服务端,输入886时停止
- 服务器:将接收到的数据写入文本文件
客户端
import java.io.*;
import java.net.Socket;
/**
* @author: 小码农
* @create: 2021-08-05 20:42
**/
public class Demo14_send {
public static void main(String[] args) throws IOException {
//第一步,创建Socket对象
Socket s = new Socket("192.168.249.67",10110);
//创建字符输出流对象
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String str;
while ((str=br.readLine())!=null){
if (str.equals("886")){
break;
}
bw.write(str);
bw.newLine();
bw.flush();
}
s.close();
}
}
服务器端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author: 小码农
* @create: 2021-08-05 20:46
**/
public class Demo14_receive {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket ss = new ServerSocket(10110);
//创建监听对象Socket
Socket s = ss.accept();
//创建File对象
BufferedWriter bw = new BufferedWriter(new FileWriter("data.txt",true));
//创建字符输入流对象
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str;
while((str=br.readLine())!=null){
if (str.equals("886")){
break;
}
System.out.println("写入:"+str);
bw.write(str);
bw.newLine();
bw.flush();
}
bw.close();
ss.close();
}
}
练习4:
- 客户端:将文本文件数据发送到服务端
- 服务器:将接收到的数据写入文本文件
在本练习中,服务端与练习3服务端相同,客户端不同
import java.io.*;
import java.net.Socket;
/**
* @author: 小码农
* @create: 2021-08-05 21:00
**/
public class Demo15 {
public static void main(String[] args) throws IOException {
//第一步,创建Socket对象
Socket s = new Socket("192.168.249.67",10110);
//创建字符输出流对象
BufferedReader br = new BufferedReader(new FileReader("data2.txt"));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String str;
while ((str=br.readLine())!=null){
bw.write(str);
bw.newLine();
bw.flush();
}
s.close();
}
}
练习5
- 客户端:数据来自于文本文件,接收服务器反馈
- 服务器:接收到的数据写入文本文件,给出反馈
客户端
import java.io.*;
import java.net.Socket;
/**
* @author: 小码农
* @create: 2021-08-05 21:08
**/
public class Demo16_Client {
public static void main(String[] args) throws IOException {
//创建Socket对象
Socket s = new Socket("192.168.249.67",10111);
//创建字符输入流对象
BufferedReader br = new BufferedReader(new FileReader("data2.txt"));
//创建字符输出流对象
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//创建输入流对象2,来接收服务器端的反馈
BufferedReader br2 = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str;
String str2;
while ((str=br.readLine())!=null){
bw.write(str);
bw.newLine();
bw.flush();
}
//中断输出流
s.shutdownOutput();
//接收服务器反馈
str2 = br2.readLine();
System.out.println(str2);
//关闭资源
bw.close();
br.close();
s.close();
}
}
服务器端
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author: 小码农
* @create: 2021-08-05 21:08
**/
public class Demo16_Server {
public static void main(String[] args) throws IOException {
//创建ServerSocket对象
ServerSocket ss = new ServerSocket(10111);
//创建监听器
Socket s = ss.accept();
//创建字符输入流对象
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//创建BufferedWriter2 用于将收到的数据写入文件
BufferedWriter bw2= new BufferedWriter(new FileWriter("data.txt",true));
//创建字符输出流对象,用于向客户端发送反馈信息
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
String str;
while ((str=br.readLine())!=null){
bw2.write(str);
bw2.newLine();
bw2.flush();
}
//给客户端反馈
bw.write("已写入");
bw.newLine();
bw.flush();
//关闭资源
bw.close();
bw2.close();
ss.close();
}
}
练习6
- 客户端:数据来自于文本文件,接收服务器反馈
- 服务器:接收到的数据写入文本文件,给出反馈。代码用线程进行封装,为每个客户端开启一个线程
ServerThread线程类
import java.io.*;
import java.net.Socket;
/**
* @author: 小码农
* @create: 2021-08-05 21:41
**/
public class ServerThread implements Runnable {
private Socket s;
public ServerThread(Socket s){
this.s=s;
}
@Override
public void run() {
//接收数据写到文本文件
BufferedReader br = null;
BufferedWriter bw = null;
BufferedWriter bw2 = null;
try {
br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//为了避免文件名重复,我们使用动态文件名的方式来构建目标文件名
int cut = 0;
File file = new File("data-"+cut+".txt");
//如果文件存在,则进入循环,找到一个不存在的文件名后离开循环
while (file.exists()){
cut++;
file = new File("data-"+cut+".txt");
}
bw = new BufferedWriter(new FileWriter(file));
String str;
while ((str=br.readLine())!=null){
bw.write(str);
bw.newLine();
bw.flush();
}
//给出反馈
bw2 = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
bw2.write("文件上传成功");
bw2.newLine();
bw2.flush();
//释放资源
} catch (IOException e) {
e.printStackTrace();
}finally {
if (br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw!=null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bw2!=null){
try {
bw2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服务端
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @author: 小码农
* @create: 2021-08-05 21:38
**/
public class Demo17_Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(10111);
while(true){
//监听客户端连接,返回一个对应的Socket对象
Socket s = ss.accept();
//为每一个客户端开启一个线程
new Thread(new ServerThread(s)).start();
}
}
}
客户端(可以多个)
import java.io.*;
import java.net.Socket;
/**
* @author: 小码农
* @create: 2021-08-05 21:38
**/
public class Demo17_Client {
public static void main(String[] args) throws IOException {
//创建Socket对象
Socket s = new Socket("192.168.249.67",10111);
//创建字符输入流对象
BufferedReader br = new BufferedReader(new FileReader("data2.txt"));
//创建字符输出流对象
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
//创建输入流对象2,来接收服务器端的反馈
BufferedReader br2 = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str;
String str2;
while ((str=br.readLine())!=null){
bw.write(str);
bw.newLine();
bw.flush();
}
//中断输出流
s.shutdownOutput();
//接收服务器反馈
str2 = br2.readLine();
System.out.println(str2);
//关闭资源
bw.close();
br.close();
s.close();
}
}