1.0 ServerSocket 服务端 API
概念
- ServerSocket 是创建TCP服务端Socket的API。
- TCP是字节省,我们并不需要像UDP那样有个专门的数据报,换句话来说一个tcp数据报,就是一个字节数组
ServerSocket 构造方法
方法署名方法阐明ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口 ServerSocket 方法
方法签 名方法阐明Socket accept()开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket 对象,并基于该Socket建立与客户端的连接,否则壅闭等候Socket accept()关闭此套接字 2.0 Socket 客户端/服务端 API
概念
- Socket 是客户端Socket,或服务端中吸收到客户端建立连接(accept方法)的请求后,返回的服务端 Socket。
- 不管是客户端还是服务端Socket,都是双方建立连接以后,生存的对端信息,及用来与对方收发数据 的。
- Socket 既可以客户端用也可以服务器用
构造方法
方法署名方法阐明Socket(String host, int port)创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的 进程建立连接 Socket 方法
方法署名方法阐明InetAddress getInetAddress()返回套接字所连接的地点InputStream getInputStream()返回此套接字的输入流OutputStream getOutputStream()返回此套接字的输出流 3.0 短连接和长连接
TCP程序涉及到两种写法
- 1.短连接一个链接中只能传输一次请求和相应
- 2.长连接 一个连接中,可以传输多次请求和相应
- 长连接与短连接在代码的区别就是有无while死循环,加了死循环,只要建立连接且不break,就可以一直通讯
4.0 TCP的回显服务器
概念
- 服务器启动后在while循环时,第一步不是吸收客户端请求,而是与客户端连接
- 不用握手,握手时系统内科负责的,写代码的过程是感受不到的
- 连接是在握手之后的处理
- 一次IO经历两个部分,1等(壅闭) 2拷贝数据
存在的题目与解决
- Q1:ServerSocket中的队列是啥? 在系统内核眼里,ServerSocket这个对象也是一个文件,ServerSocket里有一个队列,当客户端和服务器实验建立连接的时间,服务器就会和客户端建立连接时,服务器就会和客户端进行一系列数据交互,称为"握手"(这一步由操作系统完成),这个过程建立完了之后,连接就建立好了,ServerSocket队列中就会添加这些建立好连接的对象**(生产),要处理这些连接,就会取队列元素(消费)**,每个ServerSocket对象都有一个队列
- Q2:程序中建立连接的过程 一个服务器,要对应很多的客户端,服务器内核里有很多客户端的连接(已经握手了),但是应用程序还是要一个一个的建立连接,内核的连接就像一个一个"待服务项",这些代服务项在一个队列的数据布局中,应用程序就要一个一个的完成这些任务,要完成任务就需要serverSocket.accept方法取任务,如果客户端没 有连接,那么accept就会壅闭
- Q3:为什么serverSocket.accept返回的是Socket?, Socket就像耳麦(耳机加麦克风,耳机是InputStream,麦克风是OutputStream)一样,直接可以跟对方相互说话,通过Socket对象和对方进行网络通讯,实现由内核实现,我们可以这样理解:accept就是阐明我要指明某个客户端和这个服务器通讯,而Socket就是让这两个对象可以相互说话
- Q4:客户端服务器通讯有几个流对象?, 正确来说是两个Socket对象,服务器一个客户端一个,每个Socket对象(文件)对应两个流对象,有一个流入,一个流出
- Q4;String next读取的题目? next就是一直读数据,直到读到空白符竣事(空白符是一类字符,包括不限于: 换行’\n’,回车’\r’,空格,制表符,换页符,垂直制表符)
- **Q5: 换行和回车的区别? ** 换行(‘\n’)是让光标到下一行,回车(‘\r’)是让光标回到行首,不会另起一行
- Q6:flush的意义是什么? IO操作是比较有开销的相比于访问内存,进行IO次数越多,程序越慢,为了提高服从,淘汰IO次数,使用一块内存充当缓冲区,写数据先写到缓冲区里,攒一波再进行IO,flush就可以刷新缓冲区,确保数据真的通过网卡发了出去,而不是残留在内存缓冲区,上述说的是全缓冲,而换行(‘\n’)刷新是行缓冲,行缓冲通常用于尺度输入输出(控制台上打印和输入我们通常输入enter linux本质就是’\n’,windows 是 ‘\r\n’),//但我们如今是往网卡/网络文件中写(全缓冲,不会受到’\n’影响)
- Q7: 两个TCP类的关闭与否 ? ServerSocket(只有一个,生命周期跟随程序,不关闭也没事),Socket每次来一个连接,就会有一个客户端和服务器建立连接,以是要用完就关闭
- Q8:怎样解决TPC只支持单播特性 用多线程解决此题目,在主线程建立连接,在主线程建立连接完后,创建新线程专门用于处理客户端请求,可以使用Thread创建(只要服务器系统资源足够,就几个客户端连接都可以)
- Q9: 多个服务器重复连接再断启发致服务器频繁创建释放线程, 用线程池解决
- Q10: 啥是C10M
- 10k 同一时候,有10k个客户端(1w个客户端) , 硬件好大概线程池就能解决, C10M 就是同一时候有1kw的客户端并发请求,这时间以达到高并发了
- (其实总的来说解决高并发的方法就是开源节省,开源:引入更多的硬件资源,节省:提高单位硬件资源能够处理的请求数,本质上是淘汰线程数目,淘汰资源使用)
- 其中的一个重要解决高并发的方法 就是IO多路复用/IO多路转接,IO多路复用就是一个线程完成多组IO操作(比如对很多小吃摊位付钱,等候他们谁先好我就直接拿摊位的食物),多路复用是OS提供的另一组网络编程的API,这组API可以共同tcp udp 的api共同使用(去搜索 java NIO 写的服务器和当前tcp的差别挺大的)
代码示例
- 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;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- public class TcpEchoServer {
- private ServerSocket serverSocket = null;
- // 此处不应该创建固定线程数目的线程池.
- private ExecutorService service = Executors.newCachedThreadPool();//线程池
- //构造方法
- // 这个操作就会绑定端口号
- public TcpEchoServer(int port) throws IOException {
- serverSocket = new ServerSocket(port);
- }
- // 启动服务器
- public void start() throws IOException {
- System.out.println("服务器启动!");//打印日志
- //服务器启动后在while循环时,第一步不是接收客户端请求,而是与客户端连接
- while (true) {
- //把内核中已连接的对象获取到应用程序中了,这个过程类似于生产者消费者模型
- //返回值是一个Socket
- Socket clientSocket = serverSocket.accept();
- //***************************************************************************************************************
- //问题2:
- // 单个线程,不能处理多个服务器请求,只能一个结束才能处理下一个
- // 多线程解决此问题,在主线程建立连接,在主线程建立连接完后,创建新线程专门用于处理客户端请求
- // Thread t = new Thread(() -> {
- // processConnection(clientSocket);
- // });
- // t.start();
- //问题1:
- // 这个写法,是能自动关闭close,只要实现 Closeable 接口就可以这么写.
- //这么写会有其他问题.processConnection和主线程是不同线程,执行processConnection的过程中,主线程的try就执行完毕了,这就导致clientSocket没用完就被关闭了
- // try(Socket clientSocket = serverSocket.accept();){
- // Thread t = new Thread(() -> {
- // processConnection(clientSocket);
- // });
- // t.start();
- //
- // };
- // 使用线程池, 来解决上述问题
- service.submit(new Runnable() {
- @Override
- public void run() {
- processConnection(clientSocket);
- }
- });
- //***************************************************************************************************************
- }
- }
- // 通过这个方法来处理一个连接的逻辑.
- private void processConnection(Socket clientSocket) {
- System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
- // 接下来就可以1.读取请求, 2.根据请求计算响应, 3.返回响应三步走了.
- // Socket 对象内部包含了两个字节流对象, 可以把这俩字节流对象获取到, 完成后续的读写工作
- try (InputStream inputStream = clientSocket.getInputStream();
- OutputStream outputStream = clientSocket.getOutputStream()) {
- // 一次连接中, 可能会涉及到多次请求/响应
- while (true) {
- // 1. 读取请求并解析. 为了读取方便, 直接使用 Scanner.(把字节流在内地转化为字符流)
- Scanner scanner = new Scanner(inputStream);
- //当没读到客户端数据的时候hasNext会一直阻塞,或者客户端退出,hashNext感知到客户端退出就break了
- if (!scanner.hasNext()) { //如果读到eof,就读完了,就退出循环
- // 读取完毕, 客户端下线.
- System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
- break;
- }
- // 这个代码暗含一个约定, 客户端发过来的请求得是文本数据, 同时还得带有空白符作为分割. (比如换行这种)
- //next就是一直读数据,直到读到空白符结束(空白符是一类字符,包括不限于: 换行'\n',回车'\r',空格,制表符,换页符,垂直制表符)
- //换行('\n')是让光标到下一行,回车('\r')是让光标回到行首,不会另起一行
- String request = scanner.next();
- // 2. 根据请求计算响应
- String response = process(request);
- // 3. 把响应写回给客户端. 把 OutputStream 使用 PrinterWriter 包裹一下, 方便进行发数据.
- PrintWriter writer = new PrintWriter(outputStream);
- //使用 PrintWriter 的 println 方法, 把响应返回给客户端.
- //此处用 println, 而不是 print 就是为了在结尾加上 \n . 方便客户端读取响应, 使用 scanner.next 读取.
- writer.println(response);
- //这里还需要加一个 "刷新缓冲区" 操作.
- //IO操作是比较有开销的相比于访问内存,进行IO次数越多,程序越慢
- //为了提高效率,减少IO次数,使用一块内存充当缓冲区,写数据先写到缓冲区里,攒一波再进行IO,flush就可以刷新缓冲区,确保数据真的通过网卡发了出去,而不是残留在内存缓冲区
- //上述说的是全缓冲,而换行('\n')刷新是行缓冲,行缓冲通常用于标准输入输出(控制台上打印和输入我们通常输入enter linux本质就是'\n',windows 是 '\r\n'),//但我们现在是往网卡/网络文件中写(全缓冲,不会受到'\n'影响)
- writer.flush();
- // 日志, 打印当前的请求详情.
- System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(),
- request, response);
- }
- } catch (IOException e) {
- e.printStackTrace();//捕获try的IO异常
- } finally {
- // 在 finally 中加上 close 操作, 确保当前 socket 被及时关闭!!
- 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(9090);
- server.start();
- }
- }
复制代码 5.0 TCP 客户端
题目与解决
- Q1: 代码中Scanner 和 PintWriter 为什么可以不关闭, 流对象中持有的资源,为两个部分 1.内存(对象销毁,内存也就回收了,while循环一圈,当break天然销毁了), 2 scanner 和 printwriter 没有持有文件描述符 他们持有的是inputstram和outputstream的引用,也就是持有其他流对象的引用,而inputstram和outputstream 写在try括号内,会主动关闭的,更正确来说是socket对象持有inputstram和outputstream 只要关闭socket就可以了
代码示例
- 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 {
- private Socket socket = null;
- // 要和服务器通信, 就需要先知道, 服务器所在的位置.,这里的ip和端口号都是服务器的
- public TcpEchoClient(String serverIp, int serverPort) throws IOException {
- // 这个 new 操作完成之后, 就完成了 tcp 连接的建立.
- //这一段代码本质做了相当多事,在网络原理中会讲述到
- socket = new Socket(serverIp, serverPort);
- }
- public void start() {
- System.out.println("客户端启动");
- Scanner scannerConsole = new Scanner(System.in);
- try (InputStream inputStream = socket.getInputStream();
- OutputStream outputStream = socket.getOutputStream()) {
- while (true) {
- // 1. 从控制台输入字符串.
- System.out.print("-> ");
- String request = scannerConsole.next();
- // 2. 把请求发送给服务器
- PrintWriter printWriter = new PrintWriter(outputStream);
- // 使用 println 带上换行. 后续服务器读取请求, 就可以使用 scanner.next 来获取了
- printWriter.println(request);
- // 不要忘记 flush, 确保数据是真的发送出去了!!
- printWriter.flush();
- // 3. 从服务器读取响应.
- Scanner scannerNetwork = new Scanner(inputStream);
- String response = scannerNetwork.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", 9090);
- client.start();
- }
- }
复制代码 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |