ps免费模板网站,wordpress熊掌号插件,线上招生代理平台,网站诚信备案写在前
这一节的内容首先是对十五弹#xff08;UDP回显服务器#xff09;进行简单的改进#xff0c;在这基础上开始介绍TCP流套接字编程。 目录
写在前
1.改进回显服务器
1.1完整代码实现
1.2运行输出结果
2.TCP流套接字编程
2.1ServerSocketAPI
2.2SocketAPI
3.TC…写在前
这一节的内容首先是对十五弹UDP回显服务器进行简单的改进在这基础上开始介绍TCP流套接字编程。 目录
写在前
1.改进回显服务器
1.1完整代码实现
1.2运行输出结果
2.TCP流套接字编程
2.1ServerSocketAPI
2.2SocketAPI
3.TCP版本的回显服务器
3.1代码实现
3.1.1服务器端完整代码
3.1.2客户端完成代码
3.2TCP中的长短连接
3.3解决C10M问题 1.改进回显服务器
上一节中的回显服务器缺少业务逻辑这里我们对代码进行改进实现一个查词典的功能。
1.1完整代码实现
这里需要的是需要重写一下process方法
package network;import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;/*** Created with IntelliJ IDEA.* Description:* User: 苏西西* Date: 2023-10-19* Time: 21:55*/
//对于DictServer来说和EchoServer相比大部分东西都是一样的继承复用原来的代码
//主要是根据请求计算响应这个步骤不太一样
public class UdpDictServer extends UdpEchoServer{private MapString,String dict new HashMap();public UdpDictServer(int port) throws SocketException {super(port);//给这个dict设置一下内容dict.put(cat,小猫);dict.put(dog,小狗);dict.put(pig,zyt);//可以无限多设置}Overridepublic String process(String request){//查词典return dict.getOrDefault(request,查不到);}public static void main(String[] args) throws IOException {UdpDictServer server new UdpDictServer(4000);server.start();}
}
1.2运行输出结果 2.TCP流套接字编程
TCP并不需要一个类来表示“TCP”数据报。
TCP不是以数据报为单位进行传输的是以字节的方式流式传输的。
2.1ServerSocketAPI
ServerSocket 专门给服务器使用的Socket对象。 ServerSocket构造方法 方法签名方法说明 ServerSocket(int port) 这里的port指的是服务器要绑定的端口。 创建一个服务端流套接字Socket并绑定到指定端口。 ServerSocket方法 方法签名方法说明Socket accept( ) 开始监听指定端口创建时绑定的端口有客户端连接后返回一个服务端Socket对象并基于该Socket建立与客户端的连接否则阻塞等待void close关闭套接字
这里accept() 类比于接电话返回一个服务端Socket对象接电话后会返回一个Socket对象通过这个socket对象和客户端进行沟通。
2.2SocketAPI
Socket 既会给客户端使用也会给服务器使用。
在服务器这边是由accept返回的在客户端这边代码构造的时候指定一个IP和端口号此处值指的是服务器的IP和端口有了这个信息就可以和服务器建立连接了。 Socket构造方法 方法签名方法说明SocketString host,int port创建一个客户端流套接字Socket并与对应IP的主机上对应端口的进程建立连接。 Socket方法 方法签名方法说明InetAddress getInetAddress()返回套接字所连接的地址 InputStream getInputStream() 返回此套接字的输入流OutputStream getOutputStream()返回此套接字的输出流
进一步通过Socket对象获取到内部的流对象借助流对象来进行发送/接收。
3.TCP版本的回显服务器
3.1代码实现
3.1.1服务器端完整代码
package network;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;public class TcpEchoServer {private ServerSocket serverSocket null;public TcpEchoServer(int port) throws IOException {serverSocket new ServerSocket(port);}public void start() throws IOException {System.out.println(启动服务器);while (true){//使用这个clientSocket和具体的客户端进行交流。Socket clientSocket serverSocket.accept();processConnection(clientSocket);}}//使用这个放方法来处理一个连接。//这一个连接对应到一个客户端但是这里可能会涉及到多次交互。private void processConnection(Socket clientSocket) {System.out.printf([%s:%d] 客户端上线\n,clientSocket.getInetAddress().toString(),clientSocket.getPort());//基于上述socket对象和客户端进行通信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是一直读取到换行符/空格/其他空白符结束//最终返回结果里不包含上述空白符String request scanner.next();//2.根据请求构造响应String response process(request);//3.返回响应结果//对于OutputStream 没有write String这个功能可以把String里的字节数组拿出来进行写入//也可以用字符流来转换一下PrintWriter printWriter new PrintWriter(outputStream);//此处使用println来写入让结果中带有一个\n换行方便对端接收端接收解析printWriter.println(response);//flush用来冲刷缓冲区保证当前写入的数据确实发送出去了printWriter.flush();//打印一个日志System.out.printf([%s:%d] req:%s;resp:%s\n,clientSocket.getInetAddress().toString(),clientSocket.getPort(),request,response);}} catch (IOException e) {e.printStackTrace();}finally {//更合适的做法是将close放到finally里面保证close()一定会被执行到try {clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server new TcpEchoServer(1111);server.start();}} 这里的accept() 效果是“接收连接”前提是得有客户端来建立连接。客户端在构造Socket对象的时候就会指定服务器的IP和端口如果没有客户端来连接此时的accept就会阻塞。
TCP socket里面涉及两种socket对象。 这个代码中用到了一个clientSocket此时任意一个客户端连上来都会返回/创建一个Socket对象。Socket就是文件每次创建一个clientSocket对象就要占用一个文件描述符表的位置。
因此在使用完毕之后就需要进行“释放”。
在前面的记录中socket都没有释放一方面这些socket生命周期更长跟随整个程序另一方面这些socket也不多固定数量。
但是此处的clientSocket数量多每个客户端都有一个生命周期也更短。
3.1.2客户端完成代码
package network;import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;public class TcpEchoClient {//当前这里的socket既给服务器用又给客户端用private Socket socket null;public TcpEchoClient(String serverIp,int serverPort) throws IOException {//Socket构造方法能够识别点分十进制格式的ip地址比DatagramPacket更方便//new这个对象的同时就会进行Tcp连接操作socket new Socket(serverIp,serverPort);}public void start(){System.out.println(客户端启动);Scanner scanner new Scanner(System.in);try(InputStream inputStream socket.getInputStream();OutputStream outputStream socket.getOutputStream()) {while (true){//1.先从键盘上读取用户输入的内容System.out.print( );String request scanner.next();if(request.equals(exit)){System.out.println(goodbye);break;}//2.把读到的内容构造成请求发送给服务器PrintWriter printWriter new PrintWriter(outputStream);printWriter.println(request);//此处加上flush保证我们的数据确实发送出去了printWriter.flush();//3.读取服务器的响应Scanner respScanner new Scanner(inputStream);String response respScanner.next();//4.把响应的内容给显示到界面上System.out.println(response);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient client new TcpEchoClient(127.0.0.1,1111);client.start();}
} 这个对象的构造过程就是触发TCP建立连接的过程打电话就开始拨号了如果客户端没有这个代码服务器就会在accept这里阻塞无法产生出clientSocket了。
outputSteam相当于是对应着一个文件描述符表socket文件通过outputStream就可以往这个文件描述符表中写数据。
outputStream自身的方法不便写字符串把这个流转换一下用一个PrintWrite对象来表示对应的文件描述符表还是同一个
使用PrintWrite写和OutputStream写是往同一个地方写只不过写的过程更方便了。
3.2TCP中的长短连接
TCP发送数据时需要先建立连接什么时候关闭连接就决定是短连接还是长连接。
1短连接客户端每次给服务器发消息先建立连接发送请求读取响应关闭连接。下次再发送则重新建立加连接。
2长连接客户端建立连接之后连接不着急断开然后再发送请求读取响应若干轮之后客户端确实短时间内不再需要这个连接了此时就断开。
服务器这里的开发很少有不用多线程的情况(当然也有用多进程的)。
这里就可以对代码进行改进
使用多线程版本处理程序最大的问题就是可能会涉及到频繁的申请释放线程。 在多线程的基础上使用线程池进行代码编写。 但是如果客户端非常多而且客户端还迟迟不断开的高压就回导致机器上会有很多线程。此时就就会提起一种方法增加机器。但是这就意味着需要增加成本需要多花钱这里的问题又被称为是C10M问题。
3.3解决C10M问题
C10K问题单机处理1W个客户端。
C10M问题单机处理1kw个客户端针对多线程的版本最大的问题就是机器承担不了这么大的线程开销是否有办法一个线程处理很多个客户端连接
IO多路复用/IO多路转接
给这个线程安排个集合这个集合就放了一堆连接线程就负责监听这个集合这里的哪个连接有数据来线程就处理哪个连接。虽然连接有很多但是这些连接的请求并非严格意义的同时总还是有先后的。
在操作系统中提供了一些原生的API select、poll、epoll 在Java中提供了一组NIO 这样 类就封装了上述多路复用的API。
这一节和前一节的内容主要介绍的就是Udp和TcpSocket编程需要注意的是关于有无连接、面向字节流还是数据报以及全双工与否这些特点在代码中都是有体现的。关于时候是可靠传输这一特点是隐藏在TCP背后从代码的角度是感受不到的。TCP诞生的初心也就是为了解决可靠传输的问题。
下一部分将介绍网络原理知识的相关内容。
继续加油哦