Java Socket(慕课网笔记)
网络基础知识
两台主机通过网络进行通信的必备条件:
IP地址、协议、端口号
TCP/IP是目前世界上应用最为广泛的协议
是以TCP和IP为基础的不同层次上多个协议的集合
TCP:传输控制协议 IP:互联网协议
端口用于区分不同应用程序。端口号范围为0~65535,其中0-1023为系统所保留,自定义可以选其后的
IP地址和端口号组成了Socket,Socket是网络上运行的程序之间双向通信链路的终结点,是TCP和UDP的基础
http:80 ftp:21 telnet: 23
针对网络通信的不同层次,java提供的网络功能有四大类:
(1)InetAddress:用于标识网络上的硬件资源(其实就是标识IP地址)
(2)URL: 统一资源定位符 通过URL可以直接读取或写入网络上的数据
(3)Sockets:使用TCP协议实现网络通信的Socket相关的类
(4)Datagram:使用UDP协议,将数据保存在数据报中,通过发送数据报在网络进行通信
InetAddress类
无构造函数,但有一些静态方法,返回InetAddress的实例
public class Test01 {
public static void main(String[] args) throws UnknownHostException{
//获取本机的InetAddress实例
InetAddress address=InetAddress.getLocalHost();
System.out.println("计算机名"+address.getHostName());
System.out.println("IP地址"+address.getHostAddress());
byte[] bytes = address.getAddress(); // 获取字节数组形式的IP地址
System.out.println("字节数组形式的IP"+Arrays.toString(bytes));
System.out.println(address);
// 根据主机名获取InetAddress实例
InetAddress address2= InetAddress.getByName("DESKTOP-AK4BLPN");
//一样
//InetAddress address2= InetAddress.getByName("192.168.89.1");
System.out.println("计算机名"+address2.getHostName());
System.out.println("IP地址"+address2.getHostAddress());
}
}
output:
计算机名DESKTOP-AK4BLPN
IP地址192.168.89.1
字节数组形式的IP[-64, -88, 89, 1]
DESKTOP-AK4BLPN/192.168.89.1
计算机名DESKTOP-AK4BLPN
IP地址192.168.89.1
URL
表示Internet上某一资源的地址
两部分组成:协议名称和资源名称,以冒号隔开
在java.net包中,提供了URL类来表示URL
public class Test02 {
public static void main(String[] args){
try {
//创建一个URL实例
URL imooc = new URL("http://www.imooc.com");
URL url = new URL(imooc,"/index.html?username=tom#test");
//?后表示参数的信息,#后表锚点
System.out.println("协议"+url.getProtocol());
System.out.println("主机"+url.getHost());
System.out.println("端口"+url.getPort());
System.out.println("文件路径"+url.getPath());
System.out.println("文件名"+url.getFile());
System.out.println("相对路径"+url.getRef());
System.out.println("查询字符串"+url.getQuery());
} catch (MalformedURLException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
协议http
主机www.imooc.com
端口-1
文件路径/index.html
文件名/index.html?username=tom
相对路径test
查询字符串username=tom
注:如果未指定端口号,则使用默认端口号,这里http默认是80端口,此时getPort()方法返回值为-1
通过URL读取网页内容
通过URL对象的openStream()方法可以得到指定资源的输入流通过输入流可以读取、访问网络上的数据
public class Test03 {
public static void main(String[] args){
//创建一个URL实例
try {
URL url =new URL("http://www.baidu.com");
//通过URL的openStream方法获取URL对象所表示的资源的字节输入流
InputStream is = url.openStream();
//将字节输入流转换为字符输入流
InputStreamReader isr = new InputStreamReader(is,"utf-8"); // 指定编码
//为字符输入流添加缓冲,提高读取效率
BufferedReader br = new BufferedReader(isr);
String data=br.readLine();
while(data!=null){
System.out.println(data);
data = br.readLine();
}
br.close();
isr.close();
is.close();
} catch (MalformedURLException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
Socket通信(TCP编程)
TCP协议是面向连接的、可靠的、有序的,以字节流的方式发送数据
基于TCP协议实现网络通信的类:
客户端的Socket类
服务器端的ServerSocket类
Socket通信实现步骤:
1.创建ServerSocket和Socket
2.打开连接到Socket的输入/输出流
3.按照协议对Socket进行读/写操作
4.关闭输入输出流、关闭Socket
服务器端:
1、创建ServerSocket对象,绑定监听端口
2、通过accept()方法监听客户端请求
3、连接建立后,通过输入流读取客户端发送的请求信息
4、通过输出流向客户端发送响应信息
5、关闭相关资源
客户端:
1、创建Socket对象,指明需要连接的服务器的地址和端口号
2、连接建立后,通过输出流向服务器端发送请求信息
3、通过输入流获取服务器响应的信息
4、关闭相关资源
实例:
先开启服务端,后开启客户端
/*
* 基于TCP协议的socket通信,实现用户登录
*/
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("***服务器即将启动,等待客户端连接***");
Socket socket = serverSocket.accept();
// 获取输入流,并读取客户端信息
InputStream is = socket.getInputStream(); // 字节输入流
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String info = null;
while((info = br.readLine()) != null) {
System.out.println("我是服务器,客户端说:" + info);
}
socket.shutdownInput(); // 关闭输入流
// 获取输出流,响应客户端的请求
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("欢迎你");
pw.flush();
// 关闭资源
pw.close();
os.close();
br.close();
isr.close();
is.close();
socket.close();
serverSocket.close();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
// 客户端
public class Client {
public static void main(String[] args) {
try {
// 创建客户端socket,指定服务器地址和端口
Socket socket = new Socket("localhost",8888);
// 获取输出流,向服务器端发送信息
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os); // 将输出流包装为打印流
pw.write("用户名: admihn;密码:123");
pw.flush();//刷新缓存,向服务器端输出信息
socket.shutdownOutput();
InputStream is = socket.getInputStream(); // 字节输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String info = null;
while((info = br.readLine()) != null) {
System.out.println("我是客户端,服务器端说:" + info);
}
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
多线程实现客户端通信
应用多线程实现服务器与多客户端之间的通信
注: 未设置优先级可能会导致运行时速度非常慢,可降低优先级
基本步骤:
1、服务器端创建ServerSocket,循环调用accept()等待客户端连接
2、客户端创建一个socket并请求和服务器端连接
3、服务器端接收客户端请求,创建socket与该客户建立专线连接
4、建立连接的两个socket在一个单独的线程上对话
5、服务器端继续等待新的连接
客户端不变
public class Server {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = null;
System.out.println("***服务器即将启动,等待客户端连接***");
// 记录客户端数量
int count = 0;
// 循环监听等待客户端的连接
while(true) {
socket = serverSocket.accept();
// 创建一个新的线程
ServerThread serverThread = new ServerThread(socket);
thread.setPriority(4);// 设置线程的优先级,范围为[1,10],默认为5
serverThread.start();
count++; // 统计客户端数量
System.out.println("客户端数量为" + count);
InetAddress address = socket.getInetAddress();
System.out.println("当前客户端IP:" + address.getHostAddress());
}
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
/*
* 服务器线程处理类
*/
public class ServerThread extends Thread{
// 和本线程相关的socket
Socket socket = null;
public ServerThread(Socket socket) {
this.socket = socket;
}
// 线程执行的操作,响应客户端的请求
public void run() {
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
OutputStream os = null;
PrintWriter pw = null;
try {
// 获取输入流,并读取客户端信息
is = socket.getInputStream();// 字节输入流
isr = new InputStreamReader(is);
br = new BufferedReader(isr);
String info = null;
while((info = br.readLine()) != null) {
System.out.println("我是服务器,客户端说:" + info);
}
socket.shutdownInput(); // 关闭输入流
// 获取输出流,响应客户端的请求
os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write("欢迎你");
pw.flush();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} finally{
// 关闭资源
try {
if(pw != null)
pw.close();
if(os != null)
os.close();
if(br != null)
br.close();
if(isr != null)
isr.close();
if(is != null)
is.close();
if(socket != null)
socket.close();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
/*
客户端
*/
public class Client {
public static void main(String[] args) {
try {
// 创建客户端socket,指定服务器地址和端口
Socket socket = new Socket("localhost",8888);
// 获取输出流,向服务器端发送信息
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os); // 将输出流包装为打印流
pw.write("用户名: admihn;密码:123");
//pw.write("用户名: scyda;密码:121");
pw.flush();//刷新缓存,向服务器端输出信息
// pw.close(); // 不能关闭输出流,会导致socket也被关闭
socket.shutdownOutput();
InputStream is = socket.getInputStream(); // 字节输入流
BufferedReader br = new BufferedReader(new InputStreamReader(is));
String info = null;
while((info = br.readLine()) != null) {
System.out.println("我是客户端,服务器端说:" + info);
}
br.close();
is.close();
pw.close();
os.close();
socket.close();
} catch (UnknownHostException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
对于同一个Socket,如果关闭了输出流,则与该输出流关联的socket也会被关闭,所以一般不用关闭流,直接关闭socket即可
使用TCP传输对象
// 创建客户端socket,指定服务器地址和端口
Socket socket = new Socket("localhost",8888);
// 获取输出流,向服务器端发送信息
OutputStream os = socket.getOutputStream();
// 使用objectOutputStream对象序列化流,传递对象
ObjectOutputStream oos = new ObjectOutputStream(os);
User user = new User("admin", "123"); // 封装为对象
oos.writeObject(user); // 序列化
socket.shutdownOutput();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String info = null;
while((info = br.readLine()) != null) {
System.out.println("我是客户端,服务器端说:" + info);
}
UDP编程
UDP协议(用户数据报协议)是无连接、不可靠、无序的
进行数据传输时,首先需要将要传输的数据定义为数据报(Datagram),在数据报中指明数据所要达到的Socket(主机地址和端口号),然后将数据报发送出去
DatagramPacket:表示数据报包
DatagramSocket:进行端到端通信的类
服务器端:
1. 创建DatagramSocket,指定端口号
2. 创建DatagramPacket
3. 接收客户端发送的数据信息
4. 读取数据
客户端:
1. 定义发送信息
2. 创建DatagramPacket,包含将要发送的信息
3. 创建DatagramSocket
4. 发送数据
public class UDPServer {
public static void main(String[] args) throws IOException {
DatagramSocket socket = new DatagramSocket(8800);
// 2. 创建数据报,用于接收客户端发送的数据
byte[] data = new byte[1024]; // 创建字节数组,指定接收的数据包的大小
DatagramPacket packet = new DatagramPacket(data, data.length);
System.out.println("服务器已启动,等待客户端发送数据");
socket.receive(packet); // 此方法在接收到数据报之前会一直阻塞
String info = new String(data, 0, packet.getLength());
System.out.println("服务器,客户端说:" + info);
/*
* 向客户端响应数据
*/
// 定义客户端的地址、端口号、数据
InetAddress address = packet.getAddress(); // 通过数据报获取
int port = packet.getPort();
byte[] data2 = "welcome".getBytes();
DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port);
socket.send(packet2);
// 关闭资源
socket.close();
}
}
public class UDPClient {
public static void main(String[] args) throws IOException {
// 定义服务器的地址、端口号、数据
InetAddress address = InetAddress.getByName("localhost");
int port = 8800;
byte[] data = "用户名:admin,密码:123".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
DatagramSocket socket = new DatagramSocket();
socket.send(packet);
/*
* 接收服务器端的响应
*/
// 1. 创建数据报,用于接收服务端发送的数据
byte[] data2 = new byte[1024];
DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
// 接收服务端响应的数据
socket.receive(packet2);
// 读取数据
String info = new String(data2, 0, packet2.getLength());
System.out.println("客户端,服务器说:" + info);
// 关闭资源
socket.close();
}
}