22-05-05 西安 javaSE(18) 网络编程、TCP\IP、Socket通信

网络编程

网络编程要素

网络编程:在网络间完成数据的传输,相比于IO流在本地完成数据的传输

网络编程要素

  1. 如何准确地定位网络上一台或多台主机【ip】
  2. 找到主机后如何可靠高效地进行数据传输。【协议】

java.net.InetAddress  

  • InetAddress.getLocalHost() 获取本地ip对应的InetAddress实例
  • String getHostAddress() 返回该InetAddress实例对应的ip地址字符串
//获取本机ip
public static void main(String[] args) {
    try {
        //打印流 打印本机ip
        System.out.println(InetAddress.getLocalHost().getHostAddress());
    } catch (UnknownHostException e) {
        e.printStackTrace();
    }
}

唯一标识 Internet 上的计算机,InetAddress类主要表示IP地址

  • InetAddress.getByName() 根据主机名获取对应的InetAddress实例
  • String getHostAddress() 获取InetAddress实例对应的ip地址字符串
public static void main(String[] args) throws UnknownHostException {
    //根据主机名获取对应的InetAddress实例
    InetAddress inetAddress = InetAddress.getByName("www.baidu.com");
    //获取InetAddress实例域名
    System.out.println(inetAddress.getHostName());
    //获取InetAddress实例ip地址
    System.out.println(inetAddress.getHostAddress());
    //获取InetAddress实例ip地址的全限定域名
    System.out.println(inetAddress.getCanonicalHostName());
}

 ip地址的全限定域名??


DatagramSocket与DatagramPacket

java.net.DatagramPacket
系统不保证UDP数据报一定能够安全送到目的地,也不能确定什么时候可以抵达。

public class FileTest {
    //UDP发送端
    @Test
    public void client() throws IOException {
        //使用UDP协议传输
        DatagramSocket datagramSocket = new DatagramSocket();
        byte[] bytes="我在精神病院学斩神".getBytes(StandardCharsets.UTF_8);
        //DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port)
        //构造数据报包,用来将长度为 length 偏移量为 offset 的包发送到指定主机上的指定端口号。
        DatagramPacket p=new DatagramPacket(bytes,0,bytes.length, InetAddress.getByName("127.0.0.1"),9898);
        datagramSocket.send(p);
        datagramSocket.close();
    }

    //UDP接收端
    @Test
    public void server() throws IOException {
        //使用UDP协议传输
        DatagramSocket datagramSocket = new DatagramSocket(9898);
        byte[] bytes=new byte[1024];
        //DatagramPacket(byte[] buf, int length)
        //构造 DatagramPacket,用来接收长度为 length 的数据包。
        DatagramPacket p=new DatagramPacket(bytes,bytes.length);
        datagramSocket.receive(p);
        System.out.println(new String(p.getData(),0,p.getData().length));
        datagramSocket.close();
    }
}

java.net.URL

URL(Uniform Resource Locator):统一资源定位符,它表示 Internet 上某一资源的地址。

 URL的基本结构由5部分组成:
<传输协议>://<主机名>:<端口号>/<文件名>
例如: http://192.168.1.100:8080/helloworld/index.jsp

构造器:
URL(String spec) 
          根据 String 表示形式创建 URL 对象。
方法:
public final InputStream openStream() throws IOException
打开到此 URL 的连接并返回一个用于从该连接读入的 InputStream。


此方法是下面方法的缩写: openConnection().getInputStream()

public URLConnection openConnection() throws IOException
返回一个 URLConnection 对象,它表示到 URL 所引用的远程对象的连接。

//获取整个url结构
一个URL对象生成后,其属性是不能被改变的,但可以通过它给定的方法来获取这些属性

public String getProtocol(  )  ;//URL的协议名
public String getHost(  )  ;//该URL的主机名
public String getPort(  ) ;// 获取该URL的端口号
public String getPath(  )           获取该URL的文件路径
public String getFile(  )             获取该URL的文件名
public String getRef(  )             获取该URL在文件中的相对位置
public String getQuery(   )        获取该URL的查询名
    @Test
    public void test03() throws Exception {
//      通过一个表示URL地址的字符串可以构造一个URL对象
        URL url = new URL("http://192.168.15.55/atguigu/hello.txt");
//      InputStream in = url.openStream(); //openStream():能从网络上读取数据,获取指定站点的资源
        URLConnection connection = url.openConnection(); //不仅可以获取指定站点资源,还可以发送资源到指定站点
        InputStream in = connection.getInputStream();
        byte[] b = new byte[1024];
        int len = 0;
        while((len = in.read(b)) != -1){
            System.out.println(new String(b, 0, len));
        }
        in.close();
    }

HttpClient

HttpClient HttpClient 是 Apache Jakarta Common 下的子项目,用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包。

HttpClient基本上是java所有网络请求发起的底层的框架,比如说微服务之间的远程调用、阿里云oss、发送短信。。。

1、执行HTTP方法
 //1、创建对象
 HttpClient httpClient = new DefaultHttpClient();
 //2.创建请求配置:请求方式+地址+参数配置
 HttpGet httpGet = new HttpGet("http://www.baidu.com");
 //3.发送请求
 HttpResponse response = httpClient.execute(httpGet);
 //4.解析结果
 System.out.println(response.getStatusLine().getStatusCode());
 String jsonString = EntityUtils.toString(response.getEntity(), "UTF-8");
 System.out.println(jsonString);

Socket和ServerSocket

通信基本概念

短连接:socket连接,发送数据,接收数据后马上断开

长连接:建立socket连接后,不管是否使用,保持连接

半包:接收方没有接收到一个完整的包,只接收到包的一部分

粘包:发送方发送的多个包数据到接收方接收时粘成一个包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

分包:出现粘包情况下,需要分包处理或一个数据包被分成多次接收。


Socket通信步骤

不考虑本地的进程间通信。问网络中进程之间如何通信?

1.在本地可以通过进程PID来唯一标识一个进程

2.端口标识了应用层中不同的进程(ip地址,协议,端口)唯一标识网络进程

端口号与IP地址的组合得出一个网络套接字。套接字允许应用程序将 I/O 应用于网络中,并与其他应用程序进行通信

  1. 通信的两端都要有Socket,是两台机器间通信的端点,网络通信其实就是Socket间的通信。
  2. Socket允许程序把网络连接当成一个流,数据在两个Socket间通过IO传输。
  3. 客户端和服务器现在可以通过Socket对象的写入和读取来进行进行通信

端口号标识正在计算机上运行的进程, 被规定为一个 16 位的整数 0~65535。

其中,0~1023被预先定义的服务通信占用(如MySql占用端口3306,http占用端口80等)。除非我们需要访问这些特定服务,否则,就应该使用 1024~65535 这些端口中的某一个进行通信,以免发生端口冲突。 

通信步骤

建立连接

1、服务器端先初始化Socket(ServerSocket),然后与端口绑定,对端口进行监听(listen),调用accept阻塞,等待客户端连接

2、客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。

数据交互

客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束


java.net.Socket

客户端 要获取一个Socket对象通过实例化 

setSoTimeout public void setSoTimeout(int timeout) throws SocketException

这个用来设置与socket的inputStream相关的read操作阻塞的等待时间,以毫秒为单位。超过设置的时间了,假如还是阻塞状态,会抛出异常java.net.SocketTimeoutException: Read timed out

这里的阻塞不是指read的时间长短,可以理解为没有数据可读,线程一直在这等待

//客户端
public class ClientDemo {
    public static void main(String[] args) throws Exception {

        String str = "《我在精神病院斩神》";
        //指定要连接的 IP地址和端口号
        Socket s = new Socket("127.0.0.1", 1314);
        //读取数据时阻塞链路的超时时间
        s.setSoTimeout(5000);//以毫秒为单位

        //发送数据给服务端
        OutputStream os = s.getOutputStream();
        os.write(str.getBytes());
        //发送完毕 禁用此套接字的输出流
        s.shutdownOutput();

        //接收服务端的反馈
        InputStream in = s.getInputStream();
        byte[] b = new byte[1024];
        int len = 0;
        while ((len = in.read(b)) != -1) {
            System.out.println(new String(b, 0, len));
        }
        in.close();
        os.close();
        s.close();
    }
}

Socket 相关API

打开连接到 Socket 的输入/出流

获取与Socket相关联的字节输入流,用于从Socket中读数据。

InputStream getInputStream()  throws IOException

-------------------
获取与Socket相关联的字节输出流,用于向Socket中写数据。

OutputStream getOutputStream()  throws IOException

-------------

关闭监听Socket

void close()throws IOException   

isBound(); 如果Socket已经与本地一个端口绑定,返回true;否则返回false

isClosed(); //连接是否已关闭,若关闭,返回true;否则返回false

isConnected;//曾经是否连接成功过。

要判断当前的Socket对象是否处于连接状态

必须同时使用isClose和isConnected方法, 即只有当isClosed返回false,isConnected返回true的时候Socket对象才处于连接状态。


java.net.ServerSocket

  • ServerSocket(int port) throws IOException
  • ServerSocket(int port, int backlog) throws IOException
  • ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException

参数port指定服务器要绑定的端口(服务器要监听的端口),参数backlog指定客户连接请求队列的长度,参数bindAddr指定服务器要绑定的IP地址。

Socket accept() throws IOException     
等待客户端的连接请求,返回与该客户端进行通信用的Socket对象

服务器获得一个Socket对象则通过accept()方法的返回值

ServerSocket的accept()方法从连接请求队列中取出一个客户的连接请求,然后创建与客户连接的Socket对象,并将它返回。如果队列中没有连接请求,accept()方法就会一直等待,直到接收到了连接请求才返回。

1.服务器中有一个accept()方法创建与客户连接的 Socket 对象[socket],而在没有客户机连接的时候,此方法accept会一直阻塞,所以将其放在一个线程类[ServerListener ]中运行较为合适

服务器从该[socket] 对象中获得输入流和输出流, 就能与客户交换数据

public class ServerListener extends Thread {
 
    private int port = 8888;
    private ServerSocket serverSocket;
    public static ArrayList<ServerThread> serverThreads = new ArrayList<>();
    static boolean stop = false;
 
    public ServerListener() throws IOException {
        serverSocket = new ServerSocket(port);
        System.out.println("服务器启动,端口号为:" + port);
    }
 
    @Override
    public void run() {
        //每一个客户机对象都有一个线程来处理
        while (true) {
            if (stop) {
                //关闭资源,跳出循环
                break;
            } else {
                try {
                    //该accept()方法将一直等待,直到客户端连接到服务器上给定的端口。
                    Socket socket = serverSocket.accept();
                    if (!stop) {
                        //正常处理
                        System.out.println("有一个客户机上线了");
                        ServerThread st = new ServerThread(socket);
                        serverThreads.add(st);
                        st.start();
                    } else {
                        this.serverSocket.close();
                        break;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
 
    }
 
    //关闭serverSocket的方法
    public static void closeClient() {
        stop = true;
        try {
            //与本serverSocket建立链接,建立之后立即关闭。这样会打破了accept方法阻塞状态
            new Socket("127.0.0.1", 8888).close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.服务器每连接上一个客户端的时候我们都去开启一个线程[ServerThread ],在新开的线程去接收输入输出流,处理输入输出。

socket通信俩端都是socket,不同的是

客户端自己new  socket(),new的时候指定ip和端口号来确定去找哪一个socket跟它通信。

服务端,是早早开启socketServer使用accept()监听端口,来一个客户端socket再去创建一个服务端socket去通信,此时俩个socket就都建立起来了

可笑我以前还一直以为ServerSocket就是服务端创建的socket呢,,,一直误会

public class ServerThread extends Thread {
    private Socket serverScoket;
    private OutputStream ops;
    private InputStream ips;
    boolean isAck = false;
    boolean isOver = false;

    public ServerThread(Socket serverScoket) {
        this.serverScoket = serverScoket;
        try {
            ops = serverScoket.getOutputStream();
            ips = serverScoket.getInputStream();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //读取从客户机发来的消息
    public void readMessage() {
        try {
            byte[] bytes = new byte[1024];
            //循环读取客户端数据到bytes数组
            while (true) {
                int len = 0;
                while ((len = ips.read(bytes)) != -1) {
                    String str = new String(bytes, 0, len);
                    System.out.println("服务器接收客户机消息成功——>" + str);
                    //客户机确认无误,开始通信
                    if (str.equals("client-ack")) {
                        isAck = true;
                    }
                    //客户端发起关闭连接请求
                    if (str.equals("client-over")) {
                        isOver = true;
                        break;//跳出内循环
                    }
                }
                if (isOver) {
                    serverScoket.close();//关闭socket
                    ServerListener.closeserverScoket();//关闭serverSocket
                    break;
                }
            }
        } catch (IOException e) {
            System.out.println("服务器读取消息失败");
            e.printStackTrace();
        }
    }


    //给客户机发送消息
    public void sendMessage(String msa) {
        if (!isAck) {//未连接客户端,不允许通信
            return;
        }
        try {
            ops.write(msa.getBytes("GBK"));
            ops.flush();
            System.out.println("服务器发送消息成功:" + msa);
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("服务器发送消息失败");
        }
    }

    //消息随时会有,用线程来执行更为合适
    public void run() {
        System.out.println("线程信息 id=" + Thread.currentThread().getId() + "名字=" + Thread.currentThread().getName());
        try {
            //给客户端发送确认信息
            ops.write("server-ack".getBytes("GBK"));
            ops.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        readMessage();
    }
}

单独启动客户机

=========================

启动服务器--用telnet命令当客户机

 红色方块一直亮着。等待客户机连接,并且主线程没有被accept阻塞住


ServerSocket属性和方法

SO_TIMEOUT表示ServerSocket的accept()方法等待客户连接的超时时间,以毫秒为单位。 如果SO_TIMEOUT的值为0,表示永远不会超时,这是SO_TIMEOUT的默认值。

设置该选项:public void setSoTimeout(int timeout) throws SocketException
读取该选项:public int getSoTimeout () throws IOException

写入网络数据时,调用flush()方法。

如果不调用flush(),我们很可能会发现,客户端和服务器都收不到数据,这并不是Java标准库的设计问题,而是我们以流的形式写入数据的时候,并不是一写入就立刻发送到网络,而是先写入内存缓冲区,直到缓冲区满了以后,才会一次性真正发送到网络,这样设计的目的是为了提高传输效率。如果缓冲区的数据很少,而我们又想强制把这些数据发送到网络,就必须调用flush()强制把缓冲区数据发送出去。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值