5.6.3 网络套接字章 TCP服务器客户端

打印 上一主题 下一主题

主题 831|帖子 831|积分 2493

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的差别挺大的)

代码示例

  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.io.OutputStream;
  4. import java.io.PrintWriter;
  5. import java.net.ServerSocket;
  6. import java.net.Socket;
  7. import java.util.Scanner;
  8. import java.util.concurrent.ExecutorService;
  9. import java.util.concurrent.Executors;
  10. public class TcpEchoServer {
  11.     private ServerSocket serverSocket = null;
  12.     // 此处不应该创建固定线程数目的线程池.
  13.     private ExecutorService service = Executors.newCachedThreadPool();//线程池
  14.     //构造方法
  15.     // 这个操作就会绑定端口号
  16.     public TcpEchoServer(int port) throws IOException {
  17.         serverSocket = new ServerSocket(port);
  18.     }
  19.     // 启动服务器
  20.     public void start() throws IOException {
  21.         System.out.println("服务器启动!");//打印日志
  22.         //服务器启动后在while循环时,第一步不是接收客户端请求,而是与客户端连接
  23.         while (true) {
  24.             //把内核中已连接的对象获取到应用程序中了,这个过程类似于生产者消费者模型
  25.             //返回值是一个Socket
  26.             Socket clientSocket = serverSocket.accept();
  27. //***************************************************************************************************************
  28.             //问题2:
  29.             // 单个线程,不能处理多个服务器请求,只能一个结束才能处理下一个
  30.             // 多线程解决此问题,在主线程建立连接,在主线程建立连接完后,创建新线程专门用于处理客户端请求
  31. //            Thread t = new Thread(() -> {
  32. //                processConnection(clientSocket);
  33. //            });
  34. //            t.start();
  35.             //问题1:
  36.             // 这个写法,是能自动关闭close,只要实现 Closeable 接口就可以这么写.
  37.             //这么写会有其他问题.processConnection和主线程是不同线程,执行processConnection的过程中,主线程的try就执行完毕了,这就导致clientSocket没用完就被关闭了
  38. //            try(Socket clientSocket = serverSocket.accept();){
  39. //                Thread t = new Thread(() -> {
  40. //                    processConnection(clientSocket);
  41. //                });
  42. //                t.start();
  43. //
  44. //            };
  45.             // 使用线程池, 来解决上述问题
  46.             service.submit(new Runnable() {
  47.                 @Override
  48.                 public void run() {
  49.                     processConnection(clientSocket);
  50.                 }
  51.             });
  52. //***************************************************************************************************************
  53.         }
  54.     }
  55.     // 通过这个方法来处理一个连接的逻辑.
  56.     private void processConnection(Socket clientSocket) {
  57.         System.out.printf("[%s:%d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
  58.         // 接下来就可以1.读取请求, 2.根据请求计算响应, 3.返回响应三步走了.
  59.         // Socket 对象内部包含了两个字节流对象, 可以把这俩字节流对象获取到, 完成后续的读写工作
  60.         try (InputStream inputStream = clientSocket.getInputStream();
  61.              OutputStream outputStream = clientSocket.getOutputStream()) {
  62.             // 一次连接中, 可能会涉及到多次请求/响应
  63.             while (true) {
  64.                 // 1. 读取请求并解析. 为了读取方便, 直接使用 Scanner.(把字节流在内地转化为字符流)
  65.                 Scanner scanner = new Scanner(inputStream);
  66.                 //当没读到客户端数据的时候hasNext会一直阻塞,或者客户端退出,hashNext感知到客户端退出就break了
  67.                 if (!scanner.hasNext()) { //如果读到eof,就读完了,就退出循环
  68.                     // 读取完毕, 客户端下线.
  69.                     System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
  70.                     break;
  71.                 }
  72.                 // 这个代码暗含一个约定, 客户端发过来的请求得是文本数据, 同时还得带有空白符作为分割. (比如换行这种)
  73.                 //next就是一直读数据,直到读到空白符结束(空白符是一类字符,包括不限于: 换行'\n',回车'\r',空格,制表符,换页符,垂直制表符)
  74.                 //换行('\n')是让光标到下一行,回车('\r')是让光标回到行首,不会另起一行
  75.                 String request = scanner.next();
  76.                 // 2. 根据请求计算响应
  77.                 String response = process(request);
  78.                 // 3. 把响应写回给客户端. 把 OutputStream 使用 PrinterWriter 包裹一下, 方便进行发数据.
  79.                 PrintWriter writer = new PrintWriter(outputStream);
  80.                 //使用 PrintWriter 的 println 方法, 把响应返回给客户端.
  81.                 //此处用 println, 而不是 print 就是为了在结尾加上 \n . 方便客户端读取响应, 使用 scanner.next 读取.
  82.                 writer.println(response);
  83.                 //这里还需要加一个 "刷新缓冲区" 操作.
  84.                 //IO操作是比较有开销的相比于访问内存,进行IO次数越多,程序越慢
  85.                 //为了提高效率,减少IO次数,使用一块内存充当缓冲区,写数据先写到缓冲区里,攒一波再进行IO,flush就可以刷新缓冲区,确保数据真的通过网卡发了出去,而不是残留在内存缓冲区
  86.                 //上述说的是全缓冲,而换行('\n')刷新是行缓冲,行缓冲通常用于标准输入输出(控制台上打印和输入我们通常输入enter linux本质就是'\n',windows 是 '\r\n'),//但我们现在是往网卡/网络文件中写(全缓冲,不会受到'\n'影响)
  87.                 writer.flush();
  88.                 // 日志, 打印当前的请求详情.
  89.                 System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(), clientSocket.getPort(),
  90.                         request, response);
  91.             }
  92.         } catch (IOException e) {
  93.             e.printStackTrace();//捕获try的IO异常
  94.         } finally {
  95.             // 在 finally 中加上 close 操作, 确保当前 socket 被及时关闭!!
  96.             try {
  97.                 clientSocket.close();
  98.             } catch (IOException e) {
  99.                 e.printStackTrace();
  100.             }
  101.         }
  102.     }
  103.     public String process(String request) {
  104.         return request;
  105.     }
  106.     public static void main(String[] args) throws IOException {
  107.         TcpEchoServer server = new TcpEchoServer(9090);
  108.         server.start();
  109.     }
  110. }
复制代码
5.0 TCP 客户端

题目与解决



  • Q1: 代码中Scanner 和 PintWriter 为什么可以不关闭, 流对象中持有的资源,为两个部分 1.内存(对象销毁,内存也就回收了,while循环一圈,当break天然销毁了), 2 scanner 和 printwriter 没有持有文件描述符 他们持有的是inputstram和outputstream的引用,也就是持有其他流对象的引用,而inputstram和outputstream 写在try括号内,会主动关闭的,更正确来说是socket对象持有inputstram和outputstream 只要关闭socket就可以了
代码示例

  1. import java.io.IOException;
  2. import java.io.InputStream;
  3. import java.io.OutputStream;
  4. import java.io.PrintWriter;
  5. import java.net.Socket;
  6. import java.util.Scanner;
  7. public class TcpEchoClient {
  8.     private Socket socket = null;
  9.     // 要和服务器通信, 就需要先知道, 服务器所在的位置.,这里的ip和端口号都是服务器的
  10.     public TcpEchoClient(String serverIp, int serverPort) throws IOException {
  11.         // 这个 new 操作完成之后, 就完成了 tcp 连接的建立.
  12.         //这一段代码本质做了相当多事,在网络原理中会讲述到
  13.         socket = new Socket(serverIp, serverPort);
  14.     }
  15.     public void start() {
  16.         System.out.println("客户端启动");
  17.         Scanner scannerConsole = new Scanner(System.in);
  18.         try (InputStream inputStream = socket.getInputStream();
  19.              OutputStream outputStream = socket.getOutputStream()) {
  20.             while (true) {
  21.                 // 1. 从控制台输入字符串.
  22.                 System.out.print("-> ");
  23.                 String request = scannerConsole.next();
  24.                 // 2. 把请求发送给服务器
  25.                 PrintWriter printWriter = new PrintWriter(outputStream);
  26.                 //    使用 println 带上换行. 后续服务器读取请求, 就可以使用 scanner.next 来获取了
  27.                 printWriter.println(request);
  28.                 //    不要忘记 flush, 确保数据是真的发送出去了!!
  29.                 printWriter.flush();
  30.                 // 3. 从服务器读取响应.
  31.                 Scanner scannerNetwork = new Scanner(inputStream);
  32.                 String response = scannerNetwork.next();
  33.                 // 4. 把响应打印出来
  34.                 System.out.println(response);
  35.             }
  36.         } catch (IOException e) {
  37.             e.printStackTrace();
  38.         }
  39.     }
  40.     public static void main(String[] args) throws IOException {
  41.         TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);
  42.         client.start();
  43.     }
  44. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

万万哇

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表