Java网络编程之详细阐述
一、网络编程的概述【理解】
网络编程的概念:
-------网络互连的不同计算机上运行的程序间可以进行数据交换,网络通信其实就是Socket 间的通信;数据在两个Socket间可以通过IO传输。
补充:
1.网络中不同的计算机中程序可以通过网络进行数据交换,可以使用网络编程
2.就算同一台计算机中不同的程序也可以通过网络进行数据交换,也可以使用网络编程
二、网络通信中的三要素【理解】
网络通信中的三要素:
1、IP地址
网络中设备的唯一标识,如IPv4的32位地址:192.168.1.100。
IPv4,它是由4个字节大小的32位二进制数来表示,如: 00001010000000000000000000000001。
由于二进制形式表示的IP地址非常不便记忆和 处理,因此通常会将IP地址写成十进制的形式,每个字节用一个十进制数字(0-255)表 示,数字间用符号“.”分开,如 “192.168.1.100”。
2、端口号
用于标识进程/程序的逻辑地址,不同进程/程序的标识,一个端口号一个程序,端口号和程序一一对应。
它的取值范围是0-65535,其中,0~1023之间的端口号用于一些知名的网络服务和应 用,用户的普通应用程序需要使用1024以上的端口号,从而避免端口号被另外一个应 用或服务所占用。
3、传输协议
双方通讯的规则,常见协议:UDP,TCP
—UDP:无连接,不可靠,速度快
应用场景:电视,直播,视频会议
—TCP:有连接,可靠,速度慢
应用场景:上传,下载,发邮件
重要:
TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠。
第一次握手:客户端向服务器端发出连接请求,等待服务器确认。
第二次握手:服务器端向客户端回送一个响应,通知客户端收到了连接请求。
第三次握手:客户端再次向服务器端发送确认信息,确认连接。
UDP协议中,当一台计算机向另一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据后,也不会向发送端反馈是否收到数据。
三、InetAddress的概述和测试【应用】
阐述InetAddress的概念以及如何获取本机IP和主机名的案例
InetAddress的概念:
为了方便我们对IP地址的获取和操作,java提供了一个类InetAddress供我们使用。
即IP = InetAddress
常用API:
- 获取网络中主机名和IP
InetAddress inetAddress = InetAddress.getByName(“主机名”);
InetAddress inetAddress = InetAddress.getByName(“主机IP地址”);
代码:
/*
* InetAddress:此类表示互联网协议 (IP) 地址。
*/
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
//static InetAddress getByName(String host)
//InetAddress address = InetAddress.getByName("itheima");//主机名
InetAddress address = InetAddress.getByName("192.168.1.107");//ip地址是唯一的
//System.out.println(address);//itheima/192.168.1.107 ipconfig
String hostAddress = address.getHostAddress();//192.168.1.107 返回IP地址
String hostName = address.getHostName();//itheima 返回主机名
System.out.println(hostAddress);
System.out.println(hostName);
}
}
四、UDP协议发送数据【应用】
常用API:
1.创建发送端Socket
- DatagramSocket()
2.创建发送端数据包
- DatagramPacket(byte[] buf, int length, InetAddress address, int port)
3.发送端发送数据包
- void send(DatagramPacket p)
4.发送端释放资源
- void close()
代码:
/*
* 使用UDP协议发送数据
创建发送端Socket对象
创建数据并打包
发送数据
释放资源
*
* DatagramSocket:此类表示用来发送和接收数据,基于UDP协议的
*
* DatagramSocket() :创建Socket对象并随机分配端口号
* DatagramSocket(int port) :创建Socket对象并指定端口号
*/
public class SendDemo {
public static void main(String[] args) throws IOException {
//创建发送端Socket对象
DatagramSocket ds = new DatagramSocket();
//创建数据并打包
/*
* DatagramPacket :此类表示数据报包
* 数据 byte[]
* 设备的地址 ip
* 进程的地址 端口号
DatagramPacket(byte[] buf, int length, InetAddress address, int port)
*/
String s = "hello udp,im comming!";
byte[] bys = s.getBytes();
int length = bys.length;
InetAddress address = InetAddress.getByName("itheima");//发送给当前设备
int port = 8888;
//打包
DatagramPacket dp = new DatagramPacket(bys,length,address,port);
//发送数据
ds.send(dp);
//释放资源
ds.close();
}
}
五、UDP协议接收数据【应用】
常用API:
1.创建接收端Socket,这个port端口号必须和发送端指定的端口号相同
- DatagramSocket(int port)
2.创建接收端数据包
- DatagramPacket(byte[] buf, int length)
3.接收端接收数据
- void receive(DatagramPacket p)
4.从数据包中解析数据
- byte[] getData()
int getLength()
5.接收端释放资源
- void close()
代码:
/*
* 使用UDP协议接收数据
创建接收端Socket对象
接收数据
解析数据
输出数据
释放资源
*/
public class ReceiveDemo {
public static void main(String[] args) throws IOException {
//创建接收端Socket对象
DatagramSocket ds = new DatagramSocket(8888);
//接收数据
//DatagramPacket(byte[] buf, int length)
byte[] bys = new byte[1024];
DatagramPacket dp = new DatagramPacket(bys,bys.length);
System.out.println(1);
ds.receive(dp);//阻塞
System.out.println(2);
//解析数据
//InetAddress getAddress() : 获取发送端的IP对象
InetAddress address = dp.getAddress();
//byte[] getData() :获取接收到的数据,也可以直接使用创建包对象时的数组
byte[] data = dp.getData();
//int getLength() :获取具体收到数据的长度
int length = dp.getLength();
//输出数据
System.out.println("sender ---> " + address.getHostAddress());
//System.out.println(new String(data,0,length));
System.out.println(new String(bys,0,length));
//释放资源
ds.close();
}
}
UDP协议收发数据注意事项:
注意事项:
- 如果发送端和接收端的端口号不相同,程序不会出错,但接收端收不到数据。
- 如果接收端已经启动,这时又再一次启动接收端,程序会出错,Exception in thread “main” java.net.BindException: Address already in use: Cannot bind
端口号已经绑定了,这个错误同样适用于TCP协议。 - 先启动接收端,后启动发送端。
六、TCP协议发送数据【应用】
常用API:
1.创建发送端/客户端Socket,自动建立连接,如果连接没有建立,会出错
- Socket(InetAddress address, int port)
Socket(String host, int port)
2.获取字节输出流
- OutputStream getOutputStream()
3.发送数据
- void write(byte[] b)
4.发送端/客户端释放资源
- void close()
代码:
/*
* 使用TCP协议发送数据
创建发送端Socket对象(创建连接)
获取输出流对象
发送数据
释放资源
Socket(InetAddress address, int port)
在TCP协议下,必须先运行接收端,否则出错,信息如下:
Exception in thread "main" java.net.ConnectException: Connection refused: connect
*/
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建发送端Socket对象(创建连接)
Socket s = new Socket(InetAddress.getByName("127.0.0.1"),10086);
//获取输出流对象
OutputStream os = s.getOutputStream();
//发送数据
String str = "hello tcp,张鑫!!!";
os.write(str.getBytes());
//释放资源
//os.close();
s.close();
}
}
七、TCP协议接收数据【应用】
常用API:
1.创建接收端/服务端Socket,这里的端口号要与发送端/客户端指定的端口号相同
- ServerSocket(int port)
2.接收端/服务端监听发送端/客户端
- Socket accept()
3.获取字节输入流
- InputStream getInputStream()
4.接收数据
- int read(byte[] b)
5.接收端/服务端释放资源
- void close()
代码:
/*
* 使用TCP协议接收数据
创建接收端Socket对象
监听(阻塞)
获取输入流对象
获取数据
输出数据
释放资源
ServerSocket:接收端,服务端Socket
ServerSocket(int port)
Socket accept()
*/
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建接收端Socket对象
ServerSocket ss = new ServerSocket(10086);
//监听(阻塞)
Socket s = ss.accept();
//获取输入流对象
InputStream is = s.getInputStream();
//获取数据
byte[] bys = new byte[1024];
int len;//用于存储读到的字节个数
len = is.read(bys);
//输出数据
InetAddress address = s.getInetAddress();
System.out.println("client ---> " + address.getHostName());
System.out.println(new String(bys,0,len));
//释放资源
s.close();
//ss.close();//企业中的服务端全年不关
}
}
八、用TCP协议发送数据并将接收到的数据转换成大写返回【应用】
图解流程:
客户端:
/*
需求:使用TCP协议发送数据,并将接收到的数据转换成大写返回
客户端发出数据
服务端接收数据
服务端转换数据
服务端发出数据
客户端接收数据
*/
public class ClientDemo {
public static void main(String[] args) throws IOException {
//创建客户端Socket对象
Socket s = new Socket(InetAddress.getByName("itheima"),10010);
//获取输出流对象
OutputStream os = s.getOutputStream();
//发出数据
os.write("tcp,im comming again!!!".getBytes());
//获取输入流对象
InputStream is = s.getInputStream();
byte[] bys = new byte[1024];
int len;//用于存储读取到的字节个数
//接收数据
len = is.read(bys);
//输出数据
System.out.println(new String(bys,0,len));
//释放资源
s.close();
}
}
服务端:
public class ServerDemo {
public static void main(String[] args) throws IOException {
//创建服务端Socket对象
ServerSocket ss = new ServerSocket(10010);
//监听
Socket s = ss.accept();
//获取输入流对象
InputStream is = s.getInputStream();
//获取数据
byte[] bys = new byte[1024];
int len;//用于存储读取到的字节个数
len = is.read(bys);
String str = new String(bys,0,len);
//输出数据
System.out.println(str);
//转换数据
String upperStr = str.toUpperCase();
//获取输出流对象
OutputStream os = s.getOutputStream();
//返回数据(发出数据)
os.write(upperStr.getBytes());
//释放资源
s.close();
//ss.close();//服务端一般不关闭
}
}
九、模拟用户登录案例【应用】
常用的API:
- 创建字符输出流,直接将字节输出流进行封装,true表示自动刷新
PrintWriter(OutputStream out, boolean autoFlush)
User类:
public class User {
private String username;//用户名
private String password;//密码
public User() {
super();
// TODO Auto-generated constructor stub
}
public User(String username, String password) {
super();
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (password == null) {
if (other.password != null)
return false;
} else if (!password.equals(other.password))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
}
UserDB:
public class UserDB {
private static List<User> users = new ArrayList<User>();
static {
users.add(new User("zhangsan","123456"));
users.add(new User("lisi","654321"));
users.add(new User("itheima","itheima"));
users.add(new User("admin","password"));
}
public static List<User> getUsers() {
return users;
}
}
ServerTest:
public class ServerTest {
public static void main(String[] args) throws IOException {
//创建服务器端Socket对象
ServerSocket ss = new ServerSocket(8888);
//监听
Socket s = ss.accept();
//获取输入流对象
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
//获取用户名和密码
String username = br.readLine();
String password = br.readLine();
//判断用户名和密码是否正确
boolean flag = false;
/*if("itheima".equals(username) && "123456".equals(password)) {
flag = true;
}*/
List<User> users = UserDB.getUsers();
User user = new User(username,password);
if(users.contains(user)) {
//匹配成功
flag = true;
}
//获取输出流对象
PrintWriter out = new PrintWriter(s.getOutputStream(),true);
//返回判断信息
if(flag) {
out.println("登陆成功");
}
else {
out.println("登陆失败");
}
//释放资源
s.close();
//ss.close();//服务器一般不关闭
}
}
如果要让服务器端TcpServer永远监听客户端的连接,需要使用死循环
代码:
public class TcpServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(8888);
while (true) {
Socket s = ss.accept();
InputStream is = s.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String username = br.readLine();
String password = br.readLine();
boolean flag = false;
User user = new User(username,password);
List<User> list = UserDB.getList();
if(list.contains(user)){
flag = true;
}
OutputStream os = s.getOutputStream();
PrintWriter pw = new PrintWriter(os,true);
if(flag){
pw.println("登录成功");
}else{
pw.println("登录失败");
}
s.close();
}
}
}