网络通信编程

什么是网络编程? 网络编程可以让程序与网络上的其他设备进行数据交换。

常见的通信模式有两种 C/S(Client Server)客户端服务器模式 和B/S(Browswer Server)浏览器服务器模式。

CS在不同的主机上,如果你没有下载该客户端,就不能与服务器端进行数据交换,但是BS只要电脑上有浏览器,就可以直接通过域名访问服务器端。

 

一、 网络通信三要素

网络通信的三要素,IP地址 端口号, 协议

1.IP地址

互联网协议地址,IP地址是分配给上网设备的唯一标识,也就是一个主机对应一个IP地址。

常见的IP地址格式为 IPV4与 IPV6,IPV4分为四字节,每个字节的二进制数据转换成十进制数字就表示该主机在IPV4下的ip地址,因为一个主机对应一个IP地址,因此在IPV4下主机分配的地址可能不够用,因此才有了IPV6,不过IPV6是将二进制数据转换为十六进制。

 

IP地址基本寻路   DNS服务器是安装在我们电脑内部的服务器,用于解析域名,当计算机将访问域名的时候,DNS就会解析成对应的IP地址返回给计算机,计算机再通过该域名对应的IP地址去访问该域名服务器,然后服务器再返回数据给计算机。

IP地址形式

IP地址分为公网地址和私有地址,公网地址就是一些官方的地址,而私有地址就是局域网分配给主机的IP地址。 IP常用命令可以使用操作系统的Command命令窗进行调用。 Ping ip地址命令的作用就是检查你是否与你ping的IP地址的主机网络连通。一般用于局域网

IP地址操作类

InetAddress类,ip地址最顶级类 

当你解析一个域名,返回一个Ip地址的时候,你可以试试改最后的数字看看会不会访问到同一个域名上去,因为相同域名在全国各地可能有很多台服务器,并且不同服务器返回的IP地址肯定是不同的。 

代码: 

package com.itheima.d1_inetAddress;

import java.net.InetAddress;
import java.net.UnknownHostException;

/*
常用API
 */
public class InetAddressDemo01 {
    public static void main(String[] args) throws Exception {
        //1. 获取本机地址对象
        InetAddress ip1 = InetAddress.getLocalHost();
        System.out.println(ip1.getHostName());
        System.out.println(ip1.getHostAddress());
        //2.获取本机域名ip对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostName());
        System.out.println(ip2.getHostAddress());
        //3.获取公网IP对象
        InetAddress ip3= InetAddress.getByName("112.80.248.76");
        System.out.println(ip3.getHostName());
        System.out.println(ip3.getHostAddress());
        //4.判断是否能通 ping 5s 之前测试是否可通
        System.out.println(ip3.isReachable(5000));

    }
}

 

2.端口号

端口是什么? 端口号就是在一个IP地址内标识在设备上运行的进程,被规定为一个16位的二进制数, 范围是0~65535

IP地址就相当于一个酒店的地址,而端口号就相当于房间号,一个酒店内不能有相同的房间号,但是不同酒店中房间号的规定肯定都是一样的,端口号的作用就是进程在主机中的身份证明。 一个设备不能出现两个应用相同的端口号,因为进程在接受信息的时候,如果两个端口号相同,那么会出现端口冲突错误,这个时候你应该关闭另一个进程或者重新申请端口号。

3.协议

连接和通信数据的规则被称为网络通信协议。

网络通信中的两条模型,在生活中都是用的TCP协议,传输层就是选择不同的协议(不同的规则)进行传输,当传输给网络层之后,网络层会将自己的IP地址还有目标的IP地址以及端口的二进制数在网络层封装好传输出去。

 通常使用的都是TCP协议,UDP应用场景多用于语音通话视频等等。

 因为TCP协议的特点就是你发出去的信息请求,服务器端在收到之后会返回一个响应告诉你我接收到了这个请求,之后客户端接收到响应的时候会再次发送确认信息给服务器端是否建立连接,如果客户端没有接收到响应的话他就会再次发出链接,但是这个时候服务器端已经给他发过响应了,因此就知道客户端没有接收到上次响应,这样每当客户端发送连接请求到服务器端,服务器端都会再发一次响应返回,直到客户端接收到响应为止,因此TCP协议连接十分的可靠,是在确认你两边都可以连接的情况下再建立服务器端与客户端的链接。 

UDP协议是一种不可靠无连接的传输协议,因为这个协议不管你目的地是否接到了我发送的请求,他都会将数据给发送出去,基于这种特点,在语音通话和视频通话下应用。

 

 二、UDP通信的实现

UDP协议通信模型可以理解为两个人通过盘子扔韭菜,发送端的人通过盘子扔给接收端,接收端的人通过盘子来接到发送端的韭菜(传输的数据),但是他扔出去的韭菜可能扔到别的地方,因此这种通信方式是不可靠的。         

 UDP通信模拟中需要用到的对象以及方法

 可以看到发送端和接收端的对象都是DatagramSocket类的,该类有两个构造器,第一个构造器不填写端口号参数,这个时候会系统会随机分配一个端口号给该对象,第二个构造器是填写端口号参数,服务器端在创建对象的时候必须要设置端口号,不然发送端无法发送到该服务器端。

 实现思想:首先客户端先创建一个Socket(人)的对象,创建一个数据包(盘子),来封装数据,因为计算机之间都是通过二进制来传递数据的,因此需要将数据转换成字节存入,通过字节数组将数据封装在字节数组中,然后将填入参数,数据,数据大小,服务器地址,端口号。之后就可以通过send方法将数据(韭菜)发送给指定IP地址和端口的进程,服务器端在接受的时候同样的步骤创建对象和创建字节数组用来接受数据,服务器端的数据包参数只需要填写数据内容以及大小即可,Socket对象使用recieve方法接受数据包,通过方法Packet方法getlength获取数据大小,然后就可以实现有多少取多少了。 注意:服务器端必须比客户端先启动,不然客户端先发送数据的话,服务器端就会接收不到。

代码“:

客户端

package com.itheima.d2_UDp1;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class ClientDemo1 {
    public static void main(String[] args) throws  Exception {
        System.out.println("==========客户端启动==========");

        //创建发送端对象,发送自带默认的端口号(人)
        DatagramSocket socket = new DatagramSocket();

        // 创建数据包对象用来封装数据 (盘子)
        /*
           public DatagramPacket(byte buf[], int length,
                          InetAddress address, int port) {
                      参数一:疯转发送的数据
                      参数二: 分装数据的大小
                      参数三: 服务器的主机IP地址
                      参数四: 服务器端口号
         */
        byte[] buffer = "我是一个快乐的小韭菜,嗯造韭菜".getBytes();
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length,
                InetAddress.getLocalHost(),8888); //因为是自己主机上测试的所以第三个参数也是自己的IP地址

        socket.send(packet);

        socket.close();
    }
}

        

package com.itheima.d2_UDp1;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class ServerDemo2 {
    /*
    模拟服务器端
     */
    public static void main(String[] args) throws Exception {
        System.out.println("=======服务器端启动========");
        //1. 创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);

        //2.创建数据包对象接收数据 (盘子)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length); //只需要接收数据即可

        //等待接收数据
        socket.receive(packet);

        //展示数据信息
        int len = packet.getLength();
        //读多少到多少
        String str = new String(buffer,0,len);
        System.out.println("接收成功"+ str);

        socket.close();

    }
}

 

总结:

 多发多收

在平时的视频软件中的弹幕原理就是多发多收的原理实现的,在上面我们实现了一个客户端发给服务器端的案例,那么多个客户端同时给服务器端怎么实现呢,其实只需要多加个while循环即可,并且在服务器端中的循环中不可以释放资源。

代码:

package com.itheima.d2_UDp1;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class ServerDemo2 {
    /*
    模拟服务器端
     */
    public static void main(String[] args) throws Exception {
        System.out.println("=======服务器端启动========");
        //1. 创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);

        //2.创建数据包对象接收数据 (盘子)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length); //只需要接收数据即可

        //等待接收数据
        socket.receive(packet);

        //展示数据信息
        int len = packet.getLength();
        //读多少到多少
        String str = new String(buffer,0,len);
        System.out.println("接收成功"+ str);

        socket.close();

    }
}
package com.itheima.d3_Udp2.d2_UDp1;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ServerDemo2 {
    /*
    模拟服务器端 多收
     */
    public static void main(String[] args) throws Exception {
        System.out.println("=======服务器端启动========");
        //1. 创建接收端对象:注册端口(人)
        DatagramSocket socket = new DatagramSocket(8888);

        //2.创建数据包对象接收数据 (盘子)
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer,buffer.length); //只需要接收数据即可

        while (true) {
            //等待接收数据
            socket.receive(packet);

            //展示数据信息
            int len = packet.getLength();
            //读多少到多少
            String str = new String(buffer,0,len);
            System.out.println("收到了来自:"+packet.getAddress() +"对方端口号是" +packet.getPort() +  "的消息" + str);


        }

    }
}

 

广播和组播 

 组播与广播的原理:

如何实现组播与广播

 TCP通信快速入门

1.单发单收

Tcp通信方式是通过Socket类配合IO流来进行操作的,Socket就相当于客户端与服务器端之间的一根管道,当你接通了这根管道之后,就可以通过IO流操作来进行通信。其中如果其中一方Socket管道出现异常,另一方的Socket也会异常。

 Socket创建对象的方式   构造器中的参数为服务器的ip和端口

客户端发送消息步骤:在客户端内创建该对象,就相当于联通了与所填写参数中的服务器端的管道,因为高级流中打印流输出文本信息非常方便,因此想通过打印流来发送信息,获取到输出字节流对象之后,直接包装成打印流,通过println发送信息

代码:

package com.itheima.d4_TCP1;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;

public class ClientDemo1 {
    public static void main(String[] args) throws Exception {
        //创建Socket通信管道请求有服务器的链接
        Socket sk  = new Socket("127.0.0.1",7777);

        //从socket通信管道中得到一个字节输出流,负责发送数据
        OutputStream os = sk.getOutputStream();

        //要输出文本,因此用高级打印流
        PrintStream ps = new PrintStream(os);
        ps.print("我是TCP的客户端,我已经与你对接,约吗?");
        ps.flush();

        //关闭资源
        sk.close();
    }
}
package com.itheima.d4_TCP1;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        try {
            //注册端口
            ServerSocket server = new ServerSocket(7777);
            //必须调用accpet方法 等待接收客户端的Socket链接请求
            Socket socket = server.accept();
            //从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //将字节流转换成字符流
            //把字节输入流包装成缓冲字符输入流    因为要接收文本信息
            BufferedReader bf = new BufferedReader(new InputStreamReader(is));
            //按行读取信息
            String msg;
            if ((msg = bf.readLine())!= null)
            {
                System.out.println(socket.getRemoteSocketAddress() + "说了信息:" + msg);
            }


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

ServerSocket类是服务端的实现类,服务器端接收客户端发送的信息。步骤:需要注册端口,客户端中的端口需与服务器端中的端口保持一致,调用accpet方法接收客户端的通信连接,连接成功后返回Socket管道与客户端建立通信,之后通过socket管道中的方法来获取字节输入流,读取文本信息最好的流是缓冲字符输入流,先将字节输入流通过转换流转换成字符流再将其包装就可以了,之后进行读操作在服务器端上。

 代码:

package com.itheima.d5_TCP2.d4_TCP1;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class ClientDemo1 {
    //实现多发多收
        public static void main(String[] args) throws Exception {
        //创建Socket通信管道请求有服务器的链接
        Socket sk  = new Socket("127.0.0.1",7777);

        //从socket通信管道中得到一个字节输出流,负责发送数据
        OutputStream os = sk.getOutputStream();

        //要输出文本,因此用高级打印流
        PrintStream ps = new PrintStream(os);
        Scanner sc = new Scanner(System.in);
        String msg ;
            while (true) {
                System.out.println("请输入信息:");
                  msg = sc.nextLine();
                ps.println("我是TCP的客户端:"+ msg);
                ps.flush();

            }

            //关闭资源
//        sk.close();
    }
}
package com.itheima.d5_TCP2.d4_TCP1;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        try {
            //注册端口
            ServerSocket server = new ServerSocket(7777);
            //必须调用accpet方法 等待接收客户端的Socket链接请求
            Socket socket = server.accept();
            //从socket通信管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //将字节流转换成字符流
            //把字节输入流包装成缓冲字符输入流    因为要接收文本信息
            BufferedReader bf = new BufferedReader(new InputStreamReader(is));
            //按行读取信息
            String msg;
            //多个客户端不能并发执行
            while ((msg = bf.readLine())!= null)
            {
                System.out.println(socket.getRemoteSocketAddress() + "说了信息:" + msg);
            }


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

 

 

多发多收:因为是按行接收文本信息,因此在打印的时候也需要进行换行

代码:

2.同时接收多个客户端

虽然可以实现一个管道的多发多收,但是如果多个客户端同时并发执行,客户端只能接收到第一个客户端发来的信息,因为在服务器端口中,只有一个主线程,而此时主线程一直在接收第一个客户端发来的信息,只要第一个Socket不退出,那么后面的Socket发消息客户端就永远收不到,这个时候就引入了多线程技术,通过主线程死循环来接收Socket管道,循环内部创建新的子线程来处理新的Socket客户端信息,这样就解决了并发问题。

 代码:

package com.itheima.d6_TCP3;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class ClientDemo1 {
    //实现多发多收
        public static void main(String[] args) throws Exception {
            System.out.println("====客户端启动成功=====");
        //创建Socket通信管道请求有服务器的链接
        Socket sk  = new Socket("127.0.0.1",7777);

        //从socket通信管道中得到一个字节输出流,负责发送数据
        OutputStream os = sk.getOutputStream();

        //要输出文本,因此用高级打印流
        PrintStream ps = new PrintStream(os);
        Scanner sc = new Scanner(System.in);
        String msg ;
            while (true) {
                System.out.println("请输入信息:");
                  msg = sc.nextLine();
                ps.println("我是TCP的客户端:"+ msg);
                ps.flush();

            }

            //关闭资源
//        sk.close();
    }
}
package com.itheima.d6_TCP3;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

public class MyThread extends Thread{
    //接收Socket对象
    private Socket socket ;
    public MyThread(Socket socket)
    {
        this.socket = socket;
    }
    @Override
    public void run()
    {
        //从socket通信管道中得到一个字节输入流

        try {
            InputStream  is = socket.getInputStream();
            //将字节流转换成字符流
            //把字节输入流包装成缓冲字符输入流    因为要接收文本信息
            BufferedReader bf = new BufferedReader(new InputStreamReader(is));
            //按行读取信息
            String msg;
            //多个客户端不能并发执行
            while ((msg = bf.readLine())!= null)
            {
                System.out.println(socket.getRemoteSocketAddress() + "说了信息:" + msg);
            }
        } catch (IOException e) {
            //当Socket管道关闭后线程中的客户端管道也会跟着关闭
            System.out.println(socket.getRemoteSocketAddress() + "下线了");
        }

    }
}
package com.itheima.d6_TCP3;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

/*
实现多个客户端向服务器发送消息
 */
public class ServerDemo2 {
    public static void main(String[] args) throws Exception {
        try {
            //注册端口
            ServerSocket server = new ServerSocket(7777);
            //必须调用accpet方法 等待接收客户端的Socket链接请求
             //主线程要不断的接收不客户端传来的SOc'ket
            while (true)
            {
                Socket socket = server.accept();    //因为他一个主线程只能处理一个管道,只要这个线程不结束,那么后面再申请链接的管道就不能链接
                System.out.println(socket.getRemoteSocketAddress() + "上线了!");
                //交给子线程处理
                new MyThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

但是这种方式也有个弊端,就是一个客户端对应一个子线程,因此如果客户端数量过多的话,内存中的线程也会过多,就会导致内存溢出

线程池优化客户端并发

 为了既能解决客户端并发执行问题又能解决内存问题,在这里就可以使用到线程池技术来操作TCP通信,在线程池技术中,主线程通过循环来接收多个客户端Socket,因为线程池只需要一个因此创建一个静态线程池在服务端中,此时客户端中的实现了Runnable接口的任务进入任务队列中等待线程的调度,如果核心线程足够的话,核心线程就会先调度队列中的任务连接服务端与客户端之间的通信,当核心线程用完并且都在占用中的时候,客户端就会进入等待队列中,当等待队列满了的时候,再进入客户端,这个时候线程池会创建临时线程来处理最新来的客户端,而在等待队列中的客户端由于没有被唤醒,没有线程对其进行调度,所以临时临时线程会调度最新的客户端与服务端连接通信,当临时线程数也不够的时候,线程池就会进行拒绝策略,将后来的客户端阻挡在外。

代码:

package com.itheima.d7_TCP4;

import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class ClientDemo1 {
    //实现多发多收
        public static void main(String[] args) throws Exception {
        //创建Socket通信管道请求有服务器的链接
        Socket sk  = new Socket("127.0.0.1",7777);
            System.out.println("客户端启动了");

        //从socket通信管道中得到一个字节输出流,负责发送数据
        OutputStream os = sk.getOutputStream();

        //要输出文本,因此用高级打印流
        PrintStream ps = new PrintStream(os);
        Scanner sc = new Scanner(System.in);
        String msg ;
            while (true) {
                System.out.println("请输入信息:");
                  msg = sc.nextLine();
                ps.println("我是TCP的客户端:"+ msg);
                ps.flush();

            }

            //关闭资源
//        sk.close();
    }
}
package com.itheima.d7_TCP4;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;

public class MyRunnable implements Runnable{
    private Socket socket;
    public MyRunnable(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            //将字节流转换成字符流
            //把字节输入流包装成缓冲字符输入流    因为要接收文本信息
            BufferedReader bf = new BufferedReader(new InputStreamReader(is));
            //按行读取信息
            String msg;
            //多个客户端不能并发执行
            while ((msg = bf.readLine())!= null)
            {
                System.out.println(socket.getRemoteSocketAddress() + "说了信息:" + msg);
            }
        } catch (IOException e) {
            //当Socket管道关闭后线程中的客户端管道也会跟着关闭
            System.out.println(socket.getRemoteSocketAddress() + "下线了");
        }

    }
    }

 

package com.itheima.d7_TCP4;

import com.itheima.d6_TCP3.MyThread;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.*;

public class ServerDemo2 {
    /*
         public ThreadPoolExecutor(int corePoolSize,   核心线程数
                           int maximumPoolSize,     指定线程池最大线程数(核心线程数与临时线程数总和)
                           long keepAliveTime,      指定临时线程最大存活时间
                           TimeUnit unit,           最大时间规定单位是 秒 分钟 小时等等...
                           BlockingQueue<Runnable> workQueue,   任务队列最大数 任务队列是实现了 Runnable和Callable接口
                           ThreadFactory threadFactory,       线程工厂(用于创建线程)里面封装的是创建线程的三种方法
                           RejectedExecutionHandler handler)   如果任务队列满了,新任务来了的话需要去拒绝
                            核心线程和临时线程都在忙 任务队列也满了,新的任务过来的时候才会开始任务拒绝
      */
    public static ExecutorService pool = new ThreadPoolExecutor(3,5,5,
            TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
    public static void main(String[] args) throws Exception {
        try {
            //注册端口
            ServerSocket server = new ServerSocket(6666);
            //必须调用accpet方法 等待接收客户端的Socket链接请求
            //主线程要不断的接收不客户端传来的SOc'ket
            while (true)
            {
                Socket socket = server.accept();    //因为他一个主线程只能处理一个管道,只要这个线程不结束,那么后面再申请链接的管道就不能链接
                System.out.println(socket.getRemoteSocketAddress() + "上线了!");
                pool.execute(new MyRunnable(socket));
            }


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

 

 即时通信

即使通信技术就相当于群聊,当一个客户端发送数据的时候,在服务器中的客户端Socket包括服务器端都会接收到该客户端发出的数据,这就用到了端口转发的设计思想,何为端口转发? 就是当其中一个客户端通过子线程与服务器端连接到之后向服务器端发送数据,这个时候因为服务器端中的客户端是通过服务器端口,此时服务器端口就会转发该信息到相连接的客户端中。 那么在上个基础上怎么实现这个操作呢,这个时候客户端就除了输出信息,也需要接收信息,并且在服务器端中需要创建一个集合来存储服务器端中的管道,然后当服务器端接收到客户端发送端的消息时,就会将该消息通过集合遍历输出回其余客户端中,这样就实现了端口转发的功能。

package com.itheima.d8_TCP5;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

public class ClientDemo1 {
    //实现多发多收
        public static void main(String[] args) throws Exception {
            //创建Socket通信管道请求有服务器的链接
            Socket sk = new Socket("127.0.0.1", 7777);

            //客户端要接收信息, 因此需要多加yi一个线程
            new ClientReaderThread(sk).start();

            //从socket通信管道中得到一个字节输出流,负责发送数据
            OutputStream os = sk.getOutputStream();

            // 要输出文本,因此用高级打印流
            PrintStream ps = new PrintStream(os);
            Scanner sc = new Scanner(System.in);
            String msg;
            while (true) {
                System.out.println("请输入信息:");
                msg = sc.nextLine();
                ps.println("我是TCP的客户端:" + msg);
                ps.flush();

            }

            //关闭资源
//        sk.close();
        }
}
class ClientReaderThread extends Thread{

    private Socket socket;
    public ClientReaderThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            //将字节流转换成字符流
            //把字节输入流包装成缓冲字符输入流    因为要接收文本信息
            BufferedReader bf = new BufferedReader(new InputStreamReader(is));
            //按行读取信息
            String msg;
            //多个客户端不能并发执行
            while ((msg = bf.readLine())!= null)
            {
                System.out.println( "收到消息:" + msg);
            }
        } catch (IOException e) {
            //当Socket管道关闭后线程中的客户端管道也会跟着关闭
            System.out.println("服务器把你踢出去了");
        }

    }
}
package com.itheima.d8_TCP5;

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

public class MyRunnable implements Runnable{
    private Socket socket;
    public MyRunnable() {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream is = socket.getInputStream();
            //将字节流转换成字符流
            //把字节输入流包装成缓冲字符输入流    因为要接收文本信息
            BufferedReader bf = new BufferedReader(new InputStreamReader(is));
            //按行读取信息
            String msg;
            while ((msg = bf.readLine())!= null)
            {
                System.out.println(socket.getRemoteSocketAddress() + "说了信息:" + msg);
                //把这个消息转发给全部客户端socket管道
                sendMsgToAll(msg);
            }
        } catch (IOException e) {
            //当Socket管道关闭后线程中的客户端管道也会跟着关闭
            System.out.println(socket.getRemoteSocketAddress() + "下线了");
            ServerDemo2.all_Soket.remove(socket);
        }

    }

    private void sendMsgToAll(String msg) {
        for (Socket socket : ServerDemo2.all_Soket) {
            try {
                PrintStream ps = new PrintStream(socket.getOutputStream());
                ps.println(msg);
                ps.flush();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 

package com.itheima.d8_TCP5;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

public class ServerDemo2 {
    //创建集合存储所有管道
    static List<Socket> all_Soket = new ArrayList<>();

    public static ExecutorService pool = new ThreadPoolExecutor(3,5,5,
            TimeUnit.SECONDS,new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy());
    public static void main(String[] args) throws Exception {
        try {
            //注册端口
            ServerSocket server = new ServerSocket(7777);
            //必须调用accpet方法 等待接收客户端的Socket链接请求
            //主线程要不断的接收不客户端传来的SOc'ket
            while (true)
            {
                Socket socket = server.accept();    //因为他一个主线程只能处理一个管道,只要这个线程不结束,那么后面再申请链接的管道就不能链接
                System.out.println(socket.getRemoteSocketAddress() + "上线了!");
                all_Soket.add(socket);
                pool.execute(new MyRunnable());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

B/S架构TCP通信

 B/S开发通信原理   浏览器通过域名访问服务端浏览器Socket连接到服务器端,通过Socket发送的值,服务器端返回页面信息。

HTTP协议格式的数据

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值