java基础-网络编程

以下为本人的学习笔记

 

1.网络编程基本概念

1.1 什么是计算机网络

把发布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递消息,共享硬件、软件、数据消息等资源

 

1.2 计算机网络的主要功能
  • 资源共享

  • 信息传输与集中处理

  • 均衡负荷与分布处理

  • 综合信息服务(www/综合业务数字网络ISDN等)

 

1.3网络通信协议

要使计算机连成的网络能够互通信息,需要对数据传输速率、传输代码、代码结构、传输控制步骤、出错控制等制定一组标准,这一组共同遵守的通信标准就是网络通信协议,不同的计算机之间必须使用相同的通讯协议才能进行通信

网络通信接口

为了使两个节点之间能够进行对话,必须在他们之间建立通信工具(即接口),使彼此之间能进行信息交换。接口包括两部分:

1)硬件装置:实现节点之间的信息传送(网线....)

2)软件装置:规定双方进行的约定协议

 

1.4 TCP/IP

TCP/IP;参数控制协议/因特网协议,又叫网络通讯协议,这个协议是Internet最基本的协议、Internet国际互联网络的基础,简单来说,就是由网络层的IP协议和传输层的TCP协议组成的。

IP地址:网络中每台计算机的一个标识号,本地IP:127.0.0.1 或者 localhost

端口号(PORT):端口号的范围:0-65535之间,0-1023之间的端口数是用于一些知名的网络服务和应用,通过端口号找到具体要通信的软件

 

1.5程序开发结构

网络编程主要是指完成C/S程序的开发,程序的开发结构有两种:

  • C/S(客户端/服务器)

开发两套程序,两套程序需要同时维护,例如:QQ。CS程序一般比较稳定

  • B/S(浏览器/服务器)

开发一套程序,客户端使用浏览器进行访问,例如:各个论坛。BS程序一般稳定性较差,而且安全性较差。

但是,C/S的程序开发在实际的java应用中很少,而且整个java基本上都是以B/S为主

C/S程序主要可以完成以下两种程序的开发:

  • TCP:(Transmission Control Protocol) 传输控制协议,采用三方握手的方式,保证准确的连接操作

  • UDP:(User Datagram Protocol) 数据报协议,发送数据报,例如:手机短信或者是QQ消息

(三方握手简单来说就是A发送请求给B,B发送A我确认收到了你的请求,A再发送给B表示我确认你确认收到我的请求)

TCP、UDP的数据帧格式简单图例:

其中协议类型用于区分TCP、UDP

 

2.网络编程TCP协议

2.1TCP程序概述

TCP是一个可靠的协议,面向连接的协议

实现TCP程序,需要编写服务器端和客户端,Java API为我们提供了java.net包,为实现网络应用程序提供类

ServerSocket:此类实现服务器套接字

Socket:此类实现客户端套接字(也可以就叫“套接字”)

硬件都有驱动,网络驱动层指的是网卡的驱动

通过Socket来实现网络编程,Socket是网络驱动层提供给应用程序编程的接口和一种访问机制,

解释:数据写到Socket中,Socket就会把数据对接到驱动层,驱动层把数据对接到网卡,再通过网线传出去

也可以将Socket想象成一个快递员

 

2.2 数据发送过程

 

2.3 数据接收过程

 

2.4 实现服务器端与客户端程序

服务器端:

public class ServerSocket extends Object implements Closeable

这个类实现了服务器套接字。 服务器套接字等待通过网络进入的请求。 它根据该请求执行一些操作,然后可能将结果返回给请求者。

method 说明
ServerSocket(int port) 创建绑定到指定端口的服务器套接字。
setSoTimeout(int timeout) 启用/禁用 SO_TIMEOUT带有指定超时,以毫秒为单位。
getInetAddress() 返回此服务器套接字的本地地址。
accept() 侦听要连接到此套接字(socket)并接收它。

客户端:

public class Socket extends Object implements Closeable

该类实现客户端套接字(也称为“套接字”)。 套接字是两台机器之间通讯的端点。

method 说明
Socket(InetAddress address, int port) 创建流套接字并将其连接到指定IP地址的指定端口号
getInputStream() 返回此套接字的输入流。
getOutputStream() 返回此套接字的输出流。
setSoTimeout(int timeout) 启用/禁用指定超时的 SO_TIMEOUT(以毫秒为单位)。

 

3.TCP实现ECHO程序

Echo,意为应答,程序的功能是客户端向服务器发送一个字符串,服务器不做任何处理,直接把字符串返回给客户端,Echo程序是最为基本的客户/服务器程序。(相当于java的HelloWorld)

public class EchoServerDeom{
    public static void main(String[] args){
    
        //1.创建一个服务器端的Socket(1024-65535的端口号)
            try{
                ServerSocket server = new ServerSocket(port:6666)
                System.out.println("服务器已启动,正在等待客户端的连接...");
                //2.等待客户端的连接,造成阻塞,如果有客户端连接成功,立即返回一个Socket对象
                Socket socket = server.accpet();   
                System.out.println("客户端连接成功:"+server.getInetAddress().getHostAddress());
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream());
                 //通过输入流读取网络数据,如果没有数据,那么会阻塞
                 String info = br.readLine();
                 System.out.println(info);   
                 //获取输出流,向客户端返回消息
                 PrintStream ps = new PrintStream (new BufferedOutputStream(socket.getOutputStream()));
                  ps.println("echo"+info);
                  ps.flush();
                  //关闭
                  ps.close();
                  br.close();                                     
                                                       
             }catch (IOException e){
                 e.printStackTrace();
            }
    }
}
​
public class EchoClientDeom{
    public static void main(String[] args){
        //1.创建一个Socket对象,指定要连接的服务器
        try{
            Socket socket = new Socket("localhost",6666);//主机名+端口号
            //获取socket的输入输出流
            PrintStream ps = new PrintStream(new BufferedOutputStream(socket.getOutPutStream()));
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            
            ps.println("hello,my name is bin");
            ps.flush();
            //读取服务器端返回的数据
            String info = br.readLine();
            System.out.println(info);
            ps.close();
            br.close();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

两个对象创建成功后面就是流的操作

 

4.服务器与多客户端通信

要想服务器同时支持多个客户端的连接,就必须加入多线程的处理机制,将每一个连接的客户端都创建一个新的线程对象(用循环)

服务器端通过加入线程池来处理多个客户端请求,简单的设置线程数可以与CPU核数匹配,过多的线程数空闲会消耗服务器为资源

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//服务器,用循环处理多个客户端
public class ServerSocketDemo2 {
    public static void main(String[] args) {
        try {
            ServerSocket server = new ServerSocket(6666);
            System.out.println("服务器已启动,等待客户端连接...");
            ExecutorService es = Executors.newFixedThreadPool(3);
            
            while (true){
                Socket socket = server.accept();
                System.out.println(socket.getInetAddress().getHostAddress());
                es.execute(new UserThread(socket));//一个线程处理一个客户端,将一个客户端socket作为线程的一个参数
            }
​
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//用来处理客户端请求的线程
class UserThread implements Runnable{
    private Socket socket;
    UserThread(Socket socket){
        this.socket = socket;
    }
    public  void run(){
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
​
            String info = br.readLine();
            System.out.println(info);
​
            pw.println("echo:"+info);
            pw.flush();
            br.close();
            pw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
//客户端
public class MutilClientDemo {
    public static void main(String[] args) {
        try {
            Scanner input = new Scanner(System.in);
            //创建一个socket对象,指定要连接的服务器
            Socket socket = new Socket("localhost", 6666);
​
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintStream ps = new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
​
            System.out.println("请输入一段话:");
            String info = input.nextLine();
            ps.println(info);
            ps.flush();
​
            //读取服务器返回的数据
            info = br.readLine();
            System.out.println(info);
​
            br.close();
            ps.close();
​
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

5.多客户端之间的通信

服务器可以与多个客户端实现通信了,那我们真正的目的就是要实现多个客户端之间的通信,使用TCP协议实现的方案是:客户端的数据包通过服务器中转,发送到另一个客户端,如下图所示:

多客户端间通信案例:

服务器通过消息类型来判断发送消息的客户端是什么意图,消息类型是我们自定义的标记

图解分析:

服务器端:

/*服务器作为一个中转,转发消息给多个客户端之间*/
public class Server {
    public static void main(String[] args) {
        //2.创建集合,保存客户端处理的线程,用来找其他指定线程
        Vector<UserThread> vector = new Vector<UserThread>();
        //3.创建线程池
        ExecutorService es = Executors.newFixedThreadPool(5);
        //1.创建服务器端的Socket
        try {
            ServerSocket server = new ServerSocket(7777);
            System.out.println("服务器已启动,正在等待连接...");
            while (true){
                //4.接收一个客户端的socket
                Socket socket = server.accept();
                UserThread user = new UserThread(socket,vector);
                //5.线程执行一个客户端
                es.execute(user);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//客户端处理的线程
class UserThread implements Runnable{
    private String name ;//客户端的用户名称(唯一)
    private Socket socket;
    private Vector<UserThread> vector;//客户端处理线程的集合
    private ObjectInputStream ois;
    private ObjectOutputStream oos;
    private boolean flag = true;
​
    public UserThread(Socket socket, Vector<UserThread> vector) {
        this.socket = socket;
        this.vector = vector;
        vector.add(this);
    }
    @Override
    public void run() {
        try {
            //6.输出客户端socket主机地址
            System.out.println("客户端"+socket.getInetAddress().getHostAddress());
            //7.构建序列化输入输出对象流,
            oos = new ObjectOutputStream(socket.getOutputStream());
            ois = new ObjectInputStream(socket.getInputStream());
​
            while (flag){
                //8.读取消息对象
                Message msg = (Message)ois.readObject();
                //9.判断消息类型
                int type = msg.getType();
                switch (type){
                    //发送消息的类型
                    case MessageType.TYPE_SEND:
                        String to = msg.getTo();
                        UserThread ut ;
                        int size = vector.size();
                        //遍历线程集合里的线程名字,符合就把消息writ给该线程
                        for (int i = 0; i < size; i++) {
                            ut = vector.get(i);//取一个变量装这个线程
                            if (to.equals(ut.name) && ut != this){
                                ut.oos.writeObject(msg);//把该消息发给该线程
                                break;
                            }
                        }
​
                        break;
                    //登录消息类型
                    case MessageType.TYPE_LOGIN:
                        //获取发送者名字
                        name = msg.getFrom();
                        msg.setInfo("欢迎你:");
                        //输出客户端欢迎数据
                        oos.writeObject(msg);
                        break;
                }
            }
            //10.关闭流
            ois.close();
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

客户端:

public class Client {
    public static void main(String[] args) {
​
        Scanner input = new Scanner(System.in);
        ExecutorService es = Executors.newSingleThreadExecutor();
        try {
            //1.创建一个客户端的socket
            Socket socket = new Socket("localhost",7777);
            System.out.println("服务器连接成功...");
            //2.构建序列化输入输出对象流
            ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            //3.向服务器发送登录消息
            System.out.println("请输入名称:");
            String name = input.nextLine();
            Message msg = new Message(name,null,MessageType.TYPE_LOGIN,null);
            oos.writeObject(msg);
            msg = (Message)ois.readObject();
            System.out.println(msg.getInfo()+msg.getFrom());
            
            //启动(不断)读取消息的线程
            es.execute(new ReadInfoThread(ois));
​
            //使用主线程来实现发送消息
            boolean flag = true;
            while(flag){
                msg = new Message();
                System.out.println("To:");
                msg.setTo(input.nextLine());
                msg.setFrom(name);
                msg.setType(MessageType.TYPE_SEND);
                System.out.println("Info:");
                msg.setInfo(input.nextLine());
                oos.writeObject(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
//读取消息
class ReadInfoThread implements Runnable{
​
    private ObjectInputStream in;
    private  boolean flag = true;
​
    public ReadInfoThread(ObjectInputStream in) {
        this.in = in;
    }
    public void setFlag(boolean flag){
        this.flag= flag;
    }
    @Override
    public void run() {
        try {
        //不断地读取消息
        while (flag){
​
                Message  message = (Message)in.readObject();
                System.out.println("["+message.getFrom()+"]对我说:"+message.getInfo());
        }
        if (in!=null){
            in.close();
        }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

 

import java.io.Serializable;
​
/*消息包*/
public class Message implements Serializable {//序列化对象实现Serializable接口
​
    private String from;//发送者
    private String to;//接收者
    private int type;//消息类型
    private String info;//消息
​
    public Message() {
    }
    public Message(String from, String to, int type, String info) {
        this.from = from;
        this.to = to;
        this.type = type;
        this.info = info;
    }
    public String getFrom() {
        return from;
    }
    public void setFrom(String from) {
        this.from = from;
    }
    public String getTo() {
        return to;
    }
    public void setTo(String to) {
        this.to = to;
    }
    public int getType() {
        return type;
    }
    public void setType(int type) {
        this.type = type;
    }
    public String getInfo() {
        return info;
    }
    public void setInfo(String info) {
        this.info = info;
    }
    @Override
    public String toString() {
        return "Message{" +
                "from='" + from + '\'' +
                ", to='" + to + '\'' +
                ", type=" + type +
                ", info='" + info + '\'' +
                '}';
    }
}

 

public final class MessageType {
​
    public static final int TYPE_LOGIN = 0x1;//登录消息类型
    public static final int TYPE_SEND = 0x2;//发送消息的类型
}

 

6.网络编程UDP协议

1.UDP协议概述

UCP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的,每个被传输的数据报必须限定在64KB之内。

主要使用以下两个类:

DatagramPacket:此类表示数据报包

DatagramSocket:此类表示用来发送和接收数据报包的套接字

UDP是无连接的,减少了开销和发送数据之前的延迟,不保证可靠,UDP的报头长度要小于TCP的包头长度,例如QQ文件传输,pplive等都是使用UDP协议

服务器端:

public class UDPServerDeom{
    public static void main(String[] args){
    
        String info = "good good 天天";
        byte[] bytes = info.getBytes();
        try{
        DatagramPacket dp = new DatagramPacket(bytes,/**数据报包数据*/
                                               0,/**0表示第0个位置,分组数据偏移量*/
                                               bytes.length,/**数组长度*/
                                               InetAddress.getByName("127.0.0.1"),/**目的地址*/
                                              8000/**目的端口号*/);
            //本程序的端口号
            DatagramSocket socket = new DatagramSocket(9000);
            socket.send(dp);   
        } catch(UnknownHostException e){
            e.printStackTrace();
        } catch( IOException e){
            e.printStackTrace();
        }
    }
}

客户端:客户端要先启动,在receive()阻塞,等待server发数据报

public class UDPClientDeom{
    public static void main(String[] args){
    
    byte[] bytes = new byte[1024];
    //封装成数据报包
    DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
    try{
        DatagramSocket socket = new DatagramSocket(8000);
        System.out.println("正在接收数据中...");
        socket.receive(dp);//接收数据,会造成阻塞
        String s = new String(dp.getData(),0,dp.getLength());//从data中取,从0 到dp.getLength()
        System.out.println(s);
        socket.close();
        } catch(UnknownHostException e){
            e.printStackTrace();
        } catch( IOException e){
            e.printStackTrace();
        }
    }
}

 

7.URL

URL概述

URL(uniform resource location)类URL代表一个统一资源定位符,是互联网上标准资源的地址,它是指向互联网“资源”的指针,互联网的每个文件都有唯一的一个URL。抽象类URLConnection是所有类的超类,它代表应用程序和URL之间的通信链接

public class URLDeom{
    public static void main(String[] args){
    try{
        URL url = new URL("https://pic.cnblogs.com/avatar/2988753/20221012224248.png");//资源路径
        HttpURLConnection conn = (HttpURLConnection)url.openConnection();//打开链接,因为图片地址是http协议,可以用HttpURLConnection
        BufferedInputStream in = new BufferedInputStream(conn.getInputStream());
        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream("D:\\xx\\wukong.jpg"));
        byte[] bytes = new byte[1024];
        int len = -1 ;
        while((len = in.read(bytes))!=-1){
            out.write(bytes,0,len);
                out.flush();
        }
        in.close();
        out.close();
        System.out.println("下载成功... ");
        } catch(MalformedURLException e){
            e.printStackTrace();
        } catch( IOException e){
            e.printStackTrace();
        }
    }
}
​
​

 

8.MINA框架

  • 什么是MINA?一句话就是:一个简洁易用的基于TCP/IP通信的java框架

  • 下载地址:http://mina.apache.org/downloads-mina.html 下载zip包

  • 一个简单的网络程序需要的最少jar包:mina-core-2.0.16.jar、slf4j-api-1.7.21.jar

  • 开发一个MINA应用,简单的说,就是①创建连接,②设定过滤规则,③编写自己的消息处理器

  • Mina框架可以帮助我们快速开发高性能、高扩展的网络通信应用,Mian提供了事件驱动、异步操作的编程模型,默认使用NIO作为底层支持

    示例:
public class Server{
    public static void main(String[] args){
        //创建一个非阻塞的Server端Socket,用NIO
​
        SocketAcceptor acceptor= new NioSocketAcceptor();//创建接收数据的过滤器
​
        DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();//chain链
        //设定这个过滤器将一行一行(/r/n)的读取数据
        chain.addLast("myChin",new ProtocolCodecFilter(new TextLineCodecFactory()));
        //如,设定服务器端的消息处理器:一个MainaServerHandler对象
        acceptor.setHandler(new MinaServerHandler());
        int bindPort = 9999;//服务器端口号
        
        try{
            //绑定端口,启动服务器
            //立即返回
            acceptor.bind(new InetSocketAddress(bindPort));
        }catch(IOException e){
            e.printStackTrace();
        }
        System.out.println("Mina Server is running: = "+bindPort);
    }
}

 

/**
服务器端的消息处理器
*/
public class MinaServerHandler  extends IoHandleAdapter{
    //一次会话被打开
    public void sessionOpened(IoSession session) throws Exception{
        super.sessionOpened(session);
        System.out.println("welcome client"+session.getRemoteAddress());
    }
    //会话被关闭
    public void sessionClosed(IoSession session) throws Exception{
        super.sessionClosed(session);
        System.out.println("client closed");
    }
    //接收消息
    public void messageReceived(IoSession session,Object message) throws Exception{
        super.messageReceived(session,message);
        String msg = (String)message;//接收到的消息对象
        System.out.println("收到客户端发来的消息: "+msg);
        //向客户端发送消息对象
        session.write("echo:"+msg);
    }
    
    
    }
}

使用telnet测试:telnet localhost 9999

public class Client{
    public static void main(String[] args){
        NioSocketConnector connector = new NioSocketConnector();//创建接收数据的过滤器
        DefaultIoFilterChainBuilder chain = connector.getFilterChain();
        //设定这个过滤器将一行一行(/r/n)的读取数据
        chain.addLast("myChin",new ProtocolCodecFilter(new TextLineCodecFactory()));
        //如,设定服务器端的消息处理器:一个SamplMainaServerHandler对象
        connector.setHandler(new MinaServerHandler());
        connector.setConnectTimeoutMillis(30);//Set connect timeout.
        //连接到服务器:
        ConnectFuture cf = connector.connect(new InsetSocketAddress("localhost",9999));
        //等待连接成功,立即返回
        cf.awaitUninterruptibley();
        Scanner input = new Scanner(System.in);
        while(true){
             System.out.println("请输入:");
             String info = input.nextLine();
             //发送消息
            cf.getSession().write(info);
        }
        //等待服务器连接关闭,结束长连接
        //cf.getSession().getCloseFuture().awaitUninterruptibly();//会阻塞
        //connector.dispose();  //释放    
    }
}

 

public class SampleMinaServerHandler extends IoHandleAdapter{
    //当一个客户端连接进入时
    public void sessionOpened(IoSession session) throws Exception{
        System.out.println("incomming client"+session.getRemoteAddress());
        super.sessionOpened(session);
​
        session.writ("我来啦");
​
    }
    //当一个客户端关闭时
    public void sessionClosed(IoSession session) {
                super.sessionClosed(session);
​
       System.out.println("client closed");
    }
    //当一个客户端发送的消息到达时:
    public void messageReceived(IoSession session,Object message) throws Exception{
        super.messageReceived(session,message);
​
        //我们已设定了服务器解析消息的规则是一行一行读取,这里就可转为String;
        String msg = (String)message;
        System.out.println("收到服务器端发来的消息: "+msg);
        //测试将消息会送给客户端
        session.write(msg);
    }
   } 
    

使用Mina直接传送对象

1.public class User info implements java.io.Serializable

2.服务器,客户端都设定以对象为单位

//设定这个过滤器将以对象为单位读取数据

ProtocolCodecFilter filter = new ProtocolCodecFilter(new ObjectSerializationCodecFactory());

chain.addLast("objectFilter",filter);

3.接收对象

public void messageReceived(IoSession session,Object message) throws Exception{

//我们已设定了服务器解析消息的规则一个Userinfo对象为单位传输:

Userinfo us = (Userinfo)message;

}

public class Message implements Serializable{
    private String from ;
    private String to; 
    private String type;
    private String info;
    ......
}

 

■免责申明
⒈ 本站是纯粹个人学习网站,与朋友交流共赏,不存在任何商业目的。
⒉ 本站利用了部分网络资源,版权归原作者及网站所有,如果您对本站所载文章及作品版权的归属存有异议,请立即通知我们,我们将在第一时间予以删除,同时向你表示歉意!

 

posted @ 2022-10-14 17:34  逝去の年华  阅读(112)  评论(0编辑  收藏  举报