Java基础知识网络编程

概述

java除了可以完成本地的操作,也可以完成网络通讯。比如想从自己的电脑上发送一个信息到张三的电脑上,张三收到信息之后再给我返回一个信息,利用java实现两个机器之间的数据的通讯。数据通讯的原理就是数据传输的过程,与本机的区别就是涉及到网络。
网络通讯要具备的要素和模型:
比如和张三通讯
1、首先要找到张三的主机,张三主机的标识就是IP地址(也就是主机的名字,IP地址由4个字节表示,可以表示很多主机,避免冲突)。
2、和张三通讯的方式有很多种,可以是QQ,也可以是微信。两个机器都要装有通讯的软件QQ或者微信。数据要发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识,比如QQ用4000标识,微信用2900标识,为了方便称呼这个数字,叫做端口 ,这是逻辑端口,不是物理接口,也不是网线接口。
这里写图片描述
3、两个机器想要进行通讯,还要定义通信规则,使两方都能听懂对方的内容,这个通信规则就是协议。互联网的成千上万的机器都能进行通讯,因为国际组织定义了一个通用的协议TCP/IP,现在的操作系统里面都安装了这个协议 ,适用于广域网和局域网。
这里写图片描述
可以添加新的协议。也可以卸载TCP/IP协议,但是一般的机器卸载不了,因为已经固化到系统里面了。
但是某些特有的单位和组织,为了安全,他们的通讯方式和我们不一样,他们有自己特有的协议进行通讯,所以外界不能与之通讯,入侵不了。
IP地址介绍:

  • IP地址是由4段组成,每段是个字节,最大值是255
  • IP地址分成很多段,A/B/C等
  • 有个IP很特殊127.0.0.1,这是本地回环地址,当本机没有配地址时,本机默认的地址就是127.0.0.1,其中一个用处是用来测试网卡,ping 127.0.0.1 如果成功说明网卡正常。
  • 有些IP地址被保留不用于公网,用于局域网中。不同局域网中可以有相同的IP地址。192.168.. 是最常用的保留地址段,还有其他保留地址段。
  • 子网掩码的出现是为了解决电脑数量增多,IP地址不够用的问题。电信厂商们给某个区域只提供一个公网IP,通过子网掩码将这个区域内划分为局域网,整个区域走同一个公网IP。
  • 后来四段IP地址不够用了,便出现了六段IP地址,而且出现字母,数量多得多。

端口介绍
端口的大小:0-65535
其中0-1024端口被系统使用了,自己的程序也可以用,但是可能出现冲突现象。
几个默认端口:web服务:80、tomcat服务器:8080、MySql数据库:3306。可以自己定义端口。

网络模型

这里写图片描述
ISO网络模型中传输过程简单描述:
以QQ传送消息为例解释。
QQ软件在应用层上,首先将数据按照应用层的封装规则对数据进行封装。
然后传到表示层,再按照表示层的规则进行封装,然后进行会话层封装,传输层封装,网络层封装(加上IP地址),数据链路层封装,最后到达物理层,物理层就是网线、光纤、无线 等。这个过程叫做数据封包过程,根据每一层的协议加上每层的信息。数据封包之后,最后经过物理层传输到目的地址。
在目的地址会按照每层协议进行数据拆包,最终到达应用层,在应用层根据端口号确定将数据传给QQ软件。
TCP/IP模型
由于ISO的七层模型理解起来比较麻烦,后来出现了TCP/IP模型,将7层模型简化成了4层模型。将应用层、表示层、会话层归为应用层;将数据链路层和物理层归为主机至网络层,在加上传输层和网络层一共是4层。
我们进行Java网络编程就是在传输层和网际层,而Java Web开发,是在应用层,将底层的东西进行了封装。
传输层协议常见的有TCP、UDP
网际层最常见的协议IP
应用层的协议有很多比如HTTP和FTP等。

网络通讯要素

IP地址、端口号、传输协议。
Java语言进行网络通讯时,这三个要素是怎么体现的呢?
Java提供三个对象来操作三个要素。
IP地址:类 InetAddress 此类表示互联网协议 (IP) 地址。
IP 地址是 IP 使用的 32 位或 128 位无符号数字,它是一种低级协议,UDP 和 TCP 协议都是在它的基础上构建的。
没有构造函数。
获得对象的方式:static InetAddress getLocalHost() 返回本地主机
主要方法: String getHostAddress()
返回 IP 地址字符串(以文本表现形式)。
String getHostName()
获取此 IP 地址的主机名。
static InetAddress getByName(String host)
在给定主机名的情况下确定主机的 IP 地址。
端口号:用于标识进程的逻辑地址,不同进程的标识。有效端口号0~65535.其中0~1024系统使用或者保留端口。
传输协议:
两种传输协议比较常见:TCP和UDP,区别是什么?
UDP(面向无连接,发送数据之前双方不需要建立连接,类似邮局寄包裹):
- 将数据及源和目的封装成数据包中,不需要建立连接
- 每个数据包的大小限制在64K
- 因无连接,是不可靠协议
- 不需要建立连接,速度快
聊天通讯使用的就是UDP。网络视频也是UDP。对传输速度要求高,可靠性要求低的网络传输一般是UDP。
TCP(面向连接,双方必须都在(三次握手确定),相当于打电话):
- 建立连接,形成传输数据
- 在连接中进行大数据量传输 (不需要封装包)
- 通过三次握手完成连接,是可靠协议
- 必须建立连接,效率稍低 ,消耗资源
文件下载使用的就是TCP,可靠性要求高

Socket

Java网络编程指的就是Socket编程。Socket是插座(也称套接字)的意思。
Socket为网络服务提供一种机制。
两个主机如果想要通讯,需要有物理层的连接,比如网线,主机上都有一个网线的插口,连接两个主机。每个应用程序都有一个类似的插口,使两个主机上的同种应用程序之间可以网络通讯。这个插口就是Socket的概念。
所以,通信的两端都有Socket,网络通信其实就是Socket间的通信,数据在两个Socket之间通过IO传输。
每种传输协议对应的建立Socket端点的方式,就有了UDP传输方式中Socket服务建立方式和TCP传输方式中Socket服务建立方式。

UDP传输方式

DatagramSocket类 此类表示用来发送和接收数据报包的套接字(插座)。
构造方法:
DatagramSocket()
构造数据报套接字并将其绑定到本地主机上任何可用的端口(随机)。
DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口(接收端和发送端都可以指定绑定的端口号)。
主要方法:
void receive(DatagramPacket p)
从此套接字接收数据报包。
void send(DatagramPacket p)
从此套接字发送数据报包。
void close()
关闭此数据报套接字。

DatagramPacket类 此类表示数据报包。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
构造方法:
DatagramPacket(byte[] buf, int length)
构造 DatagramPacket,用来接收长度为 length 的数据包。
部分方法:
InetAddress getAddress()
返回某台机器的 IP 地址,此数据报将要发往该机器或者是从该机器接收到的。
byte[] getData()
返回数据缓冲区。

小实例:

需求:创建两个应用程序,一个用于发送数据,一个用于接收数据,接收数据的端口设为10000。
思路:
发送端
1,建立udpsocket服务
2. 提供数据,并将数据封装到数据包中
3. 通过socket服务的发送功能,将数据包发送出去
4.关闭资源
接收端:
1,建立udpsocket服务,通常会监听一个端口,其实就是给这个接收网络应用程序定义数字标识, 方便于明确哪些数据过来,该应用程序可以处理。
2. 定义一个数据包,因为要存储接收到的字节数据,且数据包对象中有更多功能可以
提取字节数据中的不同数据信息
3. 通过socket服务的接收功能,将数据包存入已经定义好的数据包中。
4. 通过数据包对象的特有功能,将这些不同的数据取出,打印在控制台上
5. 关闭资源

import java.net.*;
class UdpSendDemo
{
    public static void main(String[] args) throws Exception
    {
        DatagramSocket ds=new DatagramSocket();

        byte[] data="udp is coming".getBytes();
        DatagramPacket dp=new DatagramPacket(data,data.length,InetAddress.getByName("127.0.0.1"),10000);
         ds.send (dp);
         ds.close();
    }
}
import java.net.*;
class UdpReceiveDemo 
{
    public static void main(String[] args) throws Exception
    {
        DatagramSocket ds=new DatagramSocket(10000);
        byte[] data=new byte[1024];
        DatagramPacket dp=new DatagramPacket(data,data.length);
        ds.receive(dp);//阻塞式方法,没有接收到数据就等
        InetAddress ip=dp.getAddress();
        System.out.println("主机名"+ip.getHostName());
        System.out.println("IP"+ip.getHostAddress());
        System.out.println("数据:"+new String(dp.getData(),0,dp.getLength()));

    }
}

使用UDP方式编写简单的聊天程序

要求:
编写一个聊天程序。
有收数据的部分,和发数据的部分。
这两部分需要同时执行。
那就需要用到多线程技术。
一个线程控制收,一个线程控制发。
因为收和发动作是不一致的,所以要定义两个run方法。
而且这两个方法要封装到不同的类中。

发送类:

class SendThread implements Runnable
{
    private DatagramSocket ds=null;
    public  SendThread(DatagramSocket ds)
    {
        this.ds=ds;
    }

    public void run()
    {
        try
        {
            BufferedReader bur=new BufferedReader(new InputStreamReader(System.in));
            String line=null;
            while((line=bur.readLine())!=null)
            {
                byte[] senddata=line.getBytes();
                DatagramPacket dp=new DatagramPacket(senddata,senddata.length,InetAddress.getByName("192.168.1.114"),10001);
                ds.send(dp);
                //System.out.println("IP:"+InetAddress.getLocalHost().getHostName()+":"+line);
            }
        }
        catch (Exception e)
        {
            throw new RuntimeException("发送失败");
        }


    }
}

接收类:

class ReceiveThread implements Runnable
{
    private DatagramSocket ds=null;
    public  ReceiveThread(DatagramSocket ds)
    {
        this.ds=ds;
    }

    public void run()
    {
        try
        {
            while(true)
            {
                byte[] data=new byte[1024];
                DatagramPacket dp=new DatagramPacket(data,data.length);
                ds.receive(dp);//阻塞式方法,没有接收到数据就等
                InetAddress ip=dp.getAddress();
                String receiveData=new String(dp.getData(),0,dp.getLength());
                System.out.println("IP:"+ip.getHostAddress()+":"+receiveData);
            }
        }
        catch (Exception e)
        {
            throw new RuntimeException("接收失败");
        }

    }
}

聊天类:

{
    public static void main(String[] args) throws Exception
    {
        DatagramSocket sds=new DatagramSocket();
        DatagramSocket rds=new DatagramSocket(10002);

        new Thread(new SendThread(sds)).start();
        new Thread(new ReceiveThread(rds)).start();
    }
}

因为聊天是两个主机之间的通信,为了简单的测试程序,在同一主机上开启两个dos窗口测试。并且上面的代码只是通信的一方的代码,另外一方的代码没有差别,只是DatagramSocket和DatagramPacket绑定的端口不同:比如A的接收Socket服务对象的端口绑定为10002端口,发送的DatagramPacket绑定为对方的10001端口,而B的接收Socket服务对象的端口绑定为10001端口,发送的DatagramPacket绑定为对方的10002端口.两者的发送Socket服务不用绑定端口,让系统随机分配就好。听的好晕,看看代码就好了。简言之,发送数据时,用DatagramPacket确定接收者的端口,接收数据时,用DatagramSocket确定自身绑定的端口,这两个端口保持一致,就可以接收到数据啦。
这里写图片描述

TCP传输方式

UDP分的是发送端和接收端,而TCP分的是客户端和服务器端,分别对应两个对象:Socket和ServerSocket
客户端:
Socket类:此类实现客户端套接字(也可以就叫“套接字”)。
构造函数:
Socket(InetAddress address, int port)
创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
Socket(String host, int port)
创建一个流套接字并将其连接到指定主机上的指定端口号。
通过查阅Socket对象,发现在该对象建立时,就可以连接指定的主机。因为TCP是面向连接的,所以在建立Socket服务时,就要有服务端存在,并且连接成功。形成通路后,在该通道进行数据的传输。也有无参数的构造函数,此时用connect()方法确定目的端并连接。
Socket对象一旦建立成功,说明通信的通道已建立成功,便产生了Socket流也就是网络流。
Socket中封装了网络流,既有输入流也有输出流。可以用方法获得这两个流。
InputStream getInputStream()
返回此套接字的输入流。
OutputStream getOutputStream()
返回此套接字的输出流。 通过网络发送到对方主机上。
服务器端:
SocketServer类:此类实现服务器套接字。服务器套接字等待请求通过网络传入。
构造方法:
ServerSocket()
创建非绑定服务器套接字。
ServerSocket(int port)
创建绑定到特定端口的服务器套接字。
方法:
Socket accept()
侦听并接受到此套接字的连接。 这也是一个阻塞式方法。返回一个Socket对象。
多个客户端往服务端发送数据,服务端返回数据时,是如何做到那个客户端发来的,返回给哪个客户端,而不发生错误呢?
答:实际上,ServerSocket用accept()方法获得请求的客户端的Socket对象,利用该对象的输入流和输出流与该客户端通信,这样便不会发生发错对象的情况。而且,服务端的输入流和客户端的输出流对应,服务端的输出流和客户端的输入流对应。

TCP演示示例1:

客户端:
需求:给服务端发送一个文本数据

import java.io.*;
import java.net.*;

class TcpClient
{
    public static void main(String[] args) throws Exception
    {
        //创建客户端的socket服务,指定目的主机和端口,
        //一旦成功,通路建立,便有了Socket流即网络流,Socket流里面既有输入流也有输出流。
        Socket s=new Socket("192.168.1.114",10003);
        //为了发送数据,应该获取Socket流中的输出流
        OutputStream os=s.getOutputStream();
        os.write("tcp is coming".getBytes());

        s.close();
    }
}

服务端:
需求:定义端点接收数据并打印在控制台上。

服务端:
1,建立服务端的socket服务。ServerSocket();
并监听一个端口。
2,获取连接过来的客户端对象。
通过ServerSokcet的 accept方法。没有连接就会等,所以这个方法阻塞式的。
3,客户端如果发过来数据,那么服务端要使用对应的客户端对象,并获取到该客户端对象的读取流来读取发过来的数据。
并打印在控制台。

4,关闭客户端(服务端有可能不关闭自己,但会关闭客户端,节省资源)
5、关闭服务端。(可选)

class  TcpServer
{
    public static void main(String[] args) throws Exception
    {
        //建立服务端socket服务。并监听一个端口。
        ServerSocket ss = new ServerSocket(10003);

        //通过accept方法获取连接过来的客户端对象。
        while(true)
        {
        Socket s = ss.accept();

        String ip = s.getInetAddress().getHostAddress();
        System.out.println(ip+".....connected");

        //获取客户端发送过来的数据,那么要使用客户端对象的读取流来读取数据。
        InputStream in = s.getInputStream();

        byte[] buf = new byte[1024];
        int len = in.read(buf);

        System.out.println(new String(buf,0,len));

        s.close();//关闭客户端.
        }
        //ss.close();
    }
}

这里写图片描述
TCP演示示例2:
需求:客户端给服务端发送数据,服务端收到后,给客户端反馈信息。
思路:
程序基于示例1的程序进行简单修改即可。
客户端Socket对象的输出流写完数据之后,获得Socket的输入流,执行read()方法,这也是个阻塞式方法,若没读到数据,等待。
服务端读完数据之后,获得socket的输出流,写入反馈的内容即可。

class TcpClient2 
{
    public static void main(String[] args)throws Exception 
    {
        Socket s = new Socket("192.168.1.254",10004);

        OutputStream out = s.getOutputStream();

        out.write("服务端,你好".getBytes());


        InputStream in = s.getInputStream();

        byte[] buf = new byte[1024];

        int len = in.read(buf);

        System.out.println(new String(buf,0,len));

        s.close();
    }
}


class TcpServer2
{
    public static void main(String[] args) throws Exception
    {
        ServerSocket ss = new ServerSocket(10004);

        Socket s = ss.accept();

        String ip = s.getInetAddress().getHostAddress();
        System.out.println(ip+"....connected");
        InputStream in = s.getInputStream();

        byte[] buf = new byte[1024];

        int len = in.read(buf);

        System.out.println(new String(buf,0,len));


        OutputStream out = s.getOutputStream();


        Thread.sleep(10000);
        out.write("哥们收到,你也好".getBytes());

        s.close();

        ss.close();
    }
}

这里写图片描述

TCP练习

要求:
建立一个文本转换服务器。
客户端给服务端发送文本,服务断会将文本转成大写再返回给客户端。
而且客户端可以不断的进行文本转换。当客户端输入over时,转换结束。
分析:
客户端:
既然是操作设备上的数据,那么就可以使用io技术,并按照io的操作规律来思考。
源:键盘录入。
目的:网络设备,网络输出流。
而且操作的是文本数据。可以选择字符流。

步骤
1,建立服务。
2,获取键盘录入。
3,将数据发给服务端。
4,获取服务端返回的大写数据。
5,结束,关资源。

都是文本数据,可以使用字符流进行操作,同时提高效率,加入缓冲。

服务端:
源:socket读取流。
目的:socket输出流。
都是文本,装饰。

import java.io.*;
import java.net.*;

class TranseClient
{
    public static void main(String[] args) throws Exception
    {
        Socket s=new Socket("192.168.1.6",10003);
        BufferedReader bur=new BufferedReader(new InputStreamReader(System.in));
        BufferedWriter buw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
        BufferedReader burIn=new BufferedReader(new InputStreamReader(s.getInputStream())); 
        String line=null;
        while((line=bur.readLine())!=null)
        {
            System.out.println("想要转换的文本为"+line);
            if(line.equals("over"))
                break;
            buw.write(line);
            buw.newLine();
            buw.flush();

            String str=burIn.readLine();
            System.out.println("转换后的文本为"+str);

        }
        bur.close();
        s.close();

    }
}


class TranseServer
{
    public static void main(String[] args) throws Exception
    {
        ServerSocket ss=new ServerSocket(10003);
            Socket s=ss.accept();
            BufferedReader bur=new BufferedReader(new InputStreamReader(s.getInputStream()));
            BufferedWriter buw=new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            String line=null;
            while((line=bur.readLine())!=null)
            {
                System.out.println("要转换的文本为:"+line);
                if(line.equals("over"))
                    break;
                buw.write(line.toUpperCase());
                buw.newLine();
                buw.flush();
            }
            s.close();


           ss.close();

    }
}

这里写图片描述
这个练习容易出现的问题。
现象:客户端和服务端都在莫名的等待。
为什么呢?
因为客户端和服务端都有阻塞式方法。这些方法么没有读到结束标记。那么就一直等
而导致两端,都在等待。
readLine()方法读取一个文本行。通过下列字符之一即可认为某行已终止:换行 (‘\n’)、回车 (‘\r’) 或回车后直接跟着换行,如果没有遇到换行符,那么会一直等待。所以当源字节流是键盘录入时,要在程序中手动写入换行符,或者调用BufferedWriter的newLine()方法。

还有一个地方比较特殊,当客户端close(),相当于在输出流的末尾写入-1,服务端读取到-1,根据程序关闭或者不关闭。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值