Java——网络编程-IP、TCP、UDP

网络的概念

网络:一组相互连接的计算机

  • 多台计算机组成
  • 使用物理线路进行连接

网络编程的三要素

  1. IP地址:唯一标识网络上的每一台计算机,两台计算机之间通信的必备要素
  2. 端口号:计算机中应用的标号(代表一个应用程序),0-1024系统使用或保留端口,有效端口0-65536
  3. 通信协议: 通信的规则 TCP、UDP

网络模型一

OSI参考模型: 开放系统互连参考模型(Open System Interconnect)

OSI模型其实现实生活中并不会用到,现实生活中用到的是下面的四层模型

网络模型二

TCP/IP参考模型:传输控制/网际协议(Transfer Control Protocol/Internet Protocol)

特殊的IP地址

  • 0.0.0.0 :本机
  • 127.0.0.1 :本机回环地址,用于本机测试
  • 255.255.255.255 :当前子网,一般用于向子网广播信息

端口:
端口是一个虚拟的概念,并不是说在主机上真的有若干个端口,通过端口,可以在一个主机上运行多个网络应用程序

传输协议:
UDP: 相当于发短信(有字数限制),不需要建立连接,数据包的大小限制在64kb,效率较高,不安全,容易丢包
TCP:相当于打电话,需要建立连接,效率相对比较低,数据传输安全,三次握手完成.(点名—答道—确认)

注意:UDP和TCP在使用的时候并不一定都是分开使用的,它们之间可以混合使用,例如视频通话的时候就是使用TCP先开始建立连接,在连接建立后再通过UDP进行传输.如果全程都只用TCP进行传输,将会耗费特别大的数据量

Socket套接字
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket.
Java中使用Socket完成TCP程序的开发,使用此类可以方便的建立可靠的、双向的、持续性的、点对点的通讯连接.
在Socket的程序开发中,服务器端使用ServerSocket等待客户端的连接.
对于java的网络程序来讲,每一个客户端都使用一个Socket对象表示

基于TCP协议的Socket编程
进行网络通信时,Socket需要借助数据流来完成数据的传递工作

练习:建立一个客户端和服务端,并且客户端向服务端发送一条消息

public class Client {
    public static void main(String[] args) {
        try {
            Socket client;

            //创建socket对象,其实开启实现io的虚拟接口(此接口不是java中的接口,而是类似于网线的插槽)
            //需要指定数据接收方的ip地址和端口号
            client = new Socket("localhost",10086);
            //获取输出流对象,向服务端发送数据
            OutputStream out = client.getOutputStream();
            DataOutputStream dataOutputStream = new DataOutputStream(out);
            dataOutputStream.writeUTF("hello,server");
            dataOutputStream.close();
            out.close();
            client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Server {
    public static void main(String[] args) {
        try {
            //服务端需要使用serverSocket来开放本地的端口
            ServerSocket serverSocket = new ServerSocket(10086);
            //需要接受client传输过来的数据,需要定义socket对象
            Socket server = serverSocket.accept();
            //通过server获取输入流对象
            InputStream in = server.getInputStream();
            DataInputStream dataInputStream = new DataInputStream(in);
            String str= dataInputStream.readUTF();
            System.out.println(str);
            //关闭流操作
            dataInputStream.close();
            in.close();
            server.close();
            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在运行的时候记得先启动服务端,再启动客户端

接下来是服务端在接收到客户端信息的时候返回响应信息的写法

public class Client {
    public static void main(String[] args) {
        try {
            //创建客户端的套接字
            Socket client = new Socket("127.0.0.1",10000);
            //获取输出流对象
            OutputStream out = client.getOutputStream();
            //数据输出
            out.write("hello Server,are you OK?".getBytes());
            //获取输入流对象
            InputStream in = client.getInputStream();
            byte[] bytes = new byte[1024];
            int len = in.read(bytes);
            System.out.println(new String(bytes,0,len));

            //关闭流
            in.close();
            out.close();
            client.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Server {
    public static void main(String[] args) {
        try {
            //服务端需要使用serverSocket来开放本地的端口
            ServerSocket serverSocket = new ServerSocket(10000);
            //获取服务端的套接字对象
            Socket server = serverSocket.accept();
            //获取输入流对象
            InputStream in = server.getInputStream();
            byte[] bytes = new byte[1024];
            int len = in.read(bytes);
            System.out.println(new String(bytes,0,len));

            //给客户端响应
            OutputStream out = server.getOutputStream();
            out.write("I'm fine ! thanks Client".getBytes());

            //关闭流
            out.close();
            in.close();
            server.close();
            serverSocket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

到目前为止我们已经完成了客户端和服务端的建立,此时的程序就像个聊天室,而且该聊天室只允许两个人存在,而且只能发一条消息,那我们该怎样做才能让聊天室人数更多且能发更多消息呢?

我们先来写一下从客户端往服务端传输图片的程序

public class ClientPic2 {
    public static void main(String[] args) throws IOException {
        //创建图片的输入流对象
        FileInputStream fileInputStream = new FileInputStream("mePic.jpg");
        //创建Socket
        Socket client = new Socket("localhost",10089);
        //获取输出流对象
        OutputStream outputStream = client.getOutputStream();
        int tmp = 0;
        while ((tmp = fileInputStream.read())!=-1){
            outputStream.write(tmp);
        }
        client.shutdownOutput();//添加输出流完成的标志

        InputStream inputStream = client.getInputStream();
        byte[] bytes = new byte[1024];
        int len = inputStream.read(bytes);
        System.out.println(new String(bytes,0,len));
        client.shutdownInput();//添加输入流完成的标志
        //关闭操作
        inputStream.close();
        outputStream.close();
        fileInputStream.close();
        client.close();
    }
}
public class ServerPic {
    public static void main(String[] args) throws IOException {
        //创建服务端对象,开放端口
        ServerSocket serverSocket = new ServerSocket(10089);
        //创建服务端的Socket
        Socket server = serverSocket.accept();
        //获取输入流对象
        InputStream inputStream = server.getInputStream();
        //创建文件输出流对象
        FileOutputStream fileOutputStream = new FileOutputStream("meCopy.jpg");
        int tmp = 0;
        while((tmp = inputStream.read())!=-1){
            fileOutputStream.write(tmp);
        }
        server.shutdownInput();//添加输入流完成的标志
        //上传如片结束后给予客户端响应
        OutputStream outputStream = server.getOutputStream();
        outputStream.write("上传成功!".getBytes());
        server.shutdownOutput();//添加输出流完成的标志

        outputStream.close();
        fileOutputStream.close();
        inputStream.close();
        server.close();
        serverSocket.close();
    }
}

上面的程序我们使用到的shutdownInput()和shutdownOutput()是必须要手动添加的,一来它的作用是告诉程序已经传输完毕;二来,在以后涉及到多线程的时候,如果每个线程都只使用不释放的话,内存很容易溢出

练习: 实现用户登录
需求说明:
客户端序列化对象
服务器端反序列化对象

我们在io流中,如果想传输一个对象,必须使用ObjectInputStream、ObjectOutputStream
但是这个对象在实际使用过程中我们需要先实现一个序列化的值,这个序列化的值需要通过我们的序列化接口提前创建好.序列化接口中有一个单独的ID值.这个ID值在使用过程中我们是要保持一致的.
在实际的网站登录过程中,情况是登录失败时返回错误提示,然后用户可以继续尝试登录,而对服务端不会有影响

public class User implements Serializable {
    //程序自动生成的ID
    private static final long serialVersionUID = 4594787634610567556L;
    private String userName;
    private String password;

    public String getUserName() {
        return userName;
    }
}

上面的ID值其实是程序自动生成的,如何让程序自动生成这个ID值呢,我们先来设置一下我们的IDEA
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后对着我们的类型使用快捷键(fn+option+enter)即可出现设置UID的选择
在这里插入图片描述
接下来我们先编写客户端

public class Client {
    public static void main(String[] args) throws IOException {
        Socket client = new Socket("localhost",10009);
        OutputStream outputStream = client.getOutputStream();
        User user = getUser();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(user);
        //调用shutdown方法告诉对方传输完成
        client.shutdownOutput();
        //接收服务端发出的响应
        DataInputStream dataInputStream = new DataInputStream(client.getInputStream());
        String str = dataInputStream.readUTF();
        System.out.println(str);
        //关闭流
        dataInputStream.close();
        objectOutputStream.close();
        outputStream.close();
        client.close();
    }

    public static User getUser(){
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String userName = scanner.nextLine();
        System.out.println("请输入密码:");
        String password = scanner.nextLine();
        return new User(userName,password);
    }
}

编写服务端

public class Server {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ServerSocket serverSocket = new ServerSocket(10009);
        Socket server = serverSocket.accept();
        //获取输入流对象
        InputStream inputStream = server.getInputStream();
        //需要使用ObjectInputStream对象
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        //使用User对象进行接收,强制转换
        User user = (User) objectInputStream.readObject();
        String str = "";
        if (user.getUserName().equals("msb") && user.getPassword().equals("psv")){
        	System.out.println(user.getUserName+" 登录了系统")
            str = "登录成功";
        }else {
            str = "登录失败";
        }
        //调用shutdown方法告知程序接收完成
        server.shutdownInput();
        //向客户端发出响应信息
        DataOutputStream outputStream = new DataOutputStream(server.getOutputStream());
        outputStream.writeUTF(str);
        server.shutdownOutput();
        //关闭流
        outputStream.close();
        objectInputStream.close();
        inputStream.close();
        server.close();
        serverSocket.close();
    }
}

由于目前我们是在同一个IDEA里面编写的,都客户端序列化对象和服务端反序列化对象都调用的是同一个User类,并不会发现此时User类中serialVersionUID的作用.若我们是在两个IDEA或两台电脑上分别运行客户端和服务端,此时我们两台电脑中的User类的serialVersionUID就必须要保持一致了,不然会程序运行失败

运行上面的程序,我们此时可以发现,登录功能已经实现了,但服务端还是只接收了一次客户端的请求后就关闭了,我们可以使用什么方式来是服务端保持一直开启且能一直接收客户端请求呢?
我们此时可以使用while循环,也可以使用多线程的方式

我们先来使用while循环的方式来实现看看

public class Client2 {
    public static void main(String[] args) throws IOException {
        boolean flag = false;

        while (!flag){
            Socket client = new Socket("localhost",10009);
            OutputStream outputStream = client.getOutputStream();

            User user = getUser();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
            objectOutputStream.writeObject(user);
            //调用shutdown方法告诉对方传输完成
            client.shutdownOutput();
            //接收服务端发出的响应
            DataInputStream dataInputStream = new DataInputStream(client.getInputStream());
            String str = dataInputStream.readUTF();
            System.out.println(str);
            if (str.equals("登录成功")){
                flag=true;
            }
            dataInputStream.close();
            objectOutputStream.close();
            outputStream.close();
            client.close();
        }
    }
    public static User getUser(){
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String userName = scanner.nextLine();
        System.out.println("请输入密码:");
        String password = scanner.nextLine();
        return new User(userName,password);
    }
}

public class Server2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ServerSocket serverSocket = new ServerSocket(10009);

        while (true){
            Socket server = serverSocket.accept();
            //获取输入流对象
            InputStream inputStream = server.getInputStream();
            //需要使用ObjectInputStream对象
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            //使用User对象进行接收,强制转换
            User user = (User) objectInputStream.readObject();
            String str = "";
            if (user.getUserName().equals("msb") && user.getPassword().equals("psv")){
                System.out.println("用户: "+user.getUserName()+" 登录了系统");
                str = "登录成功";
            }else {
                str = "登录失败";
            }
            //调用shutdown方法告知程序接收完成
            server.shutdownInput();
            //向客户端发出响应信息
            DataOutputStream outputStream = new DataOutputStream(server.getOutputStream());
            outputStream.writeUTF(str);
            server.shutdownOutput();
            //关闭流
            outputStream.close();
            objectInputStream.close();
            inputStream.close();
            server.close();
        }
//        serverSocket.close();
    }

}

接下来我们使用多线程的方式来实现

public class LoginThread implements Runnable{
    private Socket socket;
    public LoginThread(Socket socket){
        this.socket = socket;
    }
    @Override
    public void run() {
        ObjectInputStream objectInputStream = null;

        DataOutputStream outputStream = null;
        try {
            objectInputStream = new ObjectInputStream(socket.getInputStream());
            User user = (User)objectInputStream.readObject();
            String str = "";
            if (user.getUserName().equals("msb") && user.getPassword().equals("psv")){
                System.out.println("用户: "+user.getUserName()+" 登录了系统");
                str = "登录成功";
            }else {
                str = "登录失败";
            }
            //调用shutdown方法告知程序接收完成
            socket.shutdownInput();
            //向客户端发出响应信息
            outputStream = new DataOutputStream(socket.getOutputStream());
            outputStream.writeUTF(str);
            socket.shutdownOutput();
            //关闭流
//            inputStream.close();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }finally {
            try {
                outputStream.close();
                objectInputStream.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
}

public class Server3 {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(10009);
        while (true){
            Socket socket = serverSocket.accept();
            LoginThread loginThread = new LoginThread(socket);
            new Thread(loginThread).start();
        }
    }
}

接下来我们编写UDP的传输,UDP实际我们编程中应用的比较少,在此作为了解即可

public class UDPClient {
    public static void main(String[] args) throws IOException {
        //创建UDP通信的socket
        DatagramSocket datagramSocket = new DatagramSocket(10009);
        //从控制台读取数据
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入传输内容: ");
        String str = scanner.nextLine();
        //需要传输的包,包里面添加的是服务端的地址和端口号
        DatagramPacket packet = new DatagramPacket(str.getBytes(),str.getBytes().length, InetAddress.getByName("localhost"),10008);
        datagramSocket.send(packet);
        datagramSocket.close();
    }
}
public class UDPServer {
    public static void main(String[] args) throws IOException {
        DatagramSocket datagramSocket = new DatagramSocket(10008);
        byte[] buf = new byte[1024];
        //用来接受传输过来的数据
        DatagramPacket datagramPacket =new DatagramPacket(buf,buf.length);
        //利用创建好的数据报包对象来接受数据
        datagramSocket.receive(datagramPacket);
        //打印输出信息
        System.out.println(new String(datagramPacket.getData(),0,datagramPacket.getLength()));
        datagramSocket.close();

    }
}
以下是对提供的参考资料的总结,按照要求结构化多个要点分条输出: 4G/5G无线网络优化与网规案例分析: NSA站点下终端掉4G问题:部分用户反馈NSA终端频繁掉4G,主要因终端主动发起SCGfail导致。分析显示,在信号较好的环境下,终端可能因节能、过热保护等原因主动释放连接。解决方案建议终端侧进行分析处理,尝试关闭节电开关等。 RSSI算法识别天馈遮挡:通过计算RSSI平均值及差值识别天馈遮挡,差值大于3dB则认定有遮挡。不同设备分组规则不同,如64T和32T。此方法可有效帮助现场人员识别因环境变化引起的网络问题。 5G 160M组网小区CA不生效:某5G站点开启100M+60M CA功能后,测试发现UE无法正常使用CA功能。问题原因在于CA频点集标识配置错误,修正后测试正常。 5G网络优化与策略: CCE映射方式优化:针对诺基亚站点覆盖农村区域,通过优化CCE资源映射方式(交织、非交织),提升RRC连接建立成功率和无线接通率。非交织方式相比交织方式有显著提升。 5G AAU两扇区组网:与三扇区组网相比,AAU两扇区组网在RSRP、SINR、下载速率和上传速率上表现不同,需根据具体场景选择适合的组网方式。 5G语音解决方案:包括沿用4G语音解决方案、EPS Fallback方案和VoNR方案。不同方案适用于不同的5G组网策略,如NSA和SA,并影响语音连续性和网络覆盖。 4G网络优化与资源利用: 4G室分设备利旧:面对4G网络投资压减与资源需求矛盾,提出利旧多维度调优策略,包括资源整合、统筹调配既有资源,以满足新增需求和提质增效。 宏站RRU设备1托N射灯:针对5G深度覆盖需求,研究使用宏站AAU结合1托N射灯方案,快速便捷地开通5G站点,提升深度覆盖能力。 基站与流程管理: 爱立信LTE基站邻区添加流程:未提供具体内容,但通常涉及邻区规划、参数配置、测试验证等步骤,以确保基站间顺畅切换和覆盖连续性。 网络规划与策略: 新高铁跨海大桥覆盖方案试点:虽未提供详细内容,但可推测涉及高铁跨海大桥区域的4G/5G网络覆盖规划,需考虑信号穿透、移动性管理、网络容量等因素。 总结: 提供的参考资料涵盖了4G/5G无线网络优化、网规案例分析、网络优化策略、资源利用、基站管理等多个方面。 通过具体案例分析,展示了无线网络优化中的常见问题及解决方案,如NSA终端掉4G、RSSI识别天馈遮挡、CA不生效等。 强调了5G网络优化与策略的重要性,包括CCE映射方式优化、5G语音解决方案、AAU扇区组网选择等。 提出了4G网络优化与资源利用的策略,如室分设备利旧、宏站RRU设备1托N射灯等。 基站与流程管理方面,提到了爱立信LTE基站邻区添加流程,但未给出具体细节。 新高铁跨海大桥覆盖方案试点展示了特殊场景下的网络规划需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值