17_网络编程基础
2020-12-19 本文已影响0人
真是个点子王
InetAddress类
- 该类的对象代表一个ip地址对象
- 常用的类成员方法:
static InetAddress getLocalHost()
* 获得本地主机IP地址对象。
static InetAddress getByName(String host)
* 根据IP地址字符串或主机名获得对应的IP地址对象。
String getHostName()
* 获得主机名。
String getHostAddress()
* 获得IP地址字符串。
- 示例
public class MyTestAddress {
public static void main(String[] args) throws Exception {
InetAddress address = InetAddress.getLocalHost();
System.out.println(address.getHostAddress());
System.out.println(address.getHostName());
InetAddress address1 = InetAddress.getByName("www.baidu.com");
System.out.println(address1.getHostAddress());
System.out.println(address1.getHostName());
}
}
UDP通信
- 与UDP协议主要相关的有两个类:
-
DatagramPacket
:数据包对象,用来封装要发送或要接收的数据 -
DatagramSocket
: Socket 对象,用于收发数据包。
-
- DatagramPacket
发送端用构造器:
new DatagramPacket(byte[] buf, int length, InetAddress address, int port)
创建发送端数据包对象
* buf:要发送的内容,字节数组
* length:要发送内容的长度,单位是字节
* address:接收端的IP地址对象
* port:接收端的端口号
接收端用构造器:
new DatagramPacket(byte[] buf, int length)
* 创建接收端的数据包对象
* buf:用来存储接收到内容
* length:能够接收内容的长度
常用方法
int getLength()
* 获得实际接收到的字节个数
- DatagramSocket
- 构造器
new DatagramSocket()
* 创建发送端的Socket对象,系统会随机分配一个端口号。
new DatagramSocket(int port)
* 创建接收端的Socket对象并指定端口号
- 类成员方法
void send(DatagramPacket dp)
* 发送数据包
void receive(DatagramPacket dp)
* 接收数据包
- 示例:服务端监听端口,客户端发送消息,服务端接收后打印。
// 服务端
public class TestUDPServer {
public static void main(String[] args) throws Exception{
System.out.println("======== 服务端开始工作 ===========");
byte[] buffer = new byte[1024 * 64];
DatagramSocket socket = new DatagramSocket(8990);
DatagramPacket packet = new DatagramPacket(buffer,buffer.length);
socket.receive(packet);
String rs = new String(packet.getData());
int length = packet.getLength();
int port = packet.getPort();
String address = packet.getAddress().getHostAddress();
System.out.println("接收内容:" + rs);
System.out.println("内容长度:" + length);
System.out.println("地址:" + address+":"+port);
}
}
// 客户端
public class TestUDPClient {
public static void main(String[] args) throws Exception {
System.out.println("======== 客户端开始工作 ===========");
byte[] buffer = "You can really dance!".getBytes();
DatagramPacket packet = new DatagramPacket(buffer,buffer.length, InetAddress.getLocalHost(),8990);
DatagramSocket socket = new DatagramSocket();
socket.send(packet);
socket.close();
}
}
TCP通信
- 基于IO流进行数据传输
-
TCP协议相关的类
-
Socket
:一个该类的对象就代表一个客户端 -
ServerSocket
:一个该类的对象就代表一个服务端程序
-
Socket类
// 构造器
Sokcet(String host,int port)
/*
根据ip地址字符串和端口号创建客户端Socket对像
*/
// 类常用方法
OutputStream getOutputStream(); //获得字节输出流对象
InputStream getInputStream(); //获取字节输入流对象
ServerSocket类
// 构造器
public ServerSocket(int port);
// 方法
public Socket accept(); // 等待接收一个客户端的Socket管道连接请求,连接成功返回一个Socket对象
示例一:客户端发送一行数据,服务器接收一行数据
// 客户端
public class MyClientDemo {
public static void main(String[] args) throws Exception {
// 1.客户端请求于服务端的socket管道连接
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
// 2.从socket管道中获得一个字节输出流
OutputStream os = socket.getOutputStream();
// 3.把低级的字节输出流,包装成高级的打印流
PrintStream ps = new PrintStream(os);
// 4.开始发消息
ps.println("You can really dance!");
ps.flush();
System.out.println("客户端发送完毕");
}
}
// 服务端
public class MyServerDemo {
public static void main(String[] args) throws Exception {
System.out.println("---------- 服务端启动 ------------");
// 1.注册端口
ServerSocket serverSocket = new ServerSocket(8888);
// 2.开始接收客户端的Socket管道连接
Socket socket = serverSocket.accept();
// 3.从socket通信管道中得到一个字节输入流
InputStream is = socket.getInputStream();
// 4.把字节输入流转换成字符输入流
Reader isr = new InputStreamReader(is);
// 5.把字符输入流包装成缓冲字符输入流
BufferedReader br = new BufferedReader(isr);
// 6.按行读取消息
String msg;
if ((msg= br.readLine())!=null){
System.out.println(msg);
}
}
}
示例二:服务端可以为多个客户端提供服务
- 思想:在服务端开启多线程,每个线程接收一个客户端的socket对象,并为之服务。
// 服务端
public class MyServerTest {
public static void main(String[] args) throws Exception{
System.out.println("欢迎来到深夜激情聊天室");
// 1. 绑定端口
ServerSocket serverSocket = new ServerSocket(8888);
while (true){
// 2.开始等待客户端的Socket的管道连接
Socket socket = serverSocket.accept();
Thread thread = new ServerThread(socket);
thread.start();
}
}
}
class ServerThread extends Thread{
private Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run(){
try {
// 3. 从socket管道读入一个字节输入流
InputStream is = socket.getInputStream();
// 4. 将字节输入流装换字符输入流
Reader isr = new InputStreamReader(is);
// 5. 将字符输入流包装成一个缓冲字符输入流
BufferedReader br = new BufferedReader(isr);
// 6. 按行读取、打印
String line;
while( (line = br.readLine() ) != null ){
System.out.println(socket.getRemoteSocketAddress() + ":" + line);
}
} catch (IOException e) {
System.out.println(socket.getRemoteSocketAddress() + ": 下线了");
}
}
}
// 客户端
public class MyClientTest {
public static void main(String[] args) throws Exception{
// 1. 客户端请求于服务器的Socket管道连接
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),8888);
// 2. 从socket管道中获取一个字节输出流
OutputStream os = socket.getOutputStream();
// 3. 将字节输出流包装成一个打印输出流
PrintStream ps = new PrintStream(os);
// 4. 开始发消息
while(true){
Scanner sc = new Scanner(System.in);
System.out.println("请输入:");
ps.println(sc.nextLine());
ps.flush();
}
}
}
示例三:利用线程池优化
- 事例二中代码,虽然实现了基本的功能,但是存在问题,就是对于线程的并发数目并没有进行控制,因此,当并发多高的时候会导致系统的瘫痪。
- 可以利用线程池来解决这一点。
// Runnable类
// 并发任务的主逻辑
class ReaderClientRunnable implements Runnable {
private Socket socket ;
public ReaderClientRunnable(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
// 读取一行数据
InputStream is = socket.getInputStream() ;
// 转成一个缓冲字符流
Reader fr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(fr);
// 一行一行的读取数据
String line = null ;
while((line = br.readLine())!=null){ // 阻塞式的!!
System.out.println("服务端收到了数据:"+line);
}
} catch (Exception e) {
System.out.println("有人下线了");
}
}
}
// 线程池处理类
public class HandlerSocketThreadPool {
// 线程池
private ExecutorService executor;
// 线程池:3个线程 100个
public HandlerSocketThreadPool(int maxPoolSize, int queueSize){
executor = new ThreadPoolExecutor(
maxPoolSize,
maxPoolSize,
120L,
TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(queueSize) );
}
public void execute(Runnable task){
this.executor.execute(task);
}
}
// 服务端
public class Server {
public static void main(String[] args) {
try {
System.out.println("----------服务端启动成功------------");
ServerSocket ss = new ServerSocket(9999);
// 一个服务端只需要对应一个线程池
HandlerSocketThreadPool handlerSocketThreadPool =
new HandlerSocketThreadPool(3, 100);
// 客户端可能有很多个
while(true){
Socket socket = ss.accept() ;
System.out.println("有人上线了!!");
// 每次收到一个客户端的socket请求,都需要为这个客户端分配一个
// 独立的线程 专门负责对这个客户端的通信!!
handlerSocketThreadPool.execute(new ReaderClientRunnable(socket));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 客户端
public class Client {
public static void main(String[] args) {
try {
// 1.客户端要请求于服务端的socket管道连接。
// Socket(String host, int port)
Socket socket = new Socket("127.0.0.1" , 9999);
// 2.从socket通信管道中得到一个字节输出流
OutputStream os = socket.getOutputStream();
// 3.把低级的字节输出流包装成高级的打印流。
PrintStream ps = new PrintStream(os);
Scanner sc = new Scanner(System.in);
while(true){
System.out.print("请说:");
String msg = sc.nextLine();
ps.println(msg);
ps.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
示例四:实现文件上传功能
- 这个示例的难点在于对于网络传输过程中,各种读写流的处理。
- 对于客户端,采取示例二中利用多线程的机制,同时为多个客户端提供服务。
- 分析客户端的流程:
- 1、客户端为了与服务端通信,首先需要获得一个Socket对象;
- 2、其次,为了向Socket中写入文件,需要获得从Socket中获得一个字节输出流,并将其包装成缓冲字节输出流。
- 3、为了从本地读入图片,因此需要一个被包装成缓冲字节输入流的字节输入流来读取本地的文件,并将其通过2中的缓冲字节输出流发送给服务端。
- 4、需要注意,在客户端发送完字节流之后,服务端会一直处于等待读入的状态,因此客户端需要调用
socket.shutdownOutput()
方法,告知服务端写入已经完成。
// 客户端
public class MyClientDemo {
public static void main(String[] args) throws Exception {
String filepath = "src/23232.png";
// 1. 获取与服务端的Socket管道连接
Socket socket = new Socket(InetAddress.getLocalHost().getHostAddress(),8998);
// 2. 从管道中获得一个字节输出流
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
// 3. 提取本机的图片上传给服务端
// 4. 得到一个缓冲字节输入流与本地图片接通
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filepath));
// 5. 定义一个字节数组
byte[] buffer = new byte[1024];
int len;
while ( (len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
bos.flush(); // 刷新图片到服务器
bis.close();
socket.shutdownOutput(); //通知服务端文件写入已经完成
System.out.println("客户端图片已发送完毕");
// 6. 从服务端接受响应消息
BufferedReader bf = new BufferedReader(new InputStreamReader(socket.getInputStream()));
System.out.println(bf.readLine());
}
}
- 分析服务端流程
- 服务端会监听端口,每检测到一个新的客户端连接后,便启动新的线程处理业务。
- 在线程的业务中,为了从客户端读入文件,需要获得socket对象的字节输入流,并包装成缓冲字节输入流。
- 为了将接收到的数据存储到本地,则需要一个缓冲字节输出流,将其保存到文件中。
// 服务端
public class MyServerDemo {
public static final String DEST_FILE = "F:\\课程资源\\java进阶13天资料\\day11-Socket网络编程、NIO\\code\\Day11Demo\\TestPic\\";
public static void main(String[] args) throws Exception{
// 1. 绑定端口
ServerSocket serverSocket = new ServerSocket(8998);
while (true){
// 2.开始等待客户端的Socket的管道连接
Socket socket = serverSocket.accept();
Thread thread = new ServerThread(socket);
thread.start();
}
}
}
class ServerThread extends Thread{
private Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run(){
try {
System.out.println(socket.getRemoteSocketAddress() + ": 上线了");
// 1.获得缓冲输入字节流,接收当前客户端发来的图片
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
// 2.从bio管道中读取客户端发来的图片字节,写入到本地路径中
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(MyServerDemo.DEST_FILE+ UUID.randomUUID().toString()+".png"));
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
bos.flush();
bos.close();
// 4.得到一个字节输出流,直接包装成打印流
PrintStream ps = new PrintStream(socket.getOutputStream());
ps.println("I get it!");
ps.flush();
Thread.sleep(1);
} catch (Exception e) {
System.out.println(socket.getRemoteSocketAddress() + ": 下线了");
}
}
}