网络编程UDP+TCP

网络编程UDP+TCP_第1张图片

日升时奋斗,日落时自省 

目录

1、网络编程基本概念

2、UDP数据报套接字编程

2.1、UDP相关API

2.1.1、DatagramSocket API

2.1.2、DatagramPacket API

 2.2、UDP版本服务器

 2.3、UDP版本客户端

 2.4、UDP连接操作

2.5、翻译业务

2.6、总结

 3、TCP流套接字编程

3.1、TCP相关API

3.2、TCP版本服务器

 3.3、TCP版本的客户端

 3.4、TCP连接操作

3.4.1多线程TCP服务器

3.4.2、线程池TCP服务器


1、网络编程基本概念

网络编程指的是网络上的主机,通过不同的进程,以编程的方式进行实现网络通信

详细下来就是我们只要满足进程不同就行;所以即使是同一个主机,只要是不同进程,基于网络来传输数据,也属于网络编程

(1)发送端和接收端

发送端:数据的发送方进程 ,称为发送端,发送端就是网络通信的源主机

接收端:数据的接收方进程, 称为接收端,接收端就是网络通信中的目的主机

注:发送端和接收端只是相对的。

(2)请求和响应

一般情况,获取一个网络资源,涉及到两次网络数据传输

首先: 请求数据发送

然后:响应数据发送

例如 :餐馆买饭,客人发出请求(我要吃什么),餐馆针对请求做出处理(后厨颠勺中),最后做好针对客人进行响应(做好了给你)

网络编程UDP+TCP_第2张图片

 (3)客户端和服务端

服务端:在常见的网络数据传输场景下,把提供服务的一方进程,称为服务端,可以提供对外服务

客户端:获取服务的一方进程,称为客户端

注:我们常说的客户端(发送端)和服务器(接收端),但是理解不单就是客户与服务器交互,如果是服务器与服务器交互谁又是客户端(发送端)其实就是看谁给谁发送请求,发送请求的就是发送端,接收请求的就是接收端

2、UDP数据报套接字编程

2.1、UDP相关API

基于UDP来编写一个简单的客户端服务器进行网络通信程序;需要有一定的网络基础

首先就是要认识进行网络编程要使用的API

核心内容 Socket API,操作系统给应用程序提供的网络编程API;Socket这里翻译成套接字,它是计算机之间进行通信的一种约定或一种方式;虽然socket英译为“插座”,但是在计算机领域里它叫“套接字”。

可以认为socket API是和传输层密切相关的,传输层提供了两个最核心的协议UDP和TCP所以socket也提供了两种风格的API

DatagramSocket使用这个类,表示一个socket对象,代表socket文件;原因:在操作系统中,把这个socket对象也是当成了一个文件来处理,相当于是文件描述符表上的一项,提到这里就会想到普通文件也就是对应的硬件设备(硬盘),socket文件,对应的硬件设备(网卡)一切都是文件而起。

一个socket对象,就可以和另外一台主机进行通信,如果要和多个不同的主机通信,需要创建多个socket对象。

2.1.1、DatagramSocket API

DatagramSocket是UDP Socket用于发送和接收UDP数据报 (这里稍微有点绕口,多看两遍)

这个绕口的话是解释:DatagramSocket与下面马上要提到DatagramPacket对比理解,实际意思就是DatagramSocket里面是数据报作为参数也就是DatagramPacket类的(它就是作参数的)

(1)无参数版本的构造方法,没有指定端口,系统会自动分配一个空闲的端口

 (2)一个参数版本的构造方法,有一个端口号,此时就是为了socket对象能和指定的端口号(简单的数字范围在1024-65535之间),关联起来;我们知道端口号是和进程相关连的,为了确定是哪个进程,本质上不是进程和端口号建立联系,而是进程中的socket对象和端口建立了联系

网络编程UDP+TCP_第3张图片

(3)需要了解的方法 一个receive(接收)另一个是send(发送)

 receive方法此处有一个DatagramPacket的参数,但是这里填的相当于是一个空对象,receive方法内部会对参数的这个空对象进行内容填充(将接收来的数据填充),从而构造出结果数据,参数也是一个“输出型参数”  如果没有接收到数据 会进行阻塞等待

send方法也有一个DatagramPacket参数,这里的参数就有数据了,从此套接字发送数据报

 最后一个使用需用方法就是close 关闭此数据报套接字

2.1.2、DatagramPacket API

DatagramPacket是UDP socket发送和接收的数据报

DatagramPacket表示UDP中传输的一个报文,构造这个对象可以指定一些具体的数据进去,这些数据后面实现UDP的时候会展示

(1)2个参数版本的构造方法

把buf这个缓冲区给设置进去,数据存储先放在缓存区上,既然是文件就需要涉及到文件操作,当然内部会自己处理,我们直接使用网络编程相关API就可以了(最后一个参数是缓冲区设置的长度)

(2)4个参数版本的构造方法

网络编程UDP+TCP_第4张图片

 构造缓冲区、起始位置、缓冲区长度、地址(这个地址包括两个内容IP地址 和 端口号port)

这里提到一个InetSocketAddress 的API 它也有自己得构造方法,里面包含两个参数一个IP地址,另一个是端口号

 2.2、UDP版本服务器

我们这里做的是自己本机电脑的服务器,就是本机连接本机 称为回显服务器(echo server)

一个普通的服务器的构造需要涉及这几步:收到请求,根据请求计算响应,返回响应

Echo server 省略了其中的“根据请求计算响应”,实现的这个程序是请求啥返回啥,主要针对socket API基本用法的理解,这里不写“请求计算响应”不是它不重要,它是最重要的,但是实现需要对应的业务,这个等socket会用了再实现一个比较简单业务给友友们理解

提示:我们这里自定义了一个类 叫做: UdpEchoServer 以下UDP服务器代码都是包含在这个类中

第一步:

网络编程操作本身就是操作网卡的,但是网卡不方便直接操作,在操作系统内核中,使用了一个特殊的叫做“socket”这样的文件来抽象表示网卡,因此进行网络通信是势必要有一个Socket

private DatagramSocket socket=null;

第二步:

 这里是我们自己实现一个UDP版本的服务器,socket当前置空的原因是为了在该类的构造方法中添加输入自己的端口号,创建socket对象的同时,给他绑定一个端口号;

public UdpEchoServer(int port) throws SocketException {
        //构造方法中有一个参数就是 端口号 在socket创建对象的时候传入端口号
        socket=new DatagramSocket(port);
    }

为什么要关联一个具体端口号???

因为客户端是需要通过服务器的端口号来给服务器发送请求,那前面不是说如果不分配的话操作系统会分配一个随机的端口号吗?如果是这样的话,客户端就不知道这个端口号是什么了,也就没有办法进行通信了。

第三步:

思路:
(1)创建一个执行start方法,因为客户端肯定是不会就发发送一次请求的,会有多次,所以服务器一般都是7*24小时工作的,这里会写一个while(true)循环保持服务器不会断开.

(2)那循环内需要的就是网络所需的基础 : 接收请求 , 根据请求计算响应  ,  返回响应(这里我们细说)

接收请求:

针对UDP来说传输数据的基本单位是"数据报" 对应的类就是DatagramPacket,首先就是读取客户端的请求,需要使用receive方法 ,该方法的参数是一个输出型参数,这个参数就要是一个空的DatagramPacket对象,需要DatagramPacket对象创建对象

 DatagramPacket对象中传了两个参数 ,一个缓冲区,另一个该对象缓冲区的的长度;创建对象后传参给receive,如果客户端有数据传入receive内部会针对参数对象填充数据(该数据来自网卡),如果客户端没有数据传入receive会进行阻塞

网络编程UDP+TCP_第5张图片

 根据请求计算响应:

此时DatagramPacket是一个特殊的对象,并不直接进行数据处理,可以把来面的数据转化为字符串进行处理

网络编程UDP+TCP_第6张图片

获取字符串之后,就可以进行处理了(这里获取字符串的原因就是为了便于我们做处理);

网络编程UDP+TCP_第7张图片

 因为我们这里是走一下流程,所以没有写请求处理,知道要写这个就行.

返回响应:

把响应写回客户端,使用方法 send的参数也是DatagramPacket 对象,需要把返回对象构造好,此处构造对象就不在是空字节数了,而是使用响应数据来构造的

网络编程UDP+TCP_第8张图片

 到了这里UDP的客户端就写完了.(以上都是图片方便看解析,下面附有代码)

public void start() throws IOException {
        System.out.println("服务器启动");
        //服务器不是只给一个客户端提供服务系统就完了,一定会有很多的客户端
        while(true){
            /*
            * 只要客户端来了,就提供服务
            * 1、读取客户端发来的请求是啥
            * receive 方法的参数是一个输出型参数 需要先构造一个空白的DatagramPacket对象 ,交给receive来进行填充
            * */
            DatagramPacket requestPacker=new DatagramPacket(new byte[4096],4096);
            socket.receive(requestPacker);  //将接受传来的数据放入的当前的缓冲区
            //此时这个 DatagramPacket 是一个特殊的对象, 并不方便直接进行处理 可以把这里包含的数据拿出来,构造成一个字符串
            String requset =new String (requestPacker.getData(),0,requestPacker.getLength());
            //2、根据请求计算响应, 由于此处是回显服务器 ,响应和请求相同
            String response=process(requset);
            /*
            * 3、把响应写回到客户端 ,send的参数也是 DatagramPacket 需要把这个 Packet对象构造好
            * 此处构造的响应对象 不能是用空的字节数 构造了,而是要使用响应数据来构造
            * */
            DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacker.getSocketAddress());
            socket.send(responsePacket);
            //4、打印一下, 当前这次请求响应处理中间结束
            System.out.printf("[%s: %d] req:%s ,resp:%s\n",responsePacket.getSocketAddress().toString(),
                    responsePacket.getPort(),requset,response);
        }
    }
    public String process(String requset) {
        return requset;
    }

最后一行还进行了打印,这里也对其进行解释:

网络编程UDP+TCP_第9张图片

 main方法操作:

 2.3、UDP版本客户端

这里的客户端是为了连接上面的服务器,待这里客户端写好以后友友们就可以自行尝试。

提示:这里客户端创建一个自定义类我这里叫做: UdpEchoClient 以下所有UDP客户端代码都在这个类中

第一步:
创建我们需要的变量 UDP客户端也需要socket文件来传输数据 ,需要创建一个 socket对象,同时还要知道服务器的IP地址和 端口号才能发送数据。

    private DatagramSocket socket=null;
    private  String serverIP=null;
    private  int serverPort=0;
    /*
    * 一次通信 ,需要有两个 IP 两个端口
    * 客户端 IP 是 127.0.0.1
    * 客户端的 port 是系统自动分配的
    * 服务器 IP 和 端口 也需要告诉客户端 ,才能顺利把这个消息发给服务器
    * */
    /*
    * 客户端这里为啥没有 个一个具体的端号  因为服务器不是本机 客户的主机上可能已经运行了很多的程序就会导致你设置的端口 号是被重复,会产生冲突
    * */
    public  UdpEchoClient(String serverIP ,int serverPort) throws SocketException {
        socket=new DatagramSocket();
        this.serverIP=serverIP;
        this.serverPort=serverPort;
    }

这里可能会有友友们问到为啥服务器有自己自己的端口号,这里为什么没有提到客户端具体的端口号??

答案:客户端的端口号是系统自动分配的(分配一个空闲的端口号),那可以自己给客户端定义一个具体的端口号吗?勉强可以,因为客户端上有可能有很多程序,每个程序都有自己的端口号,如果自己定义就可能会产生冲突;

举个例子:你去吃饭,人多的时候就会给你一个牌牌,叫到你,你来领餐,但是此时你就是想要66号牌牌,但是可能这个牌牌已经被人领走了(该饭店就是一个客户端,餐牌就是端口号,客户端会系统自动分配端口号,你强行要一个餐牌就可能已经被(其他程序)拿走了造成冲突)

第二步:

写一个start方法进行运行 :思路服务器大同小异(反向操作)

网络编程UDP+TCP_第10张图片

 while(true)循环中执行:写数据进行判断结束、发送数据、接收数据、控制台打印

写数据进行判断:

网络编程UDP+TCP_第11张图片

 发送数据:

网络编程UDP+TCP_第12张图片

 接收数据:

网络编程UDP+TCP_第13张图片

 控制台打印:

网络编程UDP+TCP_第14张图片

 以上图片解析比较零散,以下附有以上解释的代码

public void start() throws IOException {
        System.out.println("客户端启动 ");
        Scanner scanner=new Scanner(System.in);
        while(true){
            //1、从控制台读取发送的数据
            System.out.println("> ");
            String request=scanner.next();
            if(request.equals("exit")){
                System.out.println("结束");
                break;
            }
            /*
            * 2、构造成UDP 请求 并发送
            *   构造这个Packet 的时候 ,需要把serverIP 和 port 都传入过来 但是此处IP地址是一个32位的二级制的整数
            *   上述的IP地址是一个字符串 需要使用 InetAddress.getByName来进行一个转换
            * */
            DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName(serverIP),serverPort);
            socket.send(requestPacket);
            //3、读取服务器的UDP 响应 并解析
            DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            String response=new String(responsePacket.getData(),0,responsePacket.getLength());
            //4、把解析好的结果显示出来
            System.out.println(response);
        }
    }

main方法操作:

和UDP服务器操作基本相同,但是需要 服务器的IP地址和服务器的端口号

网络编程UDP+TCP_第15张图片

 2.4、UDP连接操作

首先这里先说怎么检测你的服务器和客户端都是写好的可以运行了:
网络编程UDP+TCP_第16张图片

以上连接操作是怎么进行,代码详细分解

网络编程UDP+TCP_第17张图片

2.5、翻译业务

以下带有业务的服务器继承了上面UDP版本服务器进行的

    //字典服务器 用来继承既可以
    //DictServer 来说 和 EchoServer 相比 大部分的东西都是一样的
    //主要是请求计算机响应是不一样的
public class UdpDictServer extends  UdpEchoServer {
    //这里是一个简单的翻译  使用到了Map  第一个泛型参数是接收单词 第二个泛型参数是 翻译过来的汉语
    private Map dict=new HashMap<>();
    //应用服务器这里需要继承父类 构造方法里面就可以写对应的东西
    public UdpDictServer(int port) throws SocketException {
        super(port);
        /*
        * 给当前 继承服务器 设置内容
        * 此处可以无限键值对  以下就是翻译内容 当然这里只是为了方便友友们看, 实际自己想实现点简单的业务也是可以的
        * */
        dict.put("cat","小猫");
        dict.put("dog","小狗");
        dict.put("fuck","卧槽");
    }
    //继承了也就需要 重写 UdpEchoServer的方法
    public String process(String request){
        //查词典的过程
        return dict.getOrDefault(request,"当前单词没有查到结果");
        //这里就是 看是否是默认值 , 如果有就是用当前值 如没有的话就是用默认值
    }
    public static void main(String[] args) throws IOException {
        //本次只需要掉用该服务器就可以了
        UdpDictServer server=new UdpDictServer(9090);
        server.start();
    }
}

这里就是简单的业务实现,上面给了注释,代码不多也很好理解,重点理解前面的服务器和客户端代码,这里就不难看懂了。

以下演示以下,该带有业务的服务器跑起来 与客户端 联用

网络编程UDP+TCP_第18张图片

2.6、总结

(1)服务器的端口 是固定指定的,目的是为了方便客户找到服务器程序

(服务器是我们自己手里的机器,上面运行啥,都是我们控制的,指定安排空闲端口即可)

从哪里看 ,可以有两个地方看: 任务管理器友友们应该都很熟悉(Ctrl+alt+del 后点击任务管理器)以下点击就能查看PID(Process ID)其实是进程号(也是独一无二的)

网络编程UDP+TCP_第19张图片

 另一种方法就是命令行查看 涉及cmd命令 netstat -ano  (命令行打开操作: win+r 弹出框输入cmd)

网络编程UDP+TCP_第20张图片

 这里只截取了一部分作为演示,能理解就行

(2)客户端的端口 是由系统分自动分配的,如果手动指定,可能能会造成其他程序的端口的冲突

(客户端上的程序是不可控的,系统分配是最好的,服务器上的端口号是可控的,因为程序员可以自己看见,或者自查)

(3)端口冲突

端口冲突会有以下提醒 ,这里以服务器作为当前的端口冲突来解释

上面已经提及了两个服务器,一个UPD版本的服务器,另一个翻译业务的服务器 两个服务器同时用一个9090端口号就会产生冲突

网络编程UDP+TCP_第21张图片

 3、TCP流套接字编程

3.1、TCP相关API

TCP API主要涉及两个类

ServerSocket 专门给服务器使用的Socket对象 

Socket 是既能给 客户端是用也能给服务器使用 (知道这里不是很好理解,往后面看结合代码理解)

TCP和UDP传输数据不一样 UDP是“数据报” 但是TCP不是 而是以“字节”的方式 流式传输

ServerSocket一个参数构造方法

 创建一个服务器套接字Socket,并绑定到指定端口

使用方法就有所变化 ,检测方法Socket.accept()开始监听指定端口,有客户端;连接后,返回一个服务端Socket对象,并基于该Socket建立与客户端的连接 ,否则阻塞等待 ,这里明显与UDP的服务器不同了,体现了TCP的有连接的特性

关闭此套接字 close()方法

这里相当于是连接,看是否连接上客户端,TCP 有一个特性就是有连接的,例如电话一样,需要有人接,所以这个accept就是监听有没有人接电话,没有的话就先进行阻塞(这里只是以电话举例,不是说电话就是这么搞的)

Socket在服务器这边,是由accept返回的,在客户端这边,咱们代码里构造的时候制定了一个IP地址和端口号,有了这两个信息就能和服务器建立连接了。

既然是以字节的方式进行传输的,那就会涉及到IO文件操作 InputStream对应getInputStream方法和OutputStream对应的getOutputStream方法 进一步通过Socket对象,获取到内部的流对象,借助流对象来进行发送接收

3.2、TCP版本服务器

提示:这里需要我们自定义一个类叫做:TcpEchoServer来写TCP版本服务器的代码

第一步:就是创建一个ServerSocket 对象 创建一个构造方法进行传参一个具体的端口号

//相当于是  接收客户来
    private ServerSocket serverSocket=null;
    public  TcpEchoServer(int port) throws IOException {
        //传入一个端口号
        serverSocket =new ServerSocket(port);
    }

 第二步:那服务器ServerSocket对象已经创建好了,需要一个方法来进行服务器start方法

public void start() throws IOException {
        System.out.println("启动服务器");
        while(true){
            //使用这个 clientSocket 和 具体的的客户端进行交流
            //具体进行服务器  这里用作监听 客户端是否上线
            Socket clientSocket =serverSocket.accept();
            //如果上线  accept方法 阻塞结束 进行连接操作
            processConnection(clientSocket);
        }
    }

每次创建或者返回一个 Socket对象 ,(Socket就是文件)每次创建一个clientSocket对象,就要占用一个文件描述符表,使用完毕之后就会进行“释放” ,但是不建议直接在这里“释放”,在finally里面“释放”

到了这里就会友友问 :ServerSocket 与Socket都是创建对象这不是多此一举吗?答案:这里其实我也不知道(凡是存在必有道理),我们先按照这样的方法来。

但是这里还是要说一下,这里该如何理解。

网络编程UDP+TCP_第22张图片

 第三步:processConnection如何来连接客户端操作

思路:

(1)可以显示一下 因为已经接收到了客户端 ,那这里打印一次客户端已上线

(2)Socket返回套接字输入流,返回套接字输出流,因为TCP是流式操作的

网络编程UDP+TCP_第23张图片

(3)处理肯定不是一个请求和响应所以需要循环操作,之后读取请求,判断请求数据是否存在,不存在那连接也就断开了 ,如果存在跳过即可,进行数据输入

网络编程UDP+TCP_第24张图片

(4)根据请求构造响应

(5)这里返回响应结果想要是一个字符串,但是接收来的是字节流 需要 稍作转换,转换成字符串

网络编程UDP+TCP_第25张图片

 (6)服务器打印

网络编程UDP+TCP_第26张图片

 以下是附上processConnection代码

 private void processConnection(Socket clientSocket) {
        System.out.printf("[%s:%d] 客户端上线! \n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        //基于上述socket 对象和 客户端 进行通信
        //以下使用 try() 是java语法 try with resources
        try (InputStream inputStream=clientSocket.getInputStream();
             OutputStream outputStream=clientSocket.getOutputStream()){
            //由于要处理多个请求和 响应 也是使用 循环进行的
            while(true){
                //1、读取请求
                Scanner scanner=new Scanner(inputStream);
                if(!scanner.hasNext()){
                    System.out.printf("[%s:%d]客户端下线\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;
                }
                //注意 !此处使用next 是一直 读取到换行符 或者 空格 或者 其他空白字符tab 但是 最终返回结果里不包含前面提到的这几个类别
                String request=scanner.next();
                //2、根据请求构造响应
                String response=process(request);
                //3、返回响应结果
                //OutputStream 没有write String功能 这里需要转换 可以把String 里的字节数组转换出来 进行写入
                //也可以用字符流来转化一下
                PrintWriter printWriter=new PrintWriter(outputStream);
                //此处使用 println来写入 让结果带有一个 \n 换行 方便对端来接收解析
                printWriter.println(response);
                //前面IO文件操作 中知道在字符写文件的时候 是需要刷新的所以这里也会进行
                printWriter.flush();

                System.out.printf("[%s:%d] req:%s ,resp :%s \n",clientSocket.getInetAddress().toString(),clientSocket.getPort()
                ,request,response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                //此处需要处理一下
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
 public String process(String request) { //这里也只是走个过程,并没有真的对请求进行处理
        return request;
    }

 main方法操作:

网络编程UDP+TCP_第27张图片

 3.3、TCP版本的客户端

提示:自定义一个TCPEchoClient类 以下TCP客户端代码都在该类中 

如果友友们,从前面看到这里了,基本不管是创建服务器还是创建一个客户端都是创建Socket对象,TCP客户端代码就没有TCP服务器代码麻烦了。

(1)创建Socket对象 写一个构造方法接收服务器的IP地址和 端口号

//定义一个Socket对象用来接收 IP地址和 端口号
    private Socket socket=null;

    public TcpEchoClient(String serverIp ,int serverPort) throws IOException {
        //Socket构造方法 能够识别点分十进制格式的 IP地址 比DatagramPacket更方便
        //new 这个对象的同时 就会进行TCP连接操作
        socket=new Socket(serverIp,serverPort);
    }

(2)写一个客户端启动start方法

客户端:用户写入数据、接收套接字输入流和输出流、连接结束的判断 、数据发送、接收数据

此处就不在详细解释了,基本和以上代码相似,代码配有详细的注释

public void start(){
        System.out.println("客户端启动");
        Scanner scanner=new Scanner(System.in);
        //接收套接字的输入流 和 输出流
        try(InputStream inputStream=socket.getInputStream();
            OutputStream outputStream=socket.getOutputStream()){
            while(true){
                System.out.println(">");
                //1. 先从键盘上读取用户数据内容
                String request=scanner.next();
                //判定输入一个算是解除连接  我们这里给了一个exit 表示结束连接 就相当于是 挂电话的按键
                if(request.equals("exit")){
                    System.out.println("goodbye");
                    break;
                }
                //2 把读到的内容构造请求发送给服务器  这里和TCP服务器是一样的 都需要进行转换将字节转化为字符串
                PrintWriter printWriter=new PrintWriter(outputStream);
                //发送给服务器 printWriter 的 println方法本身就会起到发送的作用
                printWriter.println(request);
                //此处需要刷新缓冲区 确保数据是发出去了,
                printWriter.flush();
                //3 服务器的响应    Scanner本身就参数就是字节输入流参数
                Scanner respScanner=new Scanner(inputStream);
                //进行读取
                String response=respScanner.next();
                //4 把响应内容显示到界面上
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

代码执行作以下分析:

以上代码是写完了,但是这里还有几个想要提醒要点

(1)疑惑:其中使用了printWriter类来转换字节流变成字符串是否还是同一个文件描述符表,OutputStream相当于一个文件描述符(socket文件)通过OutputStream就可以往这个 文件描述符表中写入数据,但是当前的OutputStream自身方法不方便 写字符串需要进行转换 ,PrintWriter对象来表示(对应的是同一个文件描述符表)

(2)以上能看发送是通过PrintWriter类中println方法进行发送的,细节问:如果不用println,直接用print发送行不行???

答案:不行(图解)

网络编程UDP+TCP_第28张图片

 3.4、TCP连接操作

网络编程UDP+TCP_第29张图片

 连接之后就发现问题了,不能多个客户端进行连接,如何解决呢?答案:多线程

3.4.1多线程TCP服务器

以前面的TCP服务器作为地基 这里有start方法内部需要稍作修改即可

 public void start() throws IOException {
        System.out.println("启动服务器");
        while(true){
            //使用这个 clientSocket 和 具体的的客户端进行交流
            //具体进行服务器  这里用作监听 客户端是否上线
            Socket clientSocket =serverSocket.accept();
            //如果上线  accept方法 阻塞结束 进行连接操作
            Thread thread=new Thread(()->{
                processConnection(clientSocket);
            });
            thread.start();
        }
    }

为什么多线程操作加在这里不用过多的解释,那多线程为什么不包含以下的代码呢

Socket clientSocket =serverSocket.accept();

 这里在while循环中,所以每次多线程执行并且在当前while循环中结束,再进行下一次while循环

网络编程UDP+TCP_第30张图片

 多线程已经能解决问题了,但这里还不打算演示,因为我们的客户端最多也就只能开几个,确实带的动,C10K(1w客户端)问题解决了,但是如果更多呢C10M(1kw客户端)问题呢,创建或者销毁这么多线程也是要有开销的,多线程提到开销问题优化措施就会想到线程池。

3.4.2、线程池TCP服务器

修改多线程修改代码位置相同

public void start() throws IOException {
        System.out.println("启动服务器");
        //创建线程池  这里创建的不是固定数量线程的池子, 是根据工作需要创建的线程池
        ExecutorService service=Executors.newCachedThreadPool();
        while(true){
            //使用这个 clientSocket 和 具体的的客户端进行交流
            //具体进行服务器  这里用作监听 客户端是否上线
            Socket clientSocket =serverSocket.accept();
            //如果上线  accept方法 阻塞结束 进行连接操作
            //提交任务
           service.submit(()->{
               processConnection(clientSocket);
           });
        }
    }

 这里就没有什么过多的解释了,如果对线程池不是很理解的友友(可以看下这篇博客)

现在演示,使用多线程之后,客户端有怎么样的结果

网络编程UDP+TCP_第31张图片

 注 :TCP版本服务器和客户端没有写相关业务,友友们可以模仿这UDP翻译业务也给TCP写一个,基本是一样的写法,这里再TCP版本中就不在重复进行了。

你可能感兴趣的:(网络编程,网络,udp,tcp/ip,服务器)