一、客户端和服务器
学习传输层给应用层提供的api,可以写代码把数据交给传输层,进一步通过层层封装就可以把数据通过网卡发送出去了
网络中主动发起请求的一方被称为客户端,被动接收的一方被称为服务器
客户端和服务器之间的交互有很多模式
1、一问一答:一个请应对应一个响应,最常见,网站开发就是这种一问一答
2、一问多答:一个请求对应多个响应,主要涉及到下载
3、多问一答:多个请求对应一个响应,主要涉及到上传
4、多问多答:多个请求对应多个响应,主要涉及远程控制
网络编程使用系统提供的api本质是传输层提供的(TCP,UDP)
UDP特点:无连接,不可靠传输,面向数据报,全双工
TCP特点:有链接,可靠传输,面向字节流,全双工
有连接的意思是,双方都得认同,例如打电话,得都接通了才能说话
无连接:发微信/短信等,无论是否同意,都能发过去
连接的本质是通信双方都保存对方的信息
可靠传输/非可靠传输
可靠传输:尽可能完成数据传输,虽然无法确保数据到达对方,但可以知道该数据对方是否收到了
面向字节流:此处谈到的字节流和文件中的字节流完全一致
网络中传输数据的基本单位就是字节
面向数据报:每次传输的基本单位是一个数据报,由一系列字节构成的特定结构
全双工:一个信道可以双向通信
半双工:一个信道只能单向涌信
二、UDP(api使用Sockrt)
1、核心类
1、DatagramSocket
负责对socket文件读写,借助网卡发送接收数据
2、DatagramPacket
UDP面向数据报,每次发送和接收的其本单位就是一个UDP数据报
对服务器来说,先创建 DatagramSocket 对象
操作网卡都是通过socket对象来完成的
一个主机上的一个端口号只能被一个进程绑定,反之一个进程可以绑定多个端口
服务器需要不停收到请求返回响应,一个服务器单位时间内能处理的请求越多越好
receive()中的参数是Datagrampacket(输出型参数),内部会包含一个字节数组
通过该字节数组保存到的消息正文也就是UDP数据报的载荷部分
2、启动UDP传输
1、服务器启动以后立刻进入while循环,执行到receive 时进入阻塞,此时无任何客户端发来请求
2、客户端启动,立刻进入whie循环,执行到hasNext 时进入阻塞,此时用户没有在控制台输入任何内容
3、用户在客户端输入字符串按下回车,此时阻塞解除,next会返问刚才的内容,基于用户输入的内容构造出一个DatagramSocket对象,并进行send
send 执行完毕之后继续rereive操作,等待服务器返回的响应数据
4、服务器收到请求时,就会从receive的阻塞中返回,返回后就会根据读到的 DatagramPacket 对象构造 string equest 通过 process方法构造一个string response 再根据 response 构造一个DatagramPacket 再通过send来进行发送给客户端,该过程中客户端也始终在阻塞等待
5、客户端从 receive 中返回执行,就能得到服务器返回的响应,并打印到控制台上,此时服务器进入下一次循环,也要进入第二次的receive等待下一个请求
三、TCP(核心API:ServerSocket)
1、ServerSocket
ServerSocket:Socket类,对应到网卡,但是这个类只能给服务器用
Socket:对应到网卡,既可以给服务器用又可以给客户端用
TCP面向字节流,传输的基本单位是字节
TCP是有连接的,需要客户端发起连接,服务器来接收
客户端调用的 api 和服务器尝试建立连接,内核就会发起建立连接的流程,但内核建立的连接不是决定性的,需要用户程序进行accept,才能进行后续通信 accept 也是一个可能会产生阻塞的操作,若是没有客户端连过来,accept 就会阻塞
2、启动TCP传输
1、服务器启动,阻塞在accept,等待客户端连接
2、客户端启动,new 操作引发了和服务器建立连接的操作,此时服务器就会从accept 中返回
3、服务器从accept 中返回,进入到 processConnection 方法,执行到 hasnext时虽然建立了连接,但是客户端都没有发来任何请求,产生阻塞到请求到达
4、客户端执行到 hasNext,等待用户向控制台输入内容
5、输入内容后,hasNext返回,执行请求发送的逻辑,发送请求同时客户端等待服务的响应返回,next 也会阻塞
6、服务器从hasNext返回,读取请求内容并进行处理,构造出响应并把响应写回客户端
7、客户端读取到响应并显示出来,此时进下一个循环
3、注意要点
1、flush
使用printWriter时有内置的缓冲区,若发的数据量很少,缓冲区未满,数据就会呆在缓冲区内未被真的发送,此时就要引入flush方法刷新缓冲区
2、close
服务器代码中要针对clientSocket进行close
serverSocket整个程序只有一个对象,并且这个对象无法提前关闭,只要程序退出,随进程的销毁一起被释放
调用socket.close 本质也是关闭文件,释放文件描述符表,进程销毁,文件描述符表也就没了,因此写UDP的程序无close
但是tcp的clientSocket是每个客户端一个,客户端越来越多,消耗的socket也会越来越多,
不加释放,就可能把文件描述符表占满
连接处理完毕(processConnection结束)就可以close
try with resources 仅是关闭了流对象,而没有释放文件本体,这两个流对象都是socket对象创建出来的
释放了socket对象,即使上述对象不释放也无问题,这两个流对象内部不持有文件描述符,仅有一些内存结构
但只释放流对象不释放socket不行,socket 持有文件描述符,本质还是释放文件描述符资源
3、服务于多个客户端
第一个客户端连上服务器之后,服务器就会从 accept 这里返回(解除阻塞)进入processConnection 中了
接下来就会再 scanner.hasNext 这里阻塞,等待客户端的请求
客户端请求 到达之后,scanner.hasNext 返回,继续执行, 读取请求根据请求计算响应,返回响应给客户端
执行完上述一轮操作之后,循环回来继续再 hasNext 阻塞,等待下一个请求直到客户端退出之后,连接结束,此时循环才会退出
服务器代码在里层循环转圈的时候无法第二次执行到 accept
虽然第二个客户端和服务器在内核层面上建立了 tcp 连接了,但是应用程序这里无法把连接拿到应用程序里处理
核心思路就是使用多线程.
单个线程,无法既能给客户端循环提供服务,有能去快速调用到第二次 accept
简单的办法就是引入多线程,
主线程就负责执行 accept,每次有一个客户端连上来,就分配一个新的线程,由新的线程负责给客户端提供服务
此时,把 processConnection 操作交给新的线程来负责了
主循环就会快速的执行完一次之后,回到 accept 这里阻塞等待新的客户端到来