为了保证制作简历的安全性和流畅性,建议您使用Chrome浏览器进行访问
# 先马后看
别犹豫了,马就完事了。在这里每个人都是分享者,你可以分享技能/干货/安装包/电影/图书等等宇宙内的所有资源。
···
1627人正在讨论
#
刘嘿嘿
上海立信会计金融学院·2022届

Java 网络编程学习笔记(内含长代码)

Java 网络编程学习笔记 前置概念 Java IO 模型 IO 模型 对应的 Java 版本 BIO(同步阻塞 IO) 1.4 之前 NIO(同步非阻塞 IO) 1.4 AIO(异步非阻塞 IO) 1.7 Linux 内核 IO 模型 阻塞 IO 最传统的一种 IO 模型,在读写数据过程中会发生阻塞。 当用户线程发出 IO 请求后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回 IO 执行结果给用户线程,用户线程解除阻塞状态并开始处理数据。 对应于 Java 中的 BIO。 非阻塞 IO 当用户线程发起 IO 请求后并不需要等待,即使内核数据还没有准备好也会马上得到内核返回的一个结果,用户线程可以之后再次询问内核。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么内核就将数据拷贝到用户线程并通知用户线程。 在非阻塞 IO 中,用户线程需要不断询问内核数据是否准备就绪,在数据未就绪时可以处理其他任务。 多路复用 IO 在多路复用 IO 模型中会有一个 Selector 线程不断轮询多个 Socket 的状态,只有当 Socket 真正有读写事件时才通知用户线程进行实际的 IO 读写操作。阻塞 IO 和 非阻塞 IO 模型需要为每个 Socket 建立一个单独的线程处理数据,而多路复用 IO 只需要一个线程管理多个 Socket,并且只在真正有读写事件时才会使用操作系统的 IO 资源,大大节约了系统资源。 在非阻塞 IO 中不断询问 Socket 状态是通过用户线程进行的,而在多路复用 IO 中轮询每个 Socket 状态是内核在进行的,效率要比用户线程高。 对于多路复用 IO 模型来说在事件响应体很大时,Selector 线程会成为性能瓶颈,导致后续事件无法及时处理,影响下一轮事件轮询,因此实际应用中方法体内不做复杂逻辑处理,只做数据的接收和转发,而将具体业务操作转发给业务线程处理。 对应于 Java 中的 NIO,在 NIO 中通过 selector.select() 去查询每个 Channel 是否有事件到达,如果没有事件到达用户线程会一直阻塞,因此 NIO 也会导致用户线程的阻塞。 信号驱动 IO 当用户线程发起一个 IO 请求操作,会给对应的 Socket 注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。 一般用于 UDP 中。 异步 IO 当用户线程发起异步 read 操作后,立刻就可以开始去做其它事。另一方面,从内核的角度,当它收到一个异步 read 之后会立刻返回一个状态,说明请求是否成功发起,用户线程不会任何阻塞。然后内核会等待数据准备完成并将数据拷贝到用户线程,完成后内核会给用户线程发送一个信号通知 read 操作已完成。 用户线程完全不需要关心实际的整个 IO 操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示 IO 操作已经完成,就可以直接去使用数据了。 在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成,用户线程不需要再次调用 IO 函数进行具体的读写。在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作。 对应于 Java 中的 AIO。 URL 解析与构造 当在浏览器输入: http://www.google.com 时, 可能发出的实际请求是: http://www.google.com:80/search?q=test&safe=strict 其中 http 表示应用协议,www.google.com 表示域名,80 表示端口(可以明确指定要进行数据交换的进程),search 表示路径(请求提供的服务),q=test&safe=strict 表示请求参数。 其中域名通过 DNS 域名解析系统将域名解析为主机的 IP 地址,解析时是从右向左解析的,www.google.com 实际上是www.google.com.root,.root 是根域名,一般会省略。 域名的层级如下: 域名层级 实例 根域名 .root 顶级域名 .com、 .edu 、 .org 次级域名 .google 、 .baidu 、 .qq 主机名 www DNS 本质是一个分布式数据库,域名对应 IP 地址的映射关系存储在不同层级的域名服务器上,查询方式分为递归查询和迭代查询。 递归查询:浏览器将域名发给 DNS 客户端,DNS 客户端将查询请求发送给根域名服务器,根域名服务器如果知道对应 IP 地址将返回结果,否则发送给其已知的顶级域名服务器查询,顶级域名如果知道对应的 IP 地址就返回结果,否则就发给其已知的二级域名服务器查询,二级域名服务器也进行类似操作,最终将结果一路返回给浏览器。 迭代查询:DNS 客户端将请求发送给根域名服务器,如果根域名服务器不知道对应 IP 地址不会去请求顶级域名服务器,而是把已知的顶级域名服务器返回给 DNS 客户端(不会帮 DNS 客户端去查询),DNS 客户端得到顶级域名服务器地址后就再去请求顶级域名服务器发送查询请求,以此类推。 不管使用哪种方式,一旦查询成功,都会将结果缓存在查询经过的域名服务器、DNS 客户端或浏览器上。根域名服务器很少,一般其地址都内置在 DNS 客户端中。 网络分层 网络分层 数据格式 协议 作用 应用层 报文 HTTP、FTP、SMTP 通过应用进程之间的交互来完成特定网络应用。 运输层 报文段/用户数据报 TCP、UDP 向两台主机进程之间的通信提供的数据传输服务。 网络层 分组/包 IP、ICMP、IGMP、ARP 为分组交换网上的不同主机提供通信服务。 数据链路层 帧 PPP、CSMA/CD 将网络层 IP 数据报组装成帧,在两个相邻结点之间的链路上传输。 物理层 0、1电信号 FDDI 屏蔽掉传输媒体和通信手段的差异。 java.io 网络编程的本质是进程间的通信,通信的基础是 IO 模型 IO 流分类: java.io 包中用到了装饰器模式: 例如创建 BufferedInputStream 对象时必须传入一个 InputStream 对象(如 FileInputStream 对象)作为参数,可以利用缓冲区在内存读写数据提高效率。 Socket 概述 Socket 也是一种数据源,是网络通信的端点,可以把 Socket 理解为一个唯一的 IP 地址和端口号,例如 127.0.0.1:8080。 发送数据: 应用进程创建 Socket 并绑定到网卡的驱动程序,通过 Socket 发送数据,网卡驱动程序从 Socket 读取数据并发送。 接收数据: 应用进程创建 Socket 并绑定到网卡的驱动程序,网卡驱动程序从网络中接收到数据并传输给 Socket,Socket 将数据再传输给应用进程。 同步/异步/阻塞/非阻塞 同步和异步是通信机制,阻塞和非阻塞是调用状态。 同步 IO 是用户线程发起 I/O 请求后需要等待或者轮询内核 I/O 操作完成后才能继续执行。 异步 IO 是用户线程发起 I/O 请求后仍可以继续执行,当内核 I/O 操作完成后会通知用户线程,或者调用用户线程注册的回调函数。 阻塞 IO 是指 I/O 操作需要彻底完成后才能返回用户空间 。 非阻塞 IO 是指 I/O 操作被调用后立即返回一个状态值,无需等 I/O 操作彻底完成。 线程池 线程池可以通过复用线程,减小线程创建和销毁的开销,提高程序效率。Executors 类提供了几种静态方法直接创建线程池: newSingleThreadExecutor 线程池中只有一个线程,不断复用 newFixedThreadPool 线程池中的线程数量是固定的 newCachedThreadPool 提交的任务如果有空闲线程则处理,否则创建一个新线程来处理任务 newScheduledThreadPool 提交的任务可以在指定的时间执行 BIO 同步阻塞 BIO 即同步阻塞式 IO,每一个客户端请求都对应一个线程来处理。 服务器端通常由一个独立的 Acceptor 线程负责监听客户端的连接。一般通过在 while(true) 循环中调用 accept() 方法接收客户端连接的方式监听连接请求,请求一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成, 可以通过多线程来支持多个客户端的连接。 模拟 TCP 通信 Socket类 是客户端的 Socket 连接,使用步骤: 创建对象时要提供服务器的主机和端口参数。 数据通信结束后通过 close 方法关闭连接。 ServerSocket 类是服务器端的 Socket 连接,使用步骤: 创建对象时要提供端口参数进行绑定,服务器会监听该端口,任何发送到该端口的信息都会被服务器接收并处理。 通过 accept 方法获取客户端连接,通过该连接与客户端进行数据通信,是一种阻塞式调用。 服务器的代码: 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class Server {       public static void main(String[] args) {         // 退出条件         final String QUIT = "quit";         // 服务器端口         final int PORT = 8080;         // 服务器的 socket 对象         ServerSocket serverSocket = null;           try {             // 绑定监听端口             serverSocket = new ServerSocket(PORT);             System.out.println("启动服务器,监听端口:" + PORT);               while (true){                 // 等待客户端连接                 Socket socket = serverSocket.accept();                 System.out.println("客户端[" + socket.getPort() + "]已连接");                 // 获取和客户端通信的字符输入流和字符输出流                 BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));                 BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));                   // 读取客户端发送的消息                 String message;                 while ((message = br.readLine()) != null){                     System.out.println("客户端[" + socket.getPort() + "]发送消息:" + message);                       // 回复客户端                     bw.write("服务器已经收到消息:" + message + "\n");                     bw.flush();                       // 查看客户端是否关闭                     if (QUIT.equals(message)){                         System.out.println("客户端[" + socket.getPort() + "]已断开连接");                         break;                     }                 }             }         } catch (Exception e) {             e.printStackTrace();         } finally {             // 释放资源             if(serverSocket != null){                 try {                     serverSocket.close();                     System.out.println("关闭 serverSocket");                 } catch (IOException e) {                     e.printStackTrace();                 }             }         }     } } 客户端的代码: 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 public class Client {       public static void main(String[] args) {         // 退出条件         final String QUIT = "quit";         // 服务器主机和端口         final String HOST = "127.0.0.1";         final int PORT = 8080;         // 客户端的 socket 对象         Socket socket = null;         BufferedWriter bw = null;           try {             // 创建 socket             socket = new Socket(HOST, PORT);               // 获取和服务器通信的字符输入流和字符输出流             BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));             bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));               // 等待用户输入信息             while (true) {                 System.out.println("请输入要发送的消息:");                 BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));                 String input = consoleReader.readLine();                   // 发送消息给服务器                 bw.write(input + "\n");                 bw.flush();                   // 读取服务器返回的消息                 String message= br.readLine();                 if (message != null) {                     System.out.println("服务器发送消息:" + message);                 }                   // 查看用户是否退出                 if(QUIT.equals(input)){                     break;                 }             }         }catch (Exception e){             e.printStackTrace();         }finally {             // 释放资源             try {                 if(bw != null) {                     bw.close();                     System.out.println("关闭 socket");                 }             } catch (IOException e) {                 e.printStackTrace();             }         }     } } 基于 BIO 的多人聊天室 基于 BIO 模型 支持多人同时在线 每个用户的发言都会被转发给其他在线用户 一共由四个类组成: 服务器类:ChatServer 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ChatServer {       /** 服务器端口 */     private final int PORT = 8080;       /** 结束条件 */     private final String QUIT = "quit";        /** 服务器端的 socket 连接 */     private ServerSocket serverSocket;        /** 存储在线用户 */     private final Map<Integer, Writer> clients;       public ChatServer(){         clients = new HashMap<>();     } 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 /**  * 添加在线用户  * @param socket 上线的客户端 socket 连接对象  * @throws IOException 可能产生的异常  */ public synchronized void addClient(Socket socket) throws IOException {     if(socket != null) {         int port = socket.getPort();         BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));         clients.put(port, writer);         System.out.println("客户端[" + port + "]已连接到服务器");     } }   /**  * 移除离线用户  * @param socket 下线的客户端 socket 连接对象  * @throws IOException 可能产生的异常  */ public synchronized void removeClient(Socket socket) throws IOException {     if(socket != null){         int port = socket.getPort();         // 如果当前用户仍在在线用户集合中则移除         if(clients.containsKey(port)){             clients.get(port).close();         }         clients.remove(port);         System.out.println("客户端[" + port + "]已断开连接");     } }   /**  * 转发消息给其他用户  * @param socket 要发送消息的客户端的 socket 连接对象  * @param message 要发送的消息  * @throws IOException 可能产生的异常  */ public synchronized void forwardMessage(Socket socket, String message) throws IOException {     for (Integer port : clients.keySet()){         // 遍历在线用户,通过比较端口号判断当前用户是否是发送消息的用户,如果不是则转发消息         if(!port.equals(socket.getPort())){             Writer writer = clients.get(port);             writer.write(message);             writer.flush();         }     } }   /**  * 查看用户是否准备退出  * @param message 用户发送的消息  * @return true 表示准备退出,反之  */ public boolean checkQuit (String message) {     return QUIT.equals(message); }   /**  * 释放资源  */ public synchronized void close() {     if(serverSocket != null){         try {             serverSocket.close();             System.out.println("关闭 serverSocket");         } catch (IOException e) {             e.printStackTrace();         }     } }   /**  * 启动服务器端开始监听  */ public void start() {     try {         // 绑定监听端口         serverSocket = new ServerSocket(PORT);         System.out.println("服务器已经启动,监听端口:" + PORT);         while (true) {             // 等待获取客户端连接             Socket socket = serverSocket.accept();             // 创建线程处理客户端连接             new Thread(new ChatHandler(this, socket)).start();         }     } catch (IOException e) {         e.printStackTrace();     } finally {         //释放资源         close();     } }   /**  * 用 main 线程模拟 Acceptor 线程  * @param args main方法参数  */ public static void main(String[] args) {     ChatServer chatServer = new ChatServer();     // 启动服务器     chatServer.start(); } } 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 - 服务器处理客户端连接类:ChatHandler     ```java   public class ChatHandler implements Runnable{         /** 服务端 */       private ChatServer chatServer;         /** 客户端连接 */       private Socket socket;         public ChatHandler(ChatServer chatServer, Socket socket) {           this.chatServer = chatServer;           this.socket = socket;       }         /**        * 处理客户端连接        */       @Override       public void run() {           try {               // 添加新上线用户               chatServer.addClient(socket);               // 读取用户发送的信息               BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));               String message;               while ((message = reader.readLine()) != null) {                   String forwardMessage = "客户端[" + socket.getPort() + "]发送了一条消息:" + message + "\n";                   System.out.println(forwardMessage);                   // 转发消息给其他在线用户                   chatServer.forwardMessage(socket, forwardMessage);                   // 查看用户是否准备退出                   if(chatServer.checkQuit(message)){                       break;                   }               }           } catch (IOException e) {               e.printStackTrace();           } finally {               // 移除离线用户               try {                   chatServer.removeClient(socket);               } catch (IOException e) {                   e.printStackTrace();               }           }       }   } 客户端类:ChatClient 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 public class ChatClient {       /** 服务器主机 */     private final String HOST = "127.0.0.1";       /** 服务器端口 */     private final int PORT = 8080;       /** 结束条件 */     private final String QUIT = "quit";       /** 和服务器通信的 socket */     private Socket socket;       /** 和服务器通信的输入流 */     private BufferedReader reader;       /** 和服务器通信的输出流 */     private BufferedWriter writer;       /**      * 发送信息给服务器      * @param message 要发送的信息      * @throws IOException 可能抛出的异常      */     public void send(String message) throws IOException {         // 确保输出流是开放状态         if(!socket.isOutputShutdown()){             writer.write(message + "\n");             writer.flush();         }     }       /**      * 从服务器端接收消息      * @return 返回接受的消息      * @throws IOException 可能抛出的异常      */     public String receive() throws IOException {         String message = null;         // 确保输入流是开放状态         if(!socket.isInputShutdown()){             message = reader.readLine();         }         return message;     }       /**      * 查看用户是否准备退出      * @param message 用户发送的消息      * @return true 表示准备退出,反之      */     public boolean checkQuit (String message) {         return QUIT.equals(message);     }       /**      * 释放资源      */     public synchronized void close() {         if(writer != null){             try {                 writer.close();                 System.out.println("关闭 socket");             } catch (IOException e) {                 e.printStackTrace();             }         }     }       /**      * 启动客户端      */     public void start() {         try {             // 创建 socket             socket = new Socket(HOST, PORT);             // 创建 IO 流             reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));             writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));             // 创建新线程处理用户的输入             new Thread(new UserInputHandler(this)).start();             // 读取服务器转发的消息             String message;             while ((message = receive()) != null){                 System.out.println(message);             }         } catch (IOException e) {             e.printStackTrace();         } finally {             //释放资源             close();         }     }       public static void main(String[] args) {         ChatClient chatClient = new ChatClient();         // 启动客户端         chatClient.start();     } } 客户端处理用户输入类:UserInputHandler 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class UserInputHandler implements Runnable{       /** 客户端 */     private ChatClient chatClient;       public UserInputHandler(ChatClient chatClient) {         this.chatClient = chatClient;     }       /**      * 处理用户输入信息      */     @Override     public void run() {         try {             // 等待用户输入信息             BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));             while (true) {                 String message = consoleReader.readLine();                 // 向服务器发送消息                 chatClient.send(message);                 // 检查用户是否准备退出                 if(chatClient.checkQuit(message)){                     break;                 }             }         }catch (IOException e){             e.printStackTrace();         }     } } 伪异步 IO 改进多人聊天室 在基于 BIO 的多人聊天室中,一个客户端请求对应着一个线程,当用户数量很大时线程的开销也会很大。我们可以通过线程池来实现伪异步 IO 来进行优化,限制线程的总数量,实现线程的复用。 在 ChatServer 中添加属性并修改构造器: 复制代码 1 2 3 4 5 6 7 8 /** 线程池 */ private ExecutorService executorService;   public ChatServer(){     // 限制服务器处理请求的线程数为 10     executorService = Executors.newFixedThreadPool(10);     clients = new HashMap<>(); } 同时修改 ChatServer 的 start 方法中创建线程部分的代码,改为使用线程池: 复制代码 1 2 3 4 5 6 while (true) {     // 等待获取客户端连接     Socket socket = serverSocket.accept();     // 将处理请求任务提交给线程池执行     executorService.execute(new ChatHandler(this, socket)); } NIO 同步非阻塞 BIO 中的阻塞: ServerSocket 的 accept() InputStream 和 OutputStream 的读和写 无法在同一个线程中处理多个 Stream IO 可以使用非阻塞的 IO 即 NIO,在 NIO 中: 使用 Channel 代替 Stream,Channel 是双向的,既可以读数据也可以写数据,既可以阻塞读写也可以非阻塞读写 使用 Selector 监控多条 Channel 可以在一个线程里处理多个 Channel IO NIO 的核心组件: Selector 选择器或多路复用器,主要作用是轮询检查多个 Channel 的状态,判断 Channel 注册的事件是否发生,即判断 Channel 是否处于可读或可写状态。 在使用之前需要将 Channel 注册到 Selector 上,注册之后会得到一个 SelectionKey。 SelectionKey 的相关方法: interestOps() : 查看 Channel 对象注册的状态集合,例如 Accept(接收到一个客户端请求)、Read、Wirte 状态。 readyOps() :查看 Channel 对象可操作的状态集合 channel() :获取 Channel 对象 selector():获取 Selector 对象 attachment():附加一个对象或更多信息 使用 Selector 选择 Channel: select():获取处于就绪状态的 Channel 对象数量 Channel 双向通道,替换了 IO 中的 Stream,不能直接访问数据,要通过 Buffer 来读写数据,也可以和其他 Channel 交互。 分类: FileChannel(处理文件) DatagramChannel(处理 UDP 数据) SocketChannel(处理 TCP 数据,用作客户端) ServerSocketChannel(处理 TCP 数据,用作服务器端)。 Buffer 缓冲区,本质是一块可读写数据的内存,这块内存被包装成 NIO 的 Buffer 对象,用来简化数据的读写。Buffer 的三个重要属性:position 表示下一次读写数据的位置,limit 表示本次读写的极限位置,capacity 表示最大容量。 flip() 将写转为读,底层实现原理是把 position 置 0,并把 limit 设为当前的 position 值。 通过clear() 将读转为写模式(用于读完全部数据的情况,把 position 置 0,limit 设为 capacity)。 通过compact() 将读转为写模式(用于没有读完全部数据,存在未读数据的情况,让 position 指向未读数据的下一个)。 通道的方向和 Buffer 的方向是相反的,读取数据相当于向 Buffer 写入,写出数据相当于从 Buffer 读取。 使用步骤: 向 Buffer 写入数据 调用 flip 方法将 Buffer 从写模式切换为读模式 从 Buffer 中读取数据 调用 clear 或 compact 方法来清空 Buffer 本地文件拷贝 使用 4 种方法进行本地文件的拷贝,分别是不使用/使用缓冲区的 BIO 方式,使用 Buffer/直接使用 Channel 的 NIO 方式。 其中不使用缓冲区的 BIO 效率最低,其余效率差不多,直接使用 Channel 的 NIO 方式效率较高。 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 /** 拷贝文件的接口 */ public interface FileCopy {       /**      * 拷贝文件      * @param source 拷贝文件源      * @param target 拷贝文件目的地      */     void copyFile(File source, File target); }   class FileCopyDemo {       private static void close(Closeable closeable) {         if (closeable != null) {             try {                 closeable.close();             } catch (IOException e) {                 e.printStackTrace();             }         }     }       public static void main(String[] args) {           // BIO 不使用缓冲区         FileCopy noBufferStreamCopy = (source, target) -> {             InputStream is = null;             OutputStream os = null;             try {                 is = new FileInputStream(source);                 os = new FileOutputStream(target);                 int read;                 while ((read = is.read()) != -1) {                     os.write(read);                 }             } catch (IOException e) {                 e.printStackTrace();             } finally {                 close(is);                 close(os);             }         };           // BIO 使用缓冲区         FileCopy bufferStreamCopy = (source, target) -> {             InputStream is = null;             OutputStream os = null;             try {                 is = new BufferedInputStream(new FileInputStream(source));                 os = new BufferedOutputStream(new FileOutputStream(target));                 int read;                 byte[] buffer = new byte[1024];                 while ((read = is.read(buffer)) != -1) {                     os.write(buffer, 0 ,read);                 }             } catch (IOException e) {                 e.printStackTrace();             } finally {                 close(is);                 close(os);             }         };           // NIO 使用 Buffer 交互         FileCopy nioBufferCopy = (source, target) -> {             FileChannel in = null;             FileChannel out = null;             try {                 in = new FileInputStream(source).getChannel();                 out = new FileOutputStream(target).getChannel();                 // 分配一个大小为 1024KB 的缓冲区                 ByteBuffer buffer = ByteBuffer.allocate(1024);                 while ((in.read(buffer)) != -1) {                     // 将 Buffer 的写模式转为读模式(读取数据 = 向 Buffer 写)                     buffer.flip();                     // 只要还有数据就全部写出                     while (buffer.hasRemaining()) {                         out.write(buffer);                     }                     // 将 Buffer 的读模式转为写模式(写出数据 = 从 Buffer 读)                     buffer.clear();                 }             } catch (IOException e) {                 e.printStackTrace();             } finally {                 close(in);                 close(out);             }         };           //NIO 直接使用 Channel 交互         FileCopy nioTransferCopy = (source, target) -> {             FileChannel in = null;             FileChannel out = null;             try {                 in = new FileInputStream(source).getChannel();                 out = new FileOutputStream(target).getChannel();                 long transferred = 0;                 long size = in.size();                 while (transferred != size) {                     transferred += in.transferTo(0, size, out);                 }             } catch (IOException e) {                 e.printStackTrace();             } finally {                 close(in);                 close(out);             }         };     } } 使用 NIO 改写多人聊天室 服务器端代码: 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 public class ChatServer {       /** 服务器端口 */     private static final int DEFAULT_PORT = 8080;       /** 结束条件 */     private static final String QUIT = "quit";       /** 缓冲区大小 */     private static final int BUFFER_SIZE = 1024;       /** 服务端 Channel */     private ServerSocketChannel server;       /** Selector 选择器 */     private Selector selector;       /** 读缓冲 */     private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);       /** 写缓冲 */     private ByteBuffer writeBuffer = ByteBuffer.allocate(BUFFER_SIZE);       /** 使用的编码 */     private Charset charset = StandardCharsets.UTF_8;       /** 可以自定义服务器端口 */     private int port;       public ChatServer() {         this(DEFAULT_PORT);     }     public ChatServer(int port){         this.port = port;     }       /**      * 查看用户是否准备退出      * @param message 用户发送的消息      * @return true 表示准备退出,反之      */     private boolean checkQuit (String message) {         return QUIT.equals(message);     }       /** 释放资源 */     private void close(Closeable closeable) {         if(closeable != null){             try {                 closeable.close();             } catch (IOException e) {                 e.printStackTrace();             }         }     }       private String getClientName(SocketChannel client) {         return "客户端[" + client.socket().getPort() + "]";     }       private void forwardMessage(SocketChannel client, String message) throws IOException {         // 遍历所有注册的 channel         Set<SelectionKey> selectionKeys = selector.keys();         for(SelectionKey selectionKey : selectionKeys) {             Channel channel = selectionKey.channel();             // 是服务端的 channel 则跳过             if (channel instanceof  ServerSocketChannel) {                 continue;             }             // channel 有效且不是发送消息的客户端 channel             if (selectionKey.isValid() && !client.equals(channel)) {                 // 向 Buffer 写入数据,准备发送                 writeBuffer.clear();                 writeBuffer.put(charset.encode(getClientName(client) + ":" + message));                 writeBuffer.flip();                 // 从 Buffer 读取数据并发送                 while (writeBuffer.hasRemaining()) {                     ((SocketChannel)channel).write(writeBuffer);                 }             }         }     }       // 读取消息     private String receive(SocketChannel client) throws IOException {         // 清空残留信息,将读模式转为写模式         readBuffer.clear();         // 接收消息,相等于向 Buffer 写数据         while (client.read(readBuffer) > 0);         // 写完后将写模式转为读模式         readBuffer.flip();         return String.valueOf(charset.decode(readBuffer));     }       /** 处理事件 */     private void handles(SelectionKey selectionKey) throws IOException{         // 如果触发了 ACCEPT 事件 —— 和客户端建立了连接         if (selectionKey.isAcceptable()) {             ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();             // 获取客户端的 channel             SocketChannel client = server.accept();             // 设为非阻塞调用模式             client.configureBlocking(false);             // 注册客户端的 READ 事件到 selector             client.register(selector, SelectionKey.OP_READ);             System.out.println(getClientName(client)  + "已连接服务器");         }else if (selectionKey.isReadable()){             // 如果触发了 READ 事件 —— 客户端发送了数据             SocketChannel client = (SocketChannel) selectionKey.channel();             // 读取数据             String message = receive(client);             if (message.isEmpty()) {                 // 消息为空,客户端异常,取消监听                 selectionKey.cancel();                 // 刷新                 selector.wakeup();             } else {                 // 转发消息                 forwardMessage(client, message);                 // 查看用户是否准备退出                 if(checkQuit(message)) {                     selectionKey.cancel();                     selector.wakeup();                     System.out.println(getClientName(client) + "已断开连接");                 }             }         }     }       /** 启动服务器端开始监听 */     private void start() {         try {             server = ServerSocketChannel.open();             // 设置非阻塞调用             server.configureBlocking(false);             // 绑定监听端口             ServerSocket serverSocket = server.socket();             serverSocket.bind(new InetSocketAddress(port));             // 创建 selector             selector = Selector.open();             // 注册服务器 Channel 的 ACCEPT(接收客户端请求) 事件到 selector             server.register(selector, SelectionKey.OP_ACCEPT);             System.out.println("启动服务器,监听端口:" + port + "...");             // 不断监听请求             while (true) {                 // select 是阻塞式调用,如果成功调用说明已有 Channel 就绪                 selector.select();                 // select 监听到的所有事件                 Set<SelectionKey> selectionKeys = selector.selectedKeys();                 for (SelectionKey selectionKey : selectionKeys) {                     // 处理被触发的事件                     handles(selectionKey);                 }                 // 处理后清空                 selectionKeys.clear();             }         } catch (IOException e) {             e.printStackTrace();         } finally {             close(selector);         }     }       /**      * @param args main方法参数      */     public static void main(String[] args) {         ChatServer chatServer = new ChatServer();         chatServer.start();     } } 客户端代码: 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 public class ChatClient {       /** 服务器默认主机 */     private static final String DEFAULT_HOST = "127.0.0.1";       private String host;       /** 服务器默认端口 */     private static final int DEFAULT_PORT = 8080;       private int port;       /** 结束条件 */     private static final String QUIT = "quit";       /** 和服务器通信的 socket */     private SocketChannel client;       /** 缓冲区大小 */     private static final int BUFFER_SIZE = 1024;       /** 读取缓冲 */     private ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);       /** 写出缓冲 */     private ByteBuffer writeBuffer = ByteBuffer.allocate(BUFFER_SIZE);       /** Selector 选择器 */     private Selector selector;       /** 使用的编码 */     private Charset charset = StandardCharsets.UTF_8;       public ChatClient() {         this(DEFAULT_HOST, DEFAULT_PORT);     }       public ChatClient(String host, int port) {         this.host = host;         this.port = port;     }         /**      * 向服务器发送消息      * @param message 要发送的消息      */     public void send(String message) throws IOException {         if (message.isEmpty()) {             return;         }         // 读模式转写模式         writeBuffer.clear();         writeBuffer.put(charset.encode(message));         // 写模式转读模式         writeBuffer.flip();         while (writeBuffer.hasRemaining()) {             client.write(writeBuffer);         }         // 判断用户是否准备退出         if (checkQuit(message)) {             close(selector);         }     }       /**      * 查看用户是否准备退出      * @param message 用户发送的消息      * @return true 表示准备退出,反之      */     public boolean checkQuit (String message) {         return QUIT.equals(message);     }       /** 释放资源 */     private void close(Closeable closeable) {         if(closeable != null){             try {                 closeable.close();             } catch (IOException e) {                 e.printStackTrace();             }         }     }       /** 启动客户端 */     public void start() {         try {             // 创建 channel             client = SocketChannel.open();             // 设置非阻塞调用             client.configureBlocking(false);             // 创建 selector             selector = Selector.open();             // 注册客户端 channel 的 CONNECT 事件到 selector             client.register(selector, SelectionKey.OP_CONNECT);             // 客户端向服务器发起连接请求             client.connect(new InetSocketAddress(host, port));             while (true) {                 // select 是阻塞式调用,如果成功调用说明已有 Channel 就绪                 selector.select();                 // select 监听到的所有事件                 Set<SelectionKey> selectionKeys = selector.selectedKeys();                 for (SelectionKey selectionKey : selectionKeys) {                     // 处理被触发的事件                     handles(selectionKey);                 }                 // 处理后清空                 selectionKeys.clear();             }         } catch (IOException e) {             e.printStackTrace();         } catch (ClosedSelectorException e) {             // 用户正常退出         } finally {             close(selector);         }     }       private void handles(SelectionKey selectionKey) throws IOException {         // 如果触发了 CONNECT 事件 —— 连接就绪事件         if (selectionKey.isConnectable()) {             SocketChannel client = (SocketChannel) selectionKey.channel();             // 如果连接已经建立了             if (client.isConnectionPending()) {                 client.finishConnect();                 // 处理用户输入                 new Thread(new UserInputHandler(this)).start();             }             // 注册客户端的 READ 事件到 selector             client.register(selector, SelectionKey.OP_READ);         }else if (selectionKey.isReadable()) {             // 如果触发了 READ 事件 —— 服务器转发了数据             SocketChannel client = (SocketChannel) selectionKey.channel();             // 读取数据             String message = receive(client);             if (message.isEmpty()) {                 // 消息为空,服务器异常,取消监听                 close(selector);             } else {                 System.out.println(message);             }         }     }       private String receive(SocketChannel client) throws IOException {         readBuffer.clear();         while (client.read(readBuffer) > 0);         readBuffer.flip();         return String.valueOf(charset.decode(readBuffer));     }       public static void main(String[] args) {         ChatClient chatClient = new ChatClient();         // 启动客户端         chatClient.start();     } } 处理用户输入的类 UserInputHandler 代码不变。 AIO 异步非阻塞 服务器端通道AsynchronousServerSocketChannel,通过 accept 获取客户端连接 客户端通道 AsynchronousSocketChannel,通过 connect 连接服务器 实现方式: 通过 Future 的 get 阻塞式调用 通过实现 CompletionHandler 接口,重写回调方法 completed 和 failed 回音聊天室 实现功能:可以将用户发送给服务器的消息再返回给用户,类似“回音”效果。 服务器代码: 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 public class Server {       final String LOCALHOST = "127.0.0.1";     final int DEFAULT_PORT = 8080;       /** 异步的服务器端 channel */     AsynchronousServerSocketChannel serverChannel;       public static void main(String[] args) {         new Server().start();     }       public void start() {         try {             // 绑定监听的端口             serverChannel = AsynchronousServerSocketChannel.open();             serverChannel.bind(new InetSocketAddress(LOCALHOST, DEFAULT_PORT));             System.out.println("启动服务器,监听端口:" + DEFAULT_PORT);             while (true) {                 // 异步调用,接收客户端请求                 serverChannel.accept(null, new AcceptHandler());                 // 控制调用频率                 System.in.read();             }         } catch (IOException e) {             e.printStackTrace();         } finally {             close(serverChannel);         }     }       /**      * 释放资源      * @param closeable 要释放的资源      */     private void close(Closeable closeable) {         if (closeable != null) {             try {                 closeable.close();                 System.out.println("关闭" + closeable);             } catch (IOException e) {                 e.printStackTrace();             }         }     }       /**      * 处理异步 ACCEPT 事件的方法,第一个泛型参数是成功回调的 result 类型,第二个泛型参数是 attachment 类型      */     private class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> {           /**          * 成功的回调方法          * @param result 和服务器建立连接的客户端 channel          * @param attachment 额外数据          */         @Override         public void completed(AsynchronousSocketChannel result, Object attachment) {             // 让服务器继续监听其他客户端             if (serverChannel.isOpen()) {                 serverChannel.accept(null, this);             }             AsynchronousSocketChannel clientChannel = result;             if (clientChannel != null && clientChannel.isOpen()) {                 // 处理异步 read                 ClientHandler clientHandler = new ClientHandler(clientChannel);                 // 读取客户端数据                 ByteBuffer buffer = ByteBuffer.allocate(1024);                 // 附加信息,一个 map 集合                 Map<String, Object> info = new HashMap<>();                 info.put("type", "read");                 info.put("buffer", buffer);                 // 异步调用 read,相当于向 buffer 写数据,buffer 是写模式                 clientChannel.read(buffer, info, clientHandler);             }         }           /**          * 失败的回调方法          * @param exc 异常          * @param attachment 额外数据          */         @Override         public void failed(Throwable exc, Object attachment) {             // 处理错误         }     }       /**      * 处理读写事件,第一个参数表示 Buffer 写入的字节      */     private class ClientHandler implements CompletionHandler<Integer, Object> {           private AsynchronousSocketChannel clientChannel;           ClientHandler(AsynchronousSocketChannel clientChannel) {             this.clientChannel = clientChannel;         }           @Override         public void completed(Integer result, Object attachment) {             Map<String, Object> info = (Map<String, Object>) attachment;             // 判断操作类型是 read 还是 write             String type = (String) info.get("type");             // 读完客户端数据后写出             if ("read".equals(type)) {                 ByteBuffer buffer = (ByteBuffer) info.get("buffer");                 // 将 buffer 从写模式转为读模式                 buffer.flip();                 info.put("type", "write");                 // 写出数据,相当于从 buffer 读                 clientChannel.write(buffer, info, this);                 // 将 buffer 从读模式转为写模式                 buffer.clear();             } else if ("write".equals(type)) {                 // 写出后继续读取客户端数据                 ByteBuffer buffer = ByteBuffer.allocate(1024);                 info.put("type", "read");                 info.put("buffer", buffer);                 // 异步调用 read                 clientChannel.read(buffer, info, this);             }         }           @Override         public void failed(Throwable exc, Object attachment) {             //处理错误         }     } } 客户端代码: 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class Client {       final String LOCALHOST = "127.0.0.1";     final int DEFAULT_PORT = 8080;       /** 异步的客户端 channel */     AsynchronousSocketChannel clientChannel;       public static void main(String[] args) {         new Client().start();     }       public void start() {         try {             // 创建 channel             clientChannel = AsynchronousSocketChannel.open();             Future<Void> future = clientChannel.connect(new InetSocketAddress(LOCALHOST, DEFAULT_PORT));             // get 是阻塞式调用,在客户端和服务器建立连接前会阻塞             future.get();             // 等待用户输入             BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));             while (true) {                 String input = consoleReader.readLine();                 // 将用户消息发送给服务器                 byte[] inputBytes = input.getBytes();                 ByteBuffer buffer = ByteBuffer.wrap(inputBytes);                 Future<Integer> writeResult = clientChannel.write(buffer);                 // 阻塞直到消息成功发送                 writeResult.get();                 // 从服务器读取响应消息                 // 写出数据相当于从 Buffer 读,所以现在将读转为写模式                 buffer.clear();                 Future<Integer> readResult = clientChannel.read(buffer);                 // 阻塞直到成功读取消息                 readResult.get();                 String echo = new String(buffer.array());                 // 读取数据相当于向 Buffer 写,所以现在将写转为读模式                 buffer.flip();                 System.out.println(echo);             }         } catch (Exception e) {             e.printStackTrace();         } finally {             close(clientChannel);         }     }       /**      * 释放资源      * @param closeable 要释放的资源      */     private void close(Closeable closeable) {         if (closeable != null) {             try {                 closeable.close();                 System.out.println("关闭" + closeable);             } catch (IOException e) {                 e.printStackTrace();             }         }     } } 使用 AIO 改写多人聊天室 服务器端: 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 public class ChatServer {       /** 服务器主机 */     private static final String LOCALHOST = "localhost";       /** 服务器端口 */     private static final int DEFAULT_PORT = 8080;       /** 结束条件 */     private static final String QUIT = "quit";       /** 缓冲区大小 */     private static final int BUFFER_SIZE = 1024;       /** 线程池大小 */     private static final int THREAD_POOL_SIZE = 8;       /** 自定义 ChannelGroup */     private AsynchronousChannelGroup channelGroup;       /** 服务端 Channel */     private AsynchronousServerSocketChannel serverChannel;       /** 使用的编码 */     private Charset charset = StandardCharsets.UTF_8;       /** 可以自定义服务器端口 */     private int port;       /** 存储用户在线列表 */     private List<ClientHandler> connectedClients;       public ChatServer() {         this(DEFAULT_PORT);     }       public ChatServer(int port){         this.port = port;         this.connectedClients = new ArrayList<>();     }       /**      * 程序入口      * @param args 参数      */     public static void main(String[] args) {         new ChatServer().start();     }       /**      * 启动服务器      */     private void start() {         try {             // 创建自定义 channel group             ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);             channelGroup = AsynchronousChannelGroup.withThreadPool(executorService);             // 创建 channel 并绑定 group 和端口             serverChannel = AsynchronousServerSocketChannel.open(channelGroup);             serverChannel.bind(new InetSocketAddress(LOCALHOST, port));             System.out.println("启动服务器,监听端口:" + port);             while (true) {                 // 接收客户端请求并处理                 serverChannel.accept(null, new AcceptHandler());                 System.in.read();             }         } catch (IOException e) {             e.printStackTrace();         } finally {             close(serverChannel);         }     }       /**      * 查看用户是否准备退出      * @param message 用户发送的消息      * @return true 表示准备退出,反之      */     private boolean checkQuit (String message) {         return QUIT.equals(message);     }       /**      * 获取客户端名      * @param clientChannel 客户端 channel      * @return 返回客户端名      */     private String getClientName(AsynchronousSocketChannel clientChannel) {         int clientPort = -1;         try {             InetSocketAddress inetSocketAddress = (InetSocketAddress) clientChannel.getRemoteAddress();             clientPort = inetSocketAddress.getPort();         } catch (IOException e) {             e.printStackTrace();         }         return "客户端[" + clientPort + "]";     }       /**      * 服务器转发消息给其他客户端      * @param clientChannel 发送消息的客户端      * @param message 发送的具体消息      */     private synchronized void forwardMessage(AsynchronousSocketChannel clientChannel, String message) {         for (ClientHandler handler : connectedClients){             // 该信息不用再转发到发送信息的客户端             if (!handler.clientChannel.equals(clientChannel)){                 try {                     // 将要转发的信息写入到 buffer 中                     ByteBuffer buffer = charset.encode(getClientName(handler.clientChannel) + ":" + message);                     // 写事件不添加附加信息                     handler.clientChannel.write(buffer,null, handler);                 } catch (Exception e) {                     e.printStackTrace();                 }             }         }     }       /**      * 服务器接收客户端消息      * @param buffer buffer 缓冲区      * @return 返回接收的消息      */     private synchronized String receive(ByteBuffer buffer) {         CharBuffer charBuffer = charset.decode(buffer);         return String.valueOf(charBuffer);     }       /**      * 添加一个新的客户端进到在线列表      * @param handler 客户端对应的 handler      */     private synchronized void addClient(ClientHandler handler) {         connectedClients.add(handler);         System.out.println(getClientName(handler.clientChannel) + "已经连接到服务器");     }       /**      * 将下线用户从在线列表中删除      * @param clientHandler 客户端对应的 handler      */     private synchronized void removeClient(ClientHandler clientHandler) {         connectedClients.remove(clientHandler);         System.out.println(getClientName(clientHandler.clientChannel) + "已断开连接");         // 关闭该客户对应流         close(clientHandler.clientChannel);     }       /**      * 释放资源      */     private void close(Closeable closeable) {         if(closeable != null){             try {                 closeable.close();             } catch (IOException e) {                 e.printStackTrace();             }         }     }       /**      * 处理服务器的 ACCEPT 事件,第一个泛型参数是异步调用方法应该返回的类型,ACCEPT 应该返回一个客户端的 channel      */     private class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, Object> {             @Override         public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {             // 让服务器继续监听其他客户端             if (serverChannel.isOpen()) {                 serverChannel.accept(null, this);             }             // 客户端处于有效状态             if (clientChannel != null && clientChannel.isOpen()) {                 // 处理异步 read                 ClientHandler clientHandler = new ClientHandler(clientChannel);                 // 读取客户端数据                 ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);                 // 将新用户添加到在线用户列表                 addClient(clientHandler);                 // 异步调用 read,向 buffer 写数据,因此此时 buffer 是写模式。                 // 第二个参数表示将 buffer 作为附加信息,第三个参数表示处理异步读的 handler                 clientChannel.read(buffer, buffer, clientHandler);             }         }           @Override         public void failed(Throwable exc, Object attachment) {             System.out.println("连接失败:" + exc);         }       }       /**      * 处理客户端的读写事件      */     private class ClientHandler implements CompletionHandler<Integer, Object> {             private AsynchronousSocketChannel clientChannel;           public ClientHandler(AsynchronousSocketChannel clientChannel) {             this.clientChannel = clientChannel;         }           @Override         public void completed(Integer result, Object attachment) {             ByteBuffer buffer = (ByteBuffer) attachment;             // buffer 附加信息不为空,说明处理的是异步读             if (buffer != null) {                 if (result <= 0) {                     // 客户端异常                     removeClient(this);                 } else {                     // 将 buffer 从写模式转为读模式,准备写出数据                     buffer.flip();                     String message = receive(buffer);                     System.out.println(getClientName(clientChannel) + ":" + message);                     forwardMessage(clientChannel, message);                     // 将 buffer 转回写模式                     buffer.clear();                     // 判断用户是否准备下线                     if (checkQuit(message)){                         // 将用户从在线客户列表中去除                         removeClient(this);                     }else {                         // 如果不是则继续等待读取用户输入的信息                         clientChannel.read(buffer, buffer,this);                     }                 }             }         }           @Override         public void failed(Throwable exc, Object attachment) {             System.out.println("读写失败:"+exc);         }     } } 客户端: 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 public class ChatClient {       private static final String LOCALHOST = "localhost";     private static final int DEFAULT_PORT = 8080;     private static final String QUIT = "quit";     private static final int BUFFER_SIZE = 1024;       private AsynchronousSocketChannel clientChannel;     private Charset charset = StandardCharsets.UTF_8;     private String host;     private int port;       public ChatClient(){         this(LOCALHOST , DEFAULT_PORT);     }       public ChatClient(String host, int port) {         this.host = host;         this.port = port;     }       /**      * 程序入口      * @param args 参数      */     public static void main(String[] args) {         ChatClient client = new ChatClient();         client.start();     }       /**      * 判断用户是否准备下线      * @param msg 用户输入的信息      * @return 用户是否准备下线      */     public boolean checkQuit(String msg){         boolean flag = QUIT.equalsIgnoreCase(msg);         if(flag){             close(clientChannel);         }         return flag;     }       /**      * 客户端发送数据给服务器      * @param message 要发送的数据      */     public void send(String message) {         if (message.isEmpty()) {             return;         }         // 将要发送的消息编码并写入缓冲区         ByteBuffer writeBuffer = charset.encode(message);         // 发送消息         Future<Integer> writeResult = clientChannel.write(writeBuffer);         try {             // 阻塞直到发送结束             writeResult.get();         } catch (InterruptedException | ExecutionException e) {             System.out.println("发送消息失败");             e.printStackTrace();         }     }       /**      * 释放资源      * @param closeable 要释放的资源      */     private void close(Closeable closeable){         if(closeable != null){             try {                 closeable.close();             } catch (IOException e) {                 e.printStackTrace();             }         }     }         /**      * 启动客户端      */     private void start(){         try {             // 打开管道             clientChannel = AsynchronousSocketChannel.open();             // 异步调用connect连接服务器             Future<Void> future = clientChannel.connect(new InetSocketAddress(host, port));             // 阻塞直到客户端和服务器建立连接             future.get();             new Thread(new UserInputHandler(this)).start();             // 读取服务器转发的消息             ByteBuffer readBuffer = ByteBuffer.allocate(BUFFER_SIZE);             while (true) {                 Future<Integer> readResult = clientChannel.read(readBuffer);                 // 阻塞直到读取完毕                 int result = readResult.get();                 // 假如无法读到数据                 if (result <= 0) {                     System.out.println("服务器断开连接");                     close(clientChannel);                     System.exit(1);                 } else {                     // 成功读取到了数据,打印在控制台                     // 先把缓冲区转为读模式                     readBuffer.flip();                     String message = String.valueOf(charset.decode(readBuffer));                     System.out.println(message);                     // 读完将缓冲区转为写模式                     readBuffer.clear();                 }             }           } catch (Exception e) {             e.printStackTrace();         } finally {             close(clientChannel);         }     } } 处理用户输入的类 UserInputHandler 代码不变。 简易 Web 服务器的实现 在客户端向服务器请求资源时,请求的资源可以分为: 静态资源:不因请求的次数或顺序变化,例如 HTML、CSS、GIF、PNG 等。 动态资源:随着请求发起方/发起时间/请求内容等因素变化,例如商品库存量等。客户端发起请求,服务器将请求交给容器,容器再交给 Servlet 处理,处理完后再返回给容器、服务器及客户端。 Tomcat 的结构: Server:Tomocat 服务器最顶层的组件,负责运行 Tomcat 服务器、加载服务器资源和环境变量。 Service:集合了 Connector 和 Engine 的抽象组件,一个 Service 可以包含多个 Service,多个 Connector 和 一个 Engine。 Connector:提供基于不同特定协议的实现,接收解析请求并返回响应。具体包括负责提供给客户端可以和服务器创建连接的端点、接收连接请求和建立连接后的资源请求、将服务器的响应返回给客户等。Connector 本身不会处理请求,会将请求交给 Processor 派遣至 Engine 进行处理。 容器是 Tomcat 用来处理请求的组件,内部的组件按照层级排列,其中 Engine 是容器的顶级组件。Engine 通过解析请求内容将请求交发送给对应的 Host,Host 代表一个虚拟主机,一个 Engine 可以支持对多个虚拟主机的请求。Host 将请求交给 Context 组件,Context 代表一个 Web Application,是 Tomcat 最复杂的组件之一,负责应用资源管理、应用类加载、Servlet 管理、安全管理等。Context 下一层是 Wrapper 组件,Wrapper 是容器最底层的组件,包裹 Servlet 实例,负责管理 Servlet 的生命周期。 处理静态资源请求 HTTPStatus 状态码枚举类 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public enum HTTPStatus {       OK(200, "OK"),     NOT_FOUND(404, "File Not Found");       private int statusCode;     private String reason;       HTTPStatus(int statusCode, String reason) {         this.statusCode = statusCode;         this.reason = reason;     }       public int getStatusCode() {         return statusCode;     }       public String getReason() {         return  reason;     } } ConnectorUtils 工具类 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class ConnectorUtils {       /** 存放静态资源的根路径 根据自己的情况设置 */     public static final String WEB_ROOT = "C:\\Users\\Administrator\\IdeaProjects\\java_study\\test" + File.separator + "webroot";       public static final String PROTOCOL = "HTTP/1.1";       public static final String CARRIAGE = "\r";       public static final String NEWLINE = "\n";       public static final String SPACE = " ";       public static String renderStatus(HTTPStatus status) {         // 协议 + 状态 + 状态码         return PROTOCOL + SPACE + status.getStatusCode() + SPACE + status.getReason() + CARRIAGE + NEWLINE + CARRIAGE + NEWLINE;     } } Request 处理请求 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class Request {       /**      * 默认缓冲区大小      */     private static final int BUFFER_SIZE = 1024;       /**      * 客户端和服务器的 socket 的输入流,通过该流获取请求内容      */     private InputStream inputStream;       /**      * 请求的资源标识符      */     private String URI;       public Request(InputStream inputStream) {         this.inputStream = inputStream;     }       /**      * 获取请求 URI      * @return 返回 URI 字符串      */     public String getURI() {         return URI;     }       /**      * 解析客户端请求      */     public void parse() {         int len = 0;         byte[] buffer = new byte[BUFFER_SIZE];         try {             len = inputStream.read(buffer);         } catch (IOException e) {             e.printStackTrace();         }         // 将读取到的字节数组转为字符串         String requestURL = new String(buffer, 0, len);         URI = parseURI(requestURL);     }       /**      * 解析静态请求的 URI      * @param requestStr 请求文本      * @return 返回 URI      * 例如 GET /index.html HTTP1.1,先找到第一个空格的位置,再找到第二个空格的位置,截取两个位置中间作为结果返回。      */     private String parseURI(String requestStr) {         int index1, index2;         index1 = requestStr.indexOf(' ');         if(index1 != -1) {             index2 = requestStr.indexOf(' ', index1 + 1);             if (index2 != -1) {                 return requestStr.substring(index1 + 1, index2);             }         }         // 无法解析出 URI         return "";     }   } Response 处理响应 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class Response {       /**      * 默认缓冲区大小      */     private static final int BUFFER_SIZE = 1024;       /**      * 客户端请求      */     private Request request;       /**      * 客户端和服务器的 socket 的输出流,通过该流响应客户端      */     private OutputStream outputStream;       public Response(OutputStream outputStream) {         this.outputStream =outputStream;     }       /**      * 设置客户端请求      * @param request 客户端请求      */     public void setRequest(Request request) {         this.request = request;     }       /**      * 响应静态资源的请求      */     public void sendStaticResource() throws IOException {         File file = new File(ConnectorUtils.WEB_ROOT, request.getURI());         try {             // 成功处理             write(file, HTTPStatus.OK);         } catch (IOException e) {             // 失败处理             write(new File(ConnectorUtils.WEB_ROOT, "404.html"), HTTPStatus.NOT_FOUND);         }     }       private void write(File resource, HTTPStatus status) throws IOException {         try (FileInputStream inputStream = new FileInputStream(resource)){             // 响应状态信息             outputStream.write(ConnectorUtils.renderStatus(status).getBytes());             // 响应资源             byte[] buffer = new byte[BUFFER_SIZE];             int length;             while ((length = inputStream.read(buffer)) != -1) {                 outputStream.write(buffer, 0, length);             }         }     }   } StaticProcessor 处理静态资源 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 /**  * 处理静态资源的 processor  */ public class StaticProcessor {       public void process(Request request, Response response) {         try {             response.sendStaticResource();         } catch (IOException e) {             e.printStackTrace();         }     } } Connector 处理服务器连接、请求、响应 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 public class Connector implements Runnable{       private static final int DEFAULT_PORT = 8080;       private ServerSocket serverSocket;       private int port;       public Connector() {         this(DEFAULT_PORT);     }       public Connector(int port) {         this.port = port;     }       public void start() {         Thread thread = new Thread(this);         thread.start();     }       @Override     public void run() {         try {             // 绑定端口             serverSocket = new ServerSocket(port);             System.out.println("服务器已启动,监听端口:" + port);             while (true) {                 // 等待客户端连接                 Socket socket = serverSocket.accept();                 // 客户端输入流                 InputStream inputStream = socket.getInputStream();                 // 客户端输出流                 OutputStream outputStream = socket.getOutputStream();                 // 创建 Request 实例并解析 URI                 Request request = new Request(inputStream);                 request.parse();                 // 创建 Response 实例并设置 request                 Response response = new Response(outputStream);                 response.setRequest(request);                 // 处理静态资源                 StaticProcessor staticProcessor = new StaticProcessor();                 staticProcessor.process(request, response);                 // 处理完后断开连接                 close(socket);             }         } catch (IOException e) {             e.printStackTrace();         } finally {             close(serverSocket);         }     }       /**      * 释放资源      * @param closeable 要关闭的资源      */     private void close(Closeable closeable) {         if (closeable != null) {             try {                 closeable.close();             } catch (IOException e) {                 e.printStackTrace();             }         }     } } BootStrap 服务器启动类 复制代码 1 2 3 4 5 6 7 public final class BootStrap {       public static void main(String[] args) {         // 启动服务器         new Connector().start();     } } ClientTest 客户端测试类 也可以直接在浏览器输入 localhost:8080/index.html 进行测试。 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ClientTest {       public static void main(String[] args) throws IOException {         // 建立客户端 socket,连接服务器         Socket socket = new Socket("localhost", 8080);         // 获取客户端输出流,发送请求         OutputStream outputStream = socket.getOutputStream();         outputStream.write("GET /index.html HTTP/1.1".getBytes());         socket.shutdownOutput();         // 获取客户端输入流,接收响应         InputStream inputStream = socket.getInputStream();         byte[] buffer = new byte[2048];         int length;         while ((length = inputStream.read(buffer)) != -1) {             String receive = new String(buffer, 0 ,length);             System.out.print(receive);         }         socket.shutdownInput();         // 关闭连接         socket.close();     } } 处理动态资源请求 修改 Request 和 Response 让 Request 和 Response 分别实现 ServletRequest 和 ServletResponse 接口,并使用默认的重写方法,只需要修改 Response 中的 getWriter 方法: 复制代码 1 2 3 4 @Override public PrintWriter getWriter() throws IOException {     return new PrintWriter(outputStream); } ServletProcessor 处理动态资源 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 /**  * 处理动态资源的 processor  */ public class ServletProcessor {       public void process(Request request, Response response) {         URLClassLoader servletLoader = null;         try {             servletLoader = getServletLoader();             Servlet servlet = getServlet(servletLoader, request);             servlet.service(request, response);         } catch (Exception e) {             e.printStackTrace();         }     }       URLClassLoader getServletLoader() throws MalformedURLException {         File webroot = new File(ConnectorUtils.WEB_ROOT);         URL webrootURL = webroot.toURI().toURL();         return URLClassLoader.newInstance(new URL[]{webrootURL});     }       Servlet getServlet(URLClassLoader loader, Request request) throws Exception {         // 请求 servlet 时,例如请求 XxxServlet 实际请求 servlet/XxxServlet         String URI = request.getURI();         String servletName = URI.substring(URI.lastIndexOf("/") + 1);         // 通过反射创建 servlet 的实例         Class servletClass = loader.loadClass(servletName);         Servlet servlet = (Servlet) servletClass.getConstructor().newInstance();         return servlet;     } } TimeServlet 动态资源 添加在 webroot下: 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 /**  * 动态资源 servlet  */ public class TimeServlet implements Servlet {     @Override     public void init(ServletConfig servletConfig) throws ServletException {       }       @Override     public ServletConfig getServletConfig() {         return null;     }       @Override     public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {         // 得到响应的输出流         PrintWriter writer = servletResponse.getWriter();         // 写出响应头         writer.println(ConnectorUtils.renderStatus(HTTPStatus.OK));         // 写出当前的时间         writer.println("当前时间:");         writer.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));         // 不添加可能空数据         writer.flush();     }       @Override     public String getServletInfo() {         return null;     }       @Override     public void destroy() {       } } 修改 Connector 的 run 方法 复制代码 1 2 3 4 5 6 7 8 9 10 // 根据 URI 的开头判断资源类型 if (request.getURI().startsWith("/servlet")) {     // 处理动态资源     ServletProcessor servletProcessor = new ServletProcessor();     servletProcessor.process(request, response); } else {     // 处理静态资源     StaticProcessor staticProcessor = new StaticProcessor();     staticProcessor.process(request, response); } ClientTest 客户端测试类 也可以直接在浏览器输入 localhost:8080/servlet/TimeServlet 进行测试。 复制代码 1 2 3 4 5 6 7 8 public class ClientTest {       public static void main(String[] args) throws IOException {         ...         outputStream.write("GET /servlet/TimeServlet HTTP/1.1".getBytes());         ...     } } 使用 NIO 改写 Connector 复制代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 public class Connector implements Runnable{       private static final int DEFAULT_PORT = 8080;       private ServerSocketChannel server;       private Selector selector;       private int port;       public Connector() {         this(DEFAULT_PORT);     }       public Connector(int port) {         this.port = port;     }       public void start() {         Thread thread = new Thread(this);         thread.start();     }       @Override     public void run() {         try {             // 打开 serverSocket的 Channel             server = ServerSocketChannel.open();             // 设置非阻塞调用             server.configureBlocking(false);             // 绑定监听端口             ServerSocket serverSocket = server.socket();             serverSocket.bind(new InetSocketAddress(port));             // 创建 selector             selector = Selector.open();             // 注册服务器 Channel 的 ACCEPT(接收客户端请求) 事件到 selector             server.register(selector, SelectionKey.OP_ACCEPT);             System.out.println("启动服务器,监听端口:" + port + "...");             // 不断监听请求             while (true) {                 // select 是阻塞式调用,如果成功调用说明已有 Channel 就绪                 selector.select();                 // select 监听到的所有事件                 Set<SelectionKey> selectionKeys = selector.selectedKeys();                 for (SelectionKey selectionKey : selectionKeys) {                     // 处理被触发的事件                     handles(selectionKey);                 }                 // 处理后清空                 selectionKeys.clear();             }         } catch (IOException e) {             e.printStackTrace();         } finally {             close(selector);         }     }       /** 处理事件 */     private void handles(SelectionKey selectionKey) throws IOException{         // 如果触发了 ACCEPT 事件 —— 和客户端建立了连接         if (selectionKey.isAcceptable()) {             // 获取客户端的 channel             SocketChannel client = ((ServerSocketChannel)(selectionKey.channel())).accept();             // 设为非阻塞调用模式             client.configureBlocking(false);             // 注册客户端的 READ 事件到 selector             client.register(selector, SelectionKey.OP_READ);         } else {             // 触发的是 READ 事件             // 获取客户端的 channel             SocketChannel client = (SocketChannel) selectionKey.channel();             // 避免设为 BIO 模式报错,取消注册             selectionKey.cancel();             // 由于要使用 BIO,重设为阻塞模式             client.configureBlocking(true);             Socket socket = client.socket();             // 客户端输入流             InputStream inputStream = socket.getInputStream();             // 客户端输出流             OutputStream outputStream = socket.getOutputStream();             // 创建 Request 实例并解析 URI             Request request = new Request(inputStream);             request.parse();             // 创建 Response 实例并设置 request             Response response = new Response(outputStream);             response.setRequest(request);             // 根据 URI 的开头判断资源类型             if (request.getURI().startsWith("/servlet")) {                 ServletProcessor servletProcessor = new ServletProcessor();                 servletProcessor.process(request, response);             } else {                 StaticProcessor staticProcessor = new StaticProcessor();                 staticProcessor.process(request, response);             }             // 处理完后断开连接             close(socket);         }       }       /**      * 释放资源      * @param closeable 要关闭的资源      */     private void close(Closeable closeable) {         if (closeable != null) {             try {                 closeable.close();             } catch (IOException e) {                 e.printStackTrace();             }         }     } } 总结 BIO ServerSocket 服务器端通信套接字 bind(): 绑定服务器信息。 accept(): 获取客户端连接请求,阻塞调用。 通过 InputStream / OutputStream 进行读写操作,阻塞调用。 close():释放资源。 Socket 客户端通信套接字 connect():发送连接请求,和服务器建立连接。 通过 InputStream / OutputStream 进行读写操作,阻塞调用。 close():释放资源。 特点 同步阻塞 IO,每个连接分配一个线程。 适用场景:连接数目少、服务器资源多、开发难度低。 NIO Channel 通道 可双向操作,既可读也可写。 两种模式,既支持阻塞也支持非阻塞。 ServerSocketChannel 服务器端通道,通过静态方法 open() 获取实例。 SocketChannel 客户端通道,通过静态方法 open() 获取实例。 Buffer 缓冲区 flip() 写模式转为读模式,可以准备从 buffer 获取数据。 clear() / compact() 读模式转为写模式,可以准备向 buffer 写入数据。 Selector 多路复用器 轮询监控多条 Channel。 select():查询每个 Channel 是否有事件到达。 特点 同步非阻塞 IO,只需要一个线程管理多个连接。 适应场景:连接数目多、连接时间短、开发难度高。 AIO AsynchronousServerSocketChannel 异步服务器端通道,通过静态方法 open() 获取实例。 AsynchronousSocketChannel 异步客户端通道,通过静态方法 open() 获取实例。 AsynchronousChannelGroup 异步通道分组管理器,它可以实现资源共享。创建时需要传入一个ExecutorService,也就是绑定一个线程池,该线程池负责两个任务:处理 IO 事件和触发 CompletionHandler 回调接口。 两种异步调用机制:Future 和 CompletionHandler。 适用场景:连接数目多、连接时间长、开发难度高。
分享
5
先马后看
想做你的眼
依图科技·开发工程师

【BIGO校招答疑 华工专帖】Feel Free来留言提问叭

据官方非常可靠信源:BIGO在8.18号就要启动第一场笔试啦,14号前投递还有机会赶上哦。大家准备好简历就尽快投递呀~ 【说在前面的话】 Hello,开门见山的来讲,我就是BIGO在华南理工大学的校园大使 这里是我为招揽业务而开启的BIGO的校招答疑贴 没想到叭233333 欢迎大家在评论区交流有关BIGO的一切校招讯息 这个帖子提供的服务有: 推荐简历(私戳或点击下面的干货链接)、帮查进度、问题答疑、寻找校友组织(加群请私戳)、共同探讨笔试面试问题 你想要的干货都帮你整理好啦,戳这里去看吧: https://shimo.im/docs/xTHyGXgPW6VY3rDX/ 本人作为BIGO的官方校园大使有幸接触了一大波青年才俊,也因此知悉了他们有关BIGO秋招的系列问题 “BIGO笔试什么时候开始哇?” - 8月18日就第一批了哦,投递要赶紧啦 “第一波投递的同学会不会变成‘实验田’、‘炮灰’啊” - 在想啥吶,BIGO为什么要拿宝贵候选人做实验哇,趁着HC还富裕赶紧下手吧~~~ ...... 欢迎在本帖评论区提问、欢迎小窗私戳,虽然没在牛客买房不能时时刻刻回复,但是一看到我就会尽快反馈哒~(问问题前先看下图,若还有问题,欢迎私戳) BIGO 2021秋招常见问题汇总 最后,附上BIGO的公司简介一份,帮助大家更多地了解BIGO 【关于BIGO】 BIGO,于2014年在新加坡成立,是一家高速发展的科技公司。 致力于连接美好世界、传递快乐生活, BIGO基于强大的音视频处理技术、全球音视频实时传输技术、人工智能技术、CDN技术,推出了一系列音视频类社交及内容产品,包括Bigo Live、Likee、imo、Hello语音等。 目前,BIGO在全球已拥有近4亿月活跃用户,产品及服务已覆盖超过150个国家和地区。 为了让用户更积极、自在、安全地感受世界的精彩,BIGO一直不懈地探索人工智能领域,提升自身研发水平,现已在全球建立5个研发中心及20余个本地办公室。 【热招岗位方向】 算法类、开发类、产品类、设计类、运营类、市场类、职能类 【招聘安排】 工作地点:广州、北京、上海、新加坡、佛山 面试形式:远程(为主)/线下面试 时间安排:简历投递(7-9月)→在线笔试(8-9月)→面试评估(8-10月)-发放offer(8-11月) 【在BIGO,我们为你提供】 有竞争力的薪酬起点; 舒适的办公环境,大咖牛人云集; 广阔的事业舞台,海外交流学习机会; 丰富的员工福利:住房补贴、餐饮保障、年度旅游津贴、补充医疗保险、年底双薪、年终奖、下午茶、宵夜、公司体检
分享
2
先马后看
菠菜
香港城市大学·2022届

#数据# 后疫情时代,应届生薪酬大报告!

调研数据显示,直接就业仍然是2021届应届生的主要选择,但选择深造/进修、暂缓就业的比例均较2020届有所上升。 访谈显示,受经济下行压力加大和新冠疫情突发叠加影响,就业形势愈发严峻,更多应届生选择提升学历和就业技能,或者暂缓就业,增加对自我和社会的认知。 从细分到不同学历的应届生就业意愿看,超过六成的2021届本科应届生和专科应届生选择直接就业,超过九成2021届硕士及以上学历应届生选择直接就业。 数据显示, 国有企业、事业单位和国家机关作为应届生首选目标企业性质所占比重均有所上升。当前国际疫情持续蔓延,世界经济下行风险加剧,不稳定不确定因素显著增多,应届生整体就业心态也趋于保守。国有企业、事业单位和国家机关工作相对稳定, 福利待遇体系较为完善,对应届生吸引力有所提高。 民营企业作为应届生首选目标企业性质的比例从24.3%降至22.3%。分析认为,民营企业以中小企业居多,抗风险能力相对较为薄弱,提供给应届生的薪酬和发展空间受新冠疫情影响更大,故而应届生对民营企业青睐程度有所降低。
分享
1
先马后看
自在仙
江南大学·2022届

字节跳动内推内部情况(提前批免笔试)

这里谈谈,我在内部的内推群中看到的数据 截止目前为止,通过内推渠道,总***生了70w+的投递,目前1w+入职,是入职人数第二多的渠道, 在研发序列中,内推人数1.6%,网络投递仅有0.14%,这说明,这其中我感觉大多网络投递可能都是石沉大海,所以导致很多简历没有来得及处理,而岗位就已经招满了。 这也是为什么内推那么有优势的一个地方。简历从投递到入职,平均在36.4天。 在2019年的闪电内推中,近1000人实习生入职,其中近一半(具体多少不便贴出来)都拿到公司的offer。可见转正率之高。 另外本人内推投递了600人,目前待入职3人,另外已经面试通过4人,还有20多人在面试中,辅导过一些人的简历(虽然这些人后来都没投递我的....大写的尴尬)。 帮我我内推的人中,达到3面,2面失败的人进行过转岗,内推也算是尽职尽责。从没有不回复任何一个加了我微信的人的消息,基本都是半天内就回复。很希望大家投递我的简历。 如果投递了我的内推,可以在下方评论,加我好友,备注一下名字。我会联系你。 注意;因为部分私心(坦诚清晰),我希望大家能够评论我的帖子,然后我会联系你。 为了集中力量帮助投递我链接的人!!!请千万不要私信我,不会再回复任何私信! 统一处理时间工作日为12:30,19:00,23:30 非工作日:白天随机,夜晚23:00 字节跳动21届校招内推码: E4DTG6P 投递链接: https://job.toutiao.com/s/JRuSncg 日常实习内推链接 https://job.toutiao.com/s/WBgoyU 社招的内推入口 https://job.toutiao.com/s/WBpmWF
分享
19
先马后看
Setaria viridis
东北财经大学·2022届

快消行业最全解读!想进快消的同学千万不要错过!!!

快消行业,是很多应届生,尤其是市场营销、供应链等专业学生梦寐以求的就业首选。大快消巨头的管培生项目,以万里挑一的录取率声名远扬,今天我就来为大家揭秘,如此“高大上”的快消行业到底是做什么的,有哪些岗位。 【什么是快消品】 快消品本身全称快速消费品,一种新的叫法是PMCG(Packaged Mass Consumption Goods),更加着重包装、品牌化以及大众化对这个类别的影响。最容易让人理解的对它的界定包括包装的食品、个人卫生用品、烟草及酒类和饮料。之所以被称为快速,是因为它们首先是日常用品,它们依靠消费者高频次和重复的使用与消耗、通过规模的市场量来获得利润和价值的实现。与传统消费品行业的区别是更加注重包装、品牌化、大众化及售后服务。快速消费品行业主要由市场部门和销售部门这两个核心部门推进工作,亦有广泛而完善的支持部门。 【人才培养-管培生制度】 快消企业对人才培养有着一套成熟的体系,乐于为包括新人在内的各个级别的员工提供完善的培训,培训期往往较长,投入也十分巨大。特别是被很多人青睐的管培生岗位,是企业里以“培养公司未来leader”而设的特殊岗位。 管培生一般分为两种: 1. Management Trainee,综合管理人才,即通才。 未来成为管理多个业务和团队的领导者;这类人才的可塑性较强,可以横向跨职能发展,最终发展成为一名总经理。 2. Graduation Trainee,职能管理人才,即专才。 相对来说更适合在某一领域纵向直线发展,不仅是某个领域的专家,也是一个团队领导者。未来发展为某个职能的管理者,例如:销售管理总监、设计管理总经理。 企业对管培生十分重视,管培生入职后首先是系统的培训,不同企业的培训项目、方式和时长都各有不同,一般来说,培训时长是半年到一年。根据培训的内容,管培生分为轮岗型和定岗型。 【岗位类型及分布】 根据消费品的生产过程,快消行业的核心部门可分为产品研发、生产/工艺、产品及品牌策划、采购&供应链、质检、以及销售,除此之外还有职能和支持部门例如财务、人力资源和信息技术。 -市场部门(MKT) 市场部门是快消企业的核心部门,公司主要依照市场部门制定的战略运作,包括销售部门、供应链、财务支持等,都遵照市场部门的指令与要求。 工作内容大致可分为以下四块:市场调研、品牌推广、市场活动、广告企划。 市场调研:具体包括进行市场调研,分析新品上市的财务背景,采集行业最新动态,洞察零售市场份额和变化,调查同行数据,分析消费者行为及心理,挖掘潜在客户等; 品牌推广:具体包括确定推广主题、推广策略及资源分配,宣传公司品牌等; 市场活动:具体包括负责新产品的上市与推广,各类活动策划与执行,促销,商品包装与陈列架设计,与供应商打交道,走店摆放商品等; 广告企划:具体包括为公司设计品牌形象,设计、投放广告,物料制作,写作软文等。 市场部门要求员工具有较强的市场敏感度,能够把握行业最新动态,感知用户需求、消费习惯,提升产品的用户体验;具有较强的逻辑分析能力,通过市场调研,分析竞争品;具有较强的沟通能力、策划能力、创新能力与执行力,工作严谨认真。 -销售部门(Sales) 销售部门是快消企业的核心部门之一,不同企业的销售部门往往有不同的名称,如联合利华的客户发展部,宝洁的客户业务发展部。 工作内容主要包括对接与开发现代零售渠道商、分销商、大卖场、标超与便利店等销售渠道,开发公司大客户,发展战略合作关系进行产品销售。 例如,负责北上广深等大城市一部分大卖场,或是前往二三级城市、中西部省份全面负责一定区域的业务,真枪实战地和竞争对手在线下竞争。同时,建设并管理近年内出现且迅速发展的新兴购物渠道如:网上购物,母婴店,药店,美妆店等。 销售部门要求员工具有过硬的销售技巧与统筹眼光,充分开发各级销售渠道。热爱销售行业,具有较强的沟通能力与协作能力,充满激情。 -产品研发部(R&D) 产品都具有生命周期,所以储备和研发新产品尤为重要。工作内容主要是新产品概念研发,而研发的来源一般依靠市场部的调研数据。通过分析市场和消费者数据,跟踪零售市场,把握未来消费趋势。同时,还包括将原有产品进行重新包装和改良,成为新的产品概念。 -产品供应部(PS) 产品供应部负责供应链整合,保证产品从原料到成品,通过每一个环节的物料供应,第一时间抵达消费者手中;保证设备、系统、厂房、配套设施等在安全高效的条件下运转;负责产品生产,以最先进的技术、人力、设备等管理产品的生产、包装等各个流程,保证及时、持续、高品质的产品供应;负责从原料、包装、设备到明星、广告、市场活动等各项内容的全面采购;建设全面的质量体系。 -财务部(F&A) 快消企业的财务部非常庞大,财务管控的重点是费用控制及绩效分析。大致可分为品牌财务、销售财务、供应链财务、公司财务等板块。 品牌财务:指按照产品品牌分类,负责某一品牌的战略、上市、预算管理等。 销售财务:主要对接客户,负责客户生意发展,给出促销及盈利建议。 供应链财务:注重优化供应链,减少生产过程中的损耗,投资新生产线时做好战略规划。 公司财务:则指公司层面的财务工作,包括会计、税务、财务分析、内部审计、合规等。 人力资源部(HR) 工作内容主要是招聘及人事管理,负责招聘方案,优化招聘流程,执行招聘工作,提高招聘效率,规划和人员编制预算的统筹;还需要负责公司日常人事工作,员工关系,薪酬福利等方面的工作。 -信息技术部(IT) 信息技术部和商业关联较大,主要运用IT技术去找到商业解决方案。比如:和供应链合作,实现实时货运可视化,通过先进的安全警报系统,尽收眼底的指标考核,结合大数据分析,三管齐下不断找到物流瓶颈,进行供应链的层层优化。通过高性能的IT基础架构保证广告的精准评估和投放。在保证消费者隐私的前提下,基于海量的网站浏览数据、广告曝光数据和其他第三方数据,根据品牌的需求,定义基于规则的用户分组,或者使用机器学习算法,推测出用户的兴趣标签,为广告精准投放提供数据基础。 此外,广告采买系统(DSP)与媒体的对接,需要寻找最优的技术方案,与各方达成一致。IT的技术方案往往需要与商务谈判结合在一起,才能不断推动与媒体的合作进展。软件开发、IT基础设施(云)、信息安全到大数据分析等。 那今天的科普就先到这里了,如果还有什么不了解的地方,可以在评论去留言哦~
分享
1
先马后看
怎么可以不吃香菜!
谢菲尔德大学·2022届

为什么80%的应届生都考不上国企?

老学长是一位每天和国企招聘打交道的人,在这个过程中,我经常能遇到很多应届毕业的同学,对国企可望但不可及。 以一位电气专业毕业的同学举例,毕业后根本不了解国企,也对国家电网考试不了解,想去烟草但是学历又不太好,就可以自己这辈子就进不了国企了。 其实,国企不仅仅只有烟草、电网、-中石油、中石化这些世界500强,还有很多其他国企,今天老学长就来给大家讲一讲,这些年被我们“误解”的国企。 一、有多少人想考国企? 据不完全统计,每年报考公务员、事业单位、教师、银行、国企招聘的人数,在不断增加。但是,公务员、教师等,一般是毕业后的同学参加较多。 很多同学都还留在传统的思维,实际上对于应届生来说,国企招聘其实是一个更好的选择。 国企招聘考试难? 其实,国企相对于公务员、事业单位是更加容易,更加好考。 国企工作身份不体面? 其实,很多国企福利待遇比公务员甚至要好太多! 如果你不赞同我的说法,先不要急着反驳,我们继续往下看! 二、80%的人都不知道国企考试 每一年的春招和秋招季,在校园招聘会或者宣讲会上都会有大量的国企招聘,其实,80%的学生即使知道了这些国企招聘信息,也不会重视。 例如,国家电网、国家能源这些大型央企,在校园宣讲会的现场,每场能超过50人就是很多了,超过100人就属于特大型了,日常宣讲现场基本就是十几人。 除此之外,其他的国企,比如移动联通电信三大运营商、邮政、铁路局、烟草、中石油、中石化、中储粮等,部分是通过官网进行发布招聘信息,也有也不分是通过前程无忧、智联等招聘平台发布招聘信息。 80%同学不知道国企招聘考试的原因主要有2点: 1.很多毕业生对国企招聘不了解 相比较考公、考编、考研等,考国企很少有人知道,即使有些人知道也不是很了解。在在工作的过程中,很多人对国企的概念很不清晰,更别说对国企招聘考试的了解。 (1)国家电网或电力公司 国家电网,一个熟悉而又大气的国企单位,垄断行业不需要多言。电网人的专有福利,六险二金,外加各种福利补贴。统一的招聘考试,更显公平,一年有两次集中统一招聘考试,主要对标应届毕业生。 当然,往届生有没有机会呢?也有。例如部分省份的供电局和电力公司,会不定期发布公告,给往届生们进入电网打开一扇大门。 如果你恰好是电工类专业,恭喜你,进入电网你基本上已经迈入了一步,如果你是通信类、财会类、计算机类,很幸运,这几类专业大类,在国网的历年招聘中,也是基本上都会涉及到的专业。 如果你担心学历问题,这个问题可以忽略不计。若专业对口,一批基本以研究生、本科为主,二批的招考则会有大量的专科岗位,专科上岸电网并非难事。 (2)中国烟草 中国烟草,下分烟草专卖局和中烟工业,一个负责销售,一个负责生产。世界500强公司,最强大的第一纳税人,税利总额破万亿,国企中的大佬。 中国烟草的福利待遇,整体而言是比价高的。对比当地的公务员薪资待遇,据传是比当地的公务员薪资的2倍,当然,单位的整体收益也决定了薪资的不同。其它的基础性福利自然不用多说。 但是烟草的招聘却往往不按常理出牌,烟草的招聘不像国网那样显得很有规律,它较为零散,各省、各地市,单独招聘。但是考试内容却和公务员的考试极为相似。 也许你在好奇,烟草的招聘门槛是不是很高?我是不是能够报考? 其实也没有你想象的那么遥不可及,部分地区专科起报,倘若你成绩优良,证书齐全,身体健康,备考得当,烟草的这道门也是可以一试的。 (3)铁路局,稳打稳扎 官方称呼是这样的:铁路局是由中央管理的国有独资企业,注册资金10360亿元。下设18个铁路局,3个专业运输公司。 日常标签是这样的:开火车、开动车、开高铁、修火车、跑一线、坐办公室的…… 长话短说,铁路局一般招聘什么样的人才?一般是当年或者次年毕业的高校应届毕业生。有些批次对标铁路及其相关专业,部分则是不限专业。 那么,铁路局的招聘时间一般集中在什么时候呢? 铁路局的招聘一般会分批次,9-11月是招聘高峰期,单次招聘的周期长。部分单位甚至无笔试,直接面试考核,这对于一部分笔试困难症而言,确实是一个难得的选择。 不过,对于有笔试的铁路局而言,考试内容还是值得关注的。是行测?还是综合知识?还是铁路知识? 专业的内容,专业的路子,专业的老师,专业的学习,专业的方法,毕竟在国企中,铁路局的排行榜已位居前五,薪资待遇一直稳居中上游水平,最重要的是对于本科生而言,铁路局的晋升空间是比较大的。 (4)中国邮政 提到中国邮政,许多人弹出一个印象:是下乡送快递的呢?还是邮储银行呢?在这里可以先带大家了解一些邮政的基本情况。 日常科普:大型国有独资企业,依法经营各项邮政业务,承担邮政普遍服务义务,受政府委托提供邮政特殊服务,对竞争性邮政业务实行商业化运营。 每年的邮政招聘集中在春招和秋招,两批高峰期会迎来一波报考热。4-5月份是邮政春招的高峰期,在校招环节中,邮政的要求相对于其他国企而言,宽松不少,不少省份大专和往届生均有机会报考。 而谈及它的待遇,邮政的待遇在当地而言,拥有国企比较完善的福利和薪资体系,在县乡一带,整体的薪资和工作环境还算不错。而且工作也是比较稳定性,对于部分想要在家乡谋求发展的同学们来说,也是一种选择。 2.不知道国企如何备考 俗话说:好记性,不如烂笔头。 努力了不一定会有结果,但不努力一定不会有结果。 (1)你需要一批志同道合的考友 (2)你需要更加丰富的招考信息 (3)需要更加专业的报考指导及答疑 (4)需要更权威的复习资料 以上四点,如果全中,说明现在的你,距离成功又近了一步。 以上就是有关应届生进国企的一些内容,希望应届生同学们看到后可以得到一些帮助,成功入职国企!
分享
评论
先马后看
半遮面的女人
陕西师范大学·2022届

对于应届毕业生来说,想进入vr行业应该做点什么?

毕业生想要试水 VR 行业的话,大方向不外乎如下几项: 1. 硬件开发 如今的 VR 行业,其实和上个世纪 70 年代的欧美游戏机行业有点像。当年看到电子游戏有利可图之后,不少厂商纷纷试水,推出了一大堆换汤不换药或者有脑洞没技术的奇葩硬件产品——没错那个年代雅达利的日子确实过得不错,但距离一手遮天依旧有差距,翻翻那个时代的科技杂志广告页就能找到不少昔日热门一时如今籍籍无名的例子。 当然这种形式上的欣欣向荣并没有维持太久,雅达利危机一起,整个市场立马坍台;然后就是 NES 漂洋过海在这片主机废土上重建市场,威名流传至今而不倒。 扯这些题外话并没有别的意思——如今的 VR 市场上既无雅达利也没有任天堂,换言之「21 世纪的山内溥」头衔此时依旧是虚位以待,且只差一个 VR 版红白机的身位而已。 当然这也只是乍看之下的理想状况而已。从产品还有技术的思路来看,如今想要从无到有创造出让大众一致好评的 VR 设备难度基本等同于徒手登月,并且在国内这个手机盒子还有「移动 VR」大行其道的环境下(虽然快洗牌了,但市场的惯性依旧还在,技术的鸿沟也摆在那儿)选择这条披荆斩棘的技术成长之路想要干出一番成就,实在是难上加难。 具体有哪些需要填补的技术大坑就不细说了,抛去步行和触觉模拟这些深坑以及脑机接口这类无底洞不提,现阶段看似完成度最高的 VR 头显本身就有一堆技术短板有待提高,从分辨率到功耗再到剪掉数据连线都算。 至于放弃自主研发核心设备转而为相对成熟的主流产品开发附件的思路,首先如果是技术含量比较高的硬件(例如数据手套),那么与设备本身的兼容性和泛用性都会是大问题;其次如果是技术含量比较低的配件(例如一次性隔离垫、收纳充电箱、专用清洁套装以及防划伤镜片保护贴等等)固然会让开发的难度大大降低,但又该如何防范同行的抄袭呢? 所以说,现阶段投身 VR 硬件开发的前景如下: 发展潜力:高 回报比率:高 执行难度:高 成功概率:低 2. 内容开发 这个恐怕大家更不陌生了吧。如今论谈 VR 前景的文章里十篇起码有八篇半是在念叨「内容为王」,从游戏娱乐到教育培训再到旅游地产医疗科研,方方面面都能扯出一大串话题来。不过即便如此,尽管相比于硬件那边风险和坑都要小一些,但让从业者一个头两个大的问题依旧是多如牛毛。 这方面的话题细究的话连扯个三天三夜都不在话下,篇幅所限,就以 VR 游戏为例吧,其中一个最基本的问题: 「你制作的游戏,面向的用户群体是街机玩家,还是自用机玩家?」 即便使用的是相同的设备,这两类玩家对游戏内容的需求依旧是大相径庭——前者追求的是短平快无需学习拿起来就能爽的快餐大作,后者则有可能更青睐可以静下心来慢慢体验的内涵杰作。从完善的程度来看,这两者之间还谈不上能说孰优孰劣,倒是在目前遭遇的问题方面存在一定的共性。 考虑到现在值得认真投入开发内容的 VR 设备装机量并没有想象中那么高,选择短平快爽一下的类型更容易被更多人接触认识(毕竟花点小钱去线下 VR 体验店尝个鲜要比直接购置设备轻松许多),但这种浅尝辄止的体验能否树立起品牌印象,以及游戏本身可以有多少销量就都是未知数了; 至于面向个人用户的作品,没错目前的 VR 平台上依旧找不到大作,没错创意绝佳的独立杰作依旧有机会异军突起,但销量又该如何保证呢?毕竟装机量这个大问题还远远没解决啊。 不过,尽管困难重重,但和 VR 硬件开发相比,VR 内容开发行业至少已经有不少人在各个方向上做出了尝试,也确实可以说是不乏收获——以上文中的问题为例,《Audioshield》就是个在设计层面初步兼顾了街机与自用机玩家需求的杰作,其它方向上值得借鉴的类似例子多多少少也有一些。 总之,虽然挑战等级同样不可小觑,但找准方向的话,VR 内容开发无疑是很有前途的——当然,指望能像手游那样机缘巧合爆红之后一夜发家依旧是不现实的,不推荐背水一战的创业选择这个方向。 OK,现阶段选择 VR 内容开发的前景如下: 发展潜力:高 回报比率:低/中 执行难度:中/高 成功概率:中/低 3. 相关媒体 这个算是真·一家之言了……和所有新生的科技项目一样,目前国内外有无数科技媒体开辟了 VR 相关的频道,也有为数甚众专门报道 VR/AR 内容的网站平台上线。和软硬件开发相比,媒体行业最大的优点无疑就是风险性低得多——无论这个行业兴衰起落,作为媒体人都有素材可以报道;即便时间最终证明 VR 的时代还未到来,身为科技媒体从业者想要转行也不难——当然,之后的出路如何那就看自己的能力了。 和手机还有其他领域相比,VR 这个行业的一大优点就是非常年轻,换言之技术积累相比于传统科技行业来说水会浅很多——至少对于缺乏从业经历的新人而言,自己能够接触到的内容之中有很多是凭借自己当前的经验和积累也能理解的,稍加分析就能写出看上去像模像样的文章。很显然,无论是从零开始逐步学习还是培养从业者信心,对于刚刚入行的媒体新人来说,当前阶段的 VR 行业都算是个不错的开局方向。 无论是唯 KPI 是举、以四处转载和公关内容为主进行发展,还是坚持原创保留独立观点阐述这个行业的方方面面,目前国内的虚拟现实媒体圈里还是容得下这些多样化的媒体存在的。不过,不管选择哪种从业态度,这个行当目前最大的问题就是发不了财——虽然养活自己一个人无甚压力,但想要养活一家人就有些力不从心了。 好了,最后是目前选择成为 VR 媒体人的前景: 发展潜力:低/中 回报比率:低 执行难度:低/中 成功概率:高/中
分享
评论
先马后看
Bra
浙江大学·2022届

一篇文章带你全面了解快消行业!吐血整理!

你了解快消行业吗?知道宝洁、联合利华、强生是怎样立足于世界500强的吗?爱思益求职整理了快消行业干货,希望能够帮助大家深入了解快消行业。 一、快速消费品的定义及分类 快速消费品,Fast Moving Consumer Goods(缩写FMCG),主要是日常用品,由于其依靠消费者高频次和重复的使用与消耗,通过规模的市场量来获得利润和价值的实现而得名。一种新的叫法是)PMCG(Packaged Mass Consumption Goods),顾名思义,产品经过包装成一个个独立的小单元来进行销售,更加着重包装、品牌化以及大众化对这个类别的影响。 用一个公式来概括快速消费品行业,即快速消费品=基本的行业原则+更多细节的关注+创新的产品概念+必要的广告投入+长期性品牌维护。快速消费品行业中的“三个月规律”:如果你让一个新的竞争对手在三个月里无法取得量的突破,你就很可能消灭它。与快速消费品概念相对应的是“耐用消费品”(Durable Consumer Goods),通常使用周期较长,一次性投资较大,包括(但不限于)家用电器、家具、汽车等。 快速消费品行业主要分为快速消费品制造业和通路业。快速消费品制造业又分四个子行业: 个人护理品行业,由口腔护理品、护发品等行业组成; 家庭护理品行业,由织物清洁品以及盘碟器皿清洁剂、地板清洁剂等组成; 品牌包装食品饮料行业,由健康饮料、软饮料、乳品、瓶装水以及品牌米面糖等行业组成; 烟酒行业。 药品中的非处方药(OTC)通常也可以归为此类。 便利性:消费者可以习惯性的就近购买 视觉化产品:消费者在购买时很容易受到卖场气氛的影响 品牌忠诚度不高:消费者很容易在同类产品中转换不同的品牌 缺乏本质变化:追求形式和主张上的新意 这些特征决定了消费者对快速消费品的购买习惯是:简单、迅速、冲动、感性。 二、快速消费品行业现状与展望 行业现状: 快速消费品行业偏重营销致胜。 品牌识别必须建立在三个品质的基础上:持久性、协同性以及可行性。 因此,必须在品牌建立之初,对企业识别、产品包装、终端物料调性和基础元素等进行系统的整体规划。品牌命名上必须抢占制高点,根据竞争性理论策略的植田T理论,借助一个有影响力的事物来实现品牌目标,采用事件营销与公共营销作为品牌传播途径。 品牌形象和产品口味是关键。 试图为所有的人服务,想赚所有人的钱。但这肯定是不可能的,在市场细分化已经做到如此地步的今天,不可能有一种产品适用于所有人群。所以,对于品牌定位来说,越纯粹越有震撼力越简单越有穿透力。 单以与主要竞争对手不相上下的技术、资金实力,以传统的方式进入市场,只能凭借价格冲击作为主要手段而分得一块蛋糕,因此难以形成核心优势。 行业发展与展望: 纵观中国的快速消费品行业市场,存在四大炙手可热的发展方向,随着人们生活水平的提高和思想理念的发展,拥有着越来越大的市场潜力,这便是:清洁品方向,形体塑造方向,健身服务方向,美容产品方向。这四个方向的研究、生产、销售等工作将会为广大毕业生带来大量的工作机会。 三、快速消费品行业人才需求 快速消费品行业招聘企业以外资企业和民营大中型公司为主。 外资企业方面,从其在各大网上所发布的职位显示出,高级管理人才和专业技术人才有明显的人力资源匮乏的现象。同时,因为受所需人才专业度和地域的限制,所以外资企业虽然提供良好的职业发展空间、有竞争力的薪酬待遇等优越条件,招聘到所需的研发技术人员也有一定难度。但是在对应届毕业生的吸引上,外资企业有它独到的优势,所以中层人才大多通过内部提升的方式得以解决。 民营企业方面,中层的管理人员变成了企业急需的人才热点,而且该行业大量的市场、销售人员,特别是品牌建设人员流向其他行业,而从其他行业流向快速消费品行业的市场人员并不多。因此,相关企业将有必要继续对此类人才进行储备。另一个方面,在整个行业表现出对品牌建设热情不减的时候,不少大型的快速消费品企业却转而开始关注产品本身。 一方面,快速消费品行业的产品主要面向个人用户,品牌建设和市场推广对企业的生存至关重要。伴随着快速消费品行业市场与销售的日益成熟、完善,各大公司的市场营销部门和品牌部门需要大量营销专业人才;另外一方面,由于其他行业对市场和品牌日益重视,快速消费品行业成了品牌和市场类人才的“培养基地”,近年来其他行业纷纷从快速消费品企业挖人才,大量快速消费品业的市场、销售人员,特别是品牌建设人员流向其他行业,而从其他行业流入快速消费品行业的却并不多,进一步加大了营销市场类专业人才的缺口。 快速消费品行业销售类人员通常占企业员工总数的30%以上,每当行业对人才出现集中需求时,首当其冲的是市场营销类人才。除了大量需求一线业务推广人员、销售代表之外,对营销管理类专业人才需求也较为迫切,这也是快速消费品行业缺口较大的人员。在每年的校园招聘中,宝洁、百事、联合利华等知名企业营销类职位占据多数。 由于需求大,缺口大,快速消费品行业的薪资增长较快。专业机构的薪酬调查显示,快速消费品是广州平均年薪最高的行业,预计快速消费品行业今后的平均薪酬将超过计算机行业。 四、职业发展方向——五大热门营销职位 同样是市场、营销类人员,不同行业的消费品企业,如食品、日化、服装等,因营销模式不同,需求有所侧重。目前,五大职位是快速消费品企业的重点需求对象。 1、品牌经理 品牌是快速消费品企业的生命线,品牌经理主要负责品牌的建设和维护,围绕品牌开展各类营销活动,进行市场策划等等。目前,各类企业尤其是行业内新成长企业对资深品牌经理求贤若渴。 招聘门槛:一般要求市场营销或者经济管理类专业本科以上学历;有3年以上快速消费品行业从业经验;精通品牌建设及推广。 2、产品经理 产品经理对某一产品负责,他不仅负责产品的营销,还要涉足产品的整个生命周期管理,具体职责包括分析市场,确定产品的定位、目标、战略,制定产品整套营销策略和计划等等。 招聘门槛:本科以上学历,市场营销类专业,3年以上从业经历,熟悉所在行业及业内主要竞争对手产品状况。熟悉市场发展动态,精通渠道建设、产品策划;分析、组织与人际沟通技巧良好。 3、城市经理 这类职位的需求主要在全国各地有销售网络的大公司,城市经理的主要工作是负责所在地区的销售工作、市场推广,执行公司的营销政策和策略,建立当地分销网络,维护当地客户。与此相似的职位有区域经理、大区经理等。 招聘门槛:丰富的市场营销经验,熟悉当地市场,有渠道拓展能力;熟悉产品所在行业;有较强的商务谈判能力和沟通能力。 4、渠道经理 渠道即销售通道,渠道经理一般负责区域内销售渠道和代理商的发展和管理,具体包括渠道规划、渠道拓展、渠道管理、代理商培养与支持等工作。此外还要配合区域经理等进行所在区域的市场、营销、销售活动。 招聘门槛:营销、管理及相关专业;在相关行业内有产品渠道建设和分销工作经验;良好的沟通能力与谈判技巧,以及良好的市场开拓能力、渠道建设能力、信息收集及分析能力等。 5、KA经理(重点客户经理) 在消费品行业中,重要的客户会带来大多数的收入,企业要在竞争激烈的商业中占有优势,获取利润和市场份额,往往会挑出最大的客户作为关键客户(KA)来管理和支持,KA经理就是专门对所在区域内重点客户的开发、维护负责。 招聘门槛:熟练掌握计算机,使用ERP软件。在本行业具有一定的资历和能力。具备良好的人际沟通和团队合作精神,有强烈的工作责任心。 五、快消行业需求人才素质能力 1、问题解决能力 对一些知名的国内外FMCG企业来说,大量名校毕业的求职者涌入,学历不再是他们选择人才的决定性标准。相对相对于一个庞大的FMCG链构企业来说,个人对于出现的问题所展现出的解决能力以及相关的素质,是多数大型FMCG企业更为看重的关键。 不仅仅是在企业培训和项目实战中总结积累经验,同时也需要拥有运用经验进行改革创新的魄力和能力。FMCG行业当然需要敏捷的思维能力和问题应对能力,捕捉市场信息,及时从杂乱的信息中决定下一步的策略,迅速对市场作出反应。 2、“国际化”素质 “国际化”素质是联合利华一直以来推崇的员工所需要具备的能力,并且也是其在FMCG行业中保持竞争优势的重要利器。目前许多公司面临因缺乏全球视野、多元文化管理人才而面临失去竞争力的威胁。高流动率、高培训成本、停滞的市场份额、失败的合资公司和并购,以及高机会成本,这些都是全球化管理层选拔不力所不可避免的后果。 如何让自己成为一名具有“国际化”素质的潜力FMCG人才,这对每一位求职者和招聘方都是不小的挑战,也只有这样,才能掌握更高的能力和更宽广的视野,在竞争激烈的FMCG行业中存活下来。英语作为“国际化”素质中基本而重要的一项,不管是跨国公司还是国内公司都有比较高的要求,提高英语水平是进入这个行业首先需要做到的。 ——托福、GRE、雅思、GMAT等出国考试 这些作为出国的必备考试,对进入快速消费品行业同样有非常重要的作用,是考察一个学生英语水平的非必备标准之一。但如果你有这些考试的证书,无疑是一个优势。 ——托业考试(为找工作设置的专门考试) IBM、丰田汽车、德国汉高、可口可乐、贝克、宝洁、美的、海尔、海信等则把其作为人员招聘、升迁、海外任职和员工培训的内部标准。 ——博思、口译等专业考试 博思考试是一种职业英语测评考试。目前已在全球30多个国家和地区的企业中得到推广和应用。博思考试由低到高的级别是1-5级。博思考试初步确立了销售与市场营销、财政与金融、人力资源管理等三大行业综合英语能力的“底线”。北京地区的人力资源岗位对英语能力的要求大部分集中在三级(高);上海则定位为四级(低);广州对人力资源岗位的最低要求也达到了三级(中)。 3、诚信 上海招聘网资深顾问解释道,诚信是进入每一个行业所必需具备的一种品质,宝洁公司对外透露的招聘人才要求中,诚实守信被放在了相当醒目而重点的位置。 从2005年伊始,FMCG行业的诚信危机屡屡发生,涉及食品、化妆品、冷饮等多个品类,一时起危机营销大行其道,“诚信、透明、迅速”成为有社会责任感的快速消费品企业化解危机的法宝,而身处FMCG行业中的同仁相比对这两个字有更深刻的理解,对客户而言,诚信是关键。 4、自我成就与自我剖析 这是一个瞬息万变的行业,每天都有无数条来自市场的信息充斥,作为一名身处这个行业的从业人员,拥有一份强烈的成就心至关重要。 在可口可乐人力资源总监针对可口可乐招聘人员的9个条件中,这一点被放置在第二位,足以见得在FMCG行业中,员工对于自我目标的规划、实现以及超越是上级领导乐于见到并且欣赏的。 了解自己的优势和劣势,长处和短处以及兴趣爱好等(可以通过做职业测评和性格测评等充分地了解自己),这对选择企业有很重要的参考作用。 深入认识快速消费品行业,了解进入这个行业所需要具备的条件,认清自己是否适合这个行业,以及制定自己在这个行业的发展规划。 5、品牌概念 品牌是快速消费品企业的生命线,FMCG是国内最早开始重视品牌建设的行业,无论是日化、食品还是家庭护理品等领域,宝洁、联合利华等知名企业都有一批深入人心的品牌。品牌经理对FMCG营销的作用可见一斑。 6、团队精神 对于FMCG的企业来说,制造、营销和销售等几个环节环环相扣,就拿营销一个部分来说,也会按照不同品牌被细分为不同的小组进行作业。相对而言,FMCG企业更加珍视具有团队精神的员工,而团队精神也影响着企业的将来发展。 快速消费品行业很多是世界一流企业,上百年的、全世界各个市场的经验都在该行业进行无隙的沟通和交流。在同行业中,有几个企业没有进行“拜访八步骤”的培训,又有几个企业不在开口闭口谈“生动化”? 7、永不满足 大部分FMCG的企业欣赏那些拥有无限激情的员工,对待工作和企业改革,对待下一个市场的开拓和另一种类型客户的发展。对于他们来说,墨守成规的办公室规章制度显得有些迂腐,FMCG欣赏能适时打破常规的员工并且乐于与他们共事。勇于开创的开拓精神和敢于面对挑战是FMCG企业HR招聘时倾向于选择的性格特点。 8、管理知识和意识 FMCG公司大都要求学生具有较强的领导能力和管理意识,因此加强管理方面的知识和能力也是必要的。 阅读管理方面的书籍、文章,提高自己对管理理论的认识。 在校期间尽可能参加活动、社团,通过参与、策划、组织和实施,增强自己的管理意识和能力,同时提高自己的团体合作意识。 在社会上寻找机会提升自己。比如实习、兼职等,这些经历在求职过程中都是非常注重的。 六、应届毕业生该如何进入快消行业呢? 对于应届毕业生而言,快消行业是一个易于入门的行业。很多岗位都没有特别的技术要求,但是你需要对行业充满激情,同时对自己的人际沟通能力有信心。一般来说,快消行业会有以下基本要求: 本科以上学历,这当然是得要有学历保证啦。 卓越的人际技巧。快消行业就是一个与客户互动并了解他们需求的行业,有能力达成良好的沟通对于成功与否至关重要哦。 英语能力。无论你计划在一家跨国企业还是在一家本土快消企业工作,候选人良好的英语沟通能力都是非常需要的。 快消企业偏爱可以适应快节奏工作和愿意将活力和创意带到工作的候选人。候选人需要在任务期限内完成工作并有良好的服务意识,同时还要具备以下品质: 卓越的沟通技巧 (掌握四点,迅速提升自己:必须知道说什么;必须知道什么时候说;必须知道对谁说;必须知道怎么说。其实很简单,也就是what、when、who以及how四个单词啦~) 适应快节奏的工作环境 从快消这个“快”字,我们就能够看出这个行业的工作环境与工作节奏都是很“快”的,因此,进入快消领域,也就对你要尽快适应这个环境提出了很高的要求。 管理冲突能力、计划能力、解决问题能力 当然这三项都是一般处理职场人际关系和工作中出现问题的时候每个人必需的能力。不管是不是在快消领域,作为一个求职者,都应当具备它们。 顶尖的候选人会有卓越的商业意识,可以读懂和预测到消费市场的变化。他们有出色的交流和组织技巧,懂得客户的需求并向他们突出产品的主要卖点。
分享
评论
先马后看
酒笙清栀。
中国石油大学(北京)·2022届

最全互联网运营岗位科普!不看绝对后悔系列!

事实上,作为互联网大厂“最友好也最高性价比”的求职岗位,运营岗已经成为 80% 大学生的首选。 但很多同学一打开网申链接就懵了—— 产品运营、内容运营、新媒体运营、游戏运营、活动运营、用户运营、数据运营…… 哪个运营岗更适合我?我该投哪个? 下面我针对互联网行业基本达成共识的分类是,按以下4大运营职能来一一讲解。 【内容运营 】 ●分支:新媒体运营、短视频运营等; ● 关键能力:内容创作能力、持续的创意输出; 内容运营就是通过生产和重组内容的方式,去满足用户的内容消费需求,提升产品的活跃度和内容价值,以及用户对品牌的认知度。 在内容运营岗位的人员需要完成包括内容生产、加工管理、互动制作、输出以及效果监控在内的全流程工作。 如果说你擅长制作和分享文字、音频、视频等内容,或者对互联网内容比较敏感的话,那这一岗位是你的理想选择! 你可以提早开始针对某一类内容,进行技能的打磨和提升。 不管是在图文还是视频领域,能运营个人账号并取得成绩,都将为你求职内容运营岗位大大加分! 【用户运营】 ● 分支:社群运营、社区运营、粉丝运营等; ● 关键能力:用户洞察能力、跟用户互动的热情; 从定义上来说,用户运营的核心是:以用户为中心,通过对用户的需求调查,来制定运营机制,达到引入新用户,留住老用户,保持用户活跃及付费转化的目标。 说人话,即用户运营最重要的是这四件事:获客拉新、留存促活、转化付费、防止流失。 也就是我们常听到的“拉新、留存、促活、转化”。 而在实际工作中,数据分析、用户画像和建模、用户留存、社群运营、活动策划、会员激励都是用户运营日常工作的一部分。 如果你擅长数据分析,能洞察用户需求;或者在社群互动、制造话题上独有一套,能让大家跟你一起High。 那用户运营也许就是最适合你的互联网入门岗位! *完美日记品牌通过“小完子”人设强化用户运营 【活动运营】 ● 分支:平台活动运营、活动执行等; ● 关键能力:活动策划能力、项目管理能力; 活动运营是指有计划地根据产品或业务线的目标进行活动策划、活动实施、宣传推广等系列工作。 任何运营、推广、拉新的动作都离不开活动的形式。 因此,活动运营需要具有较强综合能力的人才,也是非常适合互联网小白锻炼和学习的岗位。 如果你目标为活动运营岗,那么你不仅需要熟悉活动流程,更需要掌握项目管理、资源整合、把控细节、统筹执行这些管理人才素质。 作为最常见的运营手段,它的难度随着活动类型、跨部门程度、策划创意等多个维度有着多种变化。 在面试活动运营岗位前,务必先熟悉多种类型的活动,并好好复盘在校or实践期间策划并执行一场活动的经历噢! 【产品运营 】 ● 分支:App渠道运营、游戏运营等; ● 关键能力:数据分析能力; 可能在你们眼里看来,产品运营非常高端。特别在很多企业的分类下,看起来像是统筹了上述的所有运营的职能。 产品运营是非常靠近产品的一个岗位,综合能力均衡,涉及面极广。 其核心指标是,通过各种不同的运营手段(比如内容组织、收集反馈、需求分析、培训用户等),去实现产品的优化和用户增长。 作为一个发展路径直达产品经理的运营岗位,产品运营的含金量自不用说。 如果你有志于产品运营岗,那么就要尽早去获取互联网产品如App、网站、游戏、课程等的运营实习,以获取从策划到落地的全链路运营实践经验。 值得注意的是,产品运营根据公司定位、产品发展阶段的不同,工作内容也会非常不同。 但一致的是,这一岗位要求非常强的数据分析能力和统筹能力,还有扎实的行业知识基础。 【新兴的热门运营岗】 下面,给大家简单介绍一些细分领域的新兴的运营岗,多为上述大类的分支。 近年校招中,这些岗位的招聘需求也在不断增长,背靠高速发展的行业领域,职业前景可观。 -电商运营 电商运营主要就是进行电商店铺的运营管理(淘宝、京东、亚马逊等),基础工作主要为四类:店铺诊断、策略制定、资源协调和推进执行。 具体而言,即包括商品管理、店铺规划、活动策划与上限、产品推广、文案及设计等工作。 - KOL/主播运营 简单来说,KOL运营、主播运营的主要职责就是挖掘并扶植KOL/主播,陪伴他们一起成长。 是不是让你联想到了经纪人这一职业? 这类岗位具体工作主要包括挖掘与产品匹配的KOL、制定合作方案、负责日常管理、激励KOL创造优质内容、与用户互动和保持粘性、赋能他们的成长。 - 广告投放运营 这类岗位主要管理各类渠道的广告投放,如百度、头条、微博、抖音等线上渠道,和地铁广告、电梯广告等线下硬广。 其工作内容主要包括制定系统的广告投放策略,组成最优效果方案,整理广告投放素材,并分析归纳各广告平台的投放数据。 【我猜你还有这些问题】 1.内容运营和新媒体运营的区别是? 新媒体运营是以新媒体平台/账号为产品开展运营工作,目前最常见的就是“两微一抖”(微信,微博,抖音)。 而因为这些“产品”的功能比较统一,所以该岗位的工作呈现就集中在内容上,让人产生混淆。 其实,内容运营更聚焦于内容的质量和生产,新媒体运营除了内容还需要关注渠道运营、活动运营等具体工作。 2.产品运营和产品经理的区别是? 在大多数互联网企业的职能设计中,产品运营属于产品经理的一个分支。 而在规模比较小的企业、创业公司中,产品经理往往需要负责产品运营的工作。 二者的区别主要在于工作重心不同:产品经理重在打造产品本身,产品运营则更重视用户——如何激励用户使用产品,让产品更好地满足用户需求。 有个比喻格外生动:产品是负责把孩子生下来的,运营是负责把孩子养大的。 3.市场类岗位和运营类岗位,我该怎么选? 市场类的岗位有很多,包括大家熟悉的市场策划、商务拓展、销售、品牌公关等等。 运营更多是内部的活跃和留存,市场更多的是外部的宣传和转化。 比如李叫兽对于市场部职能的定义:创造和管理消费者无形价值的部门。相较之下,运营的岗位职责则更加务实,工作内容基本是数据导向的。 同学们根据过往经历及个人素质的匹配度去pick喜欢的岗位即可。 【最后我想说】 互联网新兴的业态,催生的运营这个岗位,但实际上TA并不那么神秘,商业的本质都是趋同的。 由于互联网的一些新玩法,传统的市场岗位难以解决,因此诞生了运营。 而又因为这些新玩法实在是很多(新媒体、短视频、直播、社交、社群…),因此大家可以看到运营有如此多的品类,并且随着行业的发展还在不断的新增。 因此,在选择一份运营岗位的时候,我推荐大家以终为始,尝试去理解行业、公司的商业逻辑(也就是他是怎么赚钱的)。 再去找运营岗位在其中承担的角色和价值,我相信你对要求职岗位的认识,会深不止一个层次。
分享
评论
先马后看
Tracy呓語
第四范式·CDN研发工程师

这算不算被逼离职

新项目没有盈利,被公司砍了。 干这个项目的人只有2个,一个我,一个领导。 但是非常突然,我的直属领导前一晚提离职第二天就带着东西交接完毕撤了。 下午Hr找我谈转岗问题,说项目要当天戛然而止不做了,没想到公司会这么野蛮粗暴甩客户… 只有2个岗位供我选择,一个是销售岗,一个是文员,每个都薪资不超过5K,并且重新从试用期做起,薪水还要连续三个月是底薪的百分之八十。 (我怀疑社保也会被断,我们公司试用期没有五险) 销售业绩非常狼性,第一个月不开单就会被淘汰辞退,对我也是同样执行公司规定。 我现在的岗位是社群运营,月薪8K,做了不到小半年,但早已转正。 这样的转岗和降薪让我差点哭晕在厕所。不给其他的选择。 感觉自己被逼离职了,这样野蛮的公司,很委屈不想多待,但是又觉得太便宜公司了,朋友们都劝我同意转岗骑驴找马。想问问大家这个过程中公司有没有对我违法行为? 有没有可以和人事拍板的细节。 刚刚看了一遍boss,仿佛一只脚迈回了老家,专科生现在在北京有很多无奈和心酸。 可是我还是告诉自己,没时间悲伤,悲伤除了浪费时间还能如何呢。 95年一枚小北漂,浑浑噩噩为自己的无知买单,工作4年积累甚少,大厂已经不敢奢望了。 觉得可能职业生涯要斩腰了,只能自己做一点点什么事情了,为别人打工,并不受欢迎,也是自己没资深工作能力。 有大佬可以指点一二吗? 后辈给小辈的忠告,不胜感激。
分享
8
先马后看
小瘦子
中南财经政法大学·2022届

算法还是领域内专业就业,求各位给点意见

希望相关领域的大佬帮我trade-off一些未来发展的职业规划,自己不了解业内的发展情况和发展前景,根本捋不清该怎么办,下面是我目前的一些情况: 国内北京211本硕+世界top70英国计算机硕士双学位。平时项目偏传感器数据分析与故障诊断。自己对机器学习深度学习很喜欢,私下里积累了很多相关知识,也在不断打磨自己的数学功底(但是唯一称得上的AI相关项目经验也是仿照开源网络框架自己炼丹)。    很想在ML和AI领域就业,但是又觉得自己现在心思在好几块上(国内结构健康监测、国外偏软件工程和数据结构算法,自己私下里搞ML和AI,很多大厂ML和AI的侧重也略有不同,如果分开的话自己现在就是往四个方向发力)。学了这么久除了java是正规军,python和Matlab都是应用级别的编程水平(因为国内项目代码都是拿python和matlab写的)。    对于未来求职太迷茫了,完全规划不出来一个明确的方向然后去专精。现在每天都很焦虑,觉得自己有劲却不知道到底该往哪里用。    自己这几年的求学项目经历:搞过故障诊断(结构健康检测)、软件开发、轻量级数据分析、计算机视觉、和一些小的ML项目。感觉没个侧重点杂乱无章。写进简历就觉得这几年跟个杂工一样有啥干啥。真的不知道就业这个问题什么时候才能解决。    海外硕士2020.09毕业,国内硕士2021.06毕业。目前为止求学期间发过一篇CV的IE水文,拿过路径规划系统和算法的专利。未来应该会再发一篇算法相关的论文(水不水就全看国内导师给不给机会/时间做深入研究,但是也没多少时间了)。    自己师兄师姐(国内的,国外木有认识的)毕业基本都是国企/研究院(为了北京户口)或者转行(为了钱或别的),师兄师姐都是几年只学结构健康监测相关的理论。 希望有大佬能帮我捋一捋,我自己实在是捋不清我到底应该往哪方面发力或者到底怎么把我这个每个都学了些的情况变成就业优势。 感谢各位大佬!!! 或者一样有困惑的道友也可以私下交流。
分享
25
先马后看
念·响
清华大学·2022届

互联网小白艰难暑期记录(美团offer,腾讯阿里字节面经)

背景:劝退专业,0互联网经历,有其他领域实习+在校科研项目 summer目标:产品经理 总结:0经验求职互联网真的很艰难,楼主行动不算早,又有些眼高手低,前后忙活了两个多月才最终幸运上岸。记录一下求职经历回馈牛客,也是为了让自己吸取经验备战秋招。 内含腾讯、阿里、字节、美团面经,祝大家都顺利上岸 腾讯 3.19投递提前批,PCG产品策划/运营,3.29被捞起 4.1电话一面,对方是做腾讯视频推荐机制的组,面试官小姐姐很温柔,一直在给正向的反馈,聊天走向很和谐,所以比较膨胀地自我感觉良好 一面问题:介绍了科研项目,从产品的思维去分析需求,接触过什么数据分析工作和方法,面试官介绍了部门的工作,谈自己的理解,关键的指标,有效性评估,对比了腾讯视频和自己常用的其他视频网站,腾讯视频在某领域是否有开拓前景。最后反问了部门工作的具体内容。 一面结束之后很快转成复试,然后状态维持了十几天没有接到新的面试通知,眼看提前批要结束了,不停咨询HR和其他同学,终于4.14通知了复试。 4.16视频二面:自我介绍,用过什么数据分析工具(excel打天下的楼主瞬间感觉不妙),分析一款自己常用的APP,过往经历中有什么能体现自己能力的事情(说了一个面试官没什么反应,让再讲一个,又说了一个还是反应平平)。反问:我自己比较了腾讯和爱奇艺优酷的推荐机制,针对一些不同的地方问了为什么会这样设计。 二面体验感觉就是非常凉,面试官对我全程兴趣不大的样子,问题比较宽泛,可能我确实没有什么相关经历可以细问,我也没有好好把握住宽泛问题的机会来掌握面试的主动权,果然当晚流程就变灰了。。。 4.24做了正式批笔试,之后再也没有被捞起,想来是提前批二面真的表现太差,而且HC应该也不多了 阿里 3.30自主投递了阿里,产品经理岗 4.21接到电话一面,对方是做菜鸟国际物流的小组,面试官小哥哥同样很温柔,详细问了实习里的一篇报告,问了对产品经理的理解,产品经理需要的能力,最后反问了小组的工作内容。 面完之后才后知后觉地发现这是个toB业务岗,但是自己的理解和能力都是按照toC准备的,顿时感觉不妙,面试体验和腾讯二面有相似之处,在没有相关经历的劣势下,我自己的思考又不够全面深入,凉的理所当然。 两天后发现已回绝,但是听说阿里有两次投递机会,所以明知希望不大还是去和校招HR磨来了一个内推邀请(过程艰辛,还被HR教育了,虽然当时心态有点崩,但是现在看来当时HR给的建议和指导是非常中肯有用的),内推直接被再次回绝,凉的很彻底。 字节 3.30投递产品合作岗(不知道为什么投了这个),直接被转岗电商产品运营,笔试后一直没有捞起 后来4月还投过字节互联网行业分析的日常实习(默默感叹一下不能转正的日常实习面试流程也是好正规) 一面:感觉是HR面的,过往实习深挖,对行业分析的理解 二面:对过往经历非常详细的深挖,侧重报告撰写经历和公司调研经历,介绍了工作会需要大量cold call,问了是否能力匹配,反问了行业分析看重的能力和个人发展的关键 本来已经做了做研究工作和打cold call的思想建设,结果一周后收到了拒信,bad ending 中期总结 由于楼主summer公司投的很少,四月底除了无反馈的基本就是全凉的状态了,所以经历了很长一段纠结迷茫的时期,投了很多日常实习,给反馈的寥寥无几,又投了一堆银行,又思考自己是不是更适合研究岗,几乎觉得暑期实习无望了,然后就在等国开行面试的上午,同时接到了美团HR的电话。 美团 4.8就投递了美团,第二天做完了笔试,然后长达一个半月杳无音讯,5.21接到HR电话,介绍说有数据产品经理的岗位,安排了下午的面试。 5.21一面,面试官是位温柔的小姐姐,一直笑得眼睛弯弯。自我介绍,项目深挖,介绍了数据产品工作,接触过什么数据产品,墨迹天气,最近在通过哪些渠道学习产品知识,会写SQL吗(当头暴击,以为要凉),反问了数据产品经理的能力要求,默默感觉自己不配。 5.22竟然打电话来约了二面,诚惶诚恐不知所措。 5.25二面,自我介绍,简单地简历挖掘,重点考察了两个case,第二个case我最开始想的不是很全面,面试官有给一些暗示和引导,楼主比较快地get了面试官的思路并且和她做了一些探讨和讨论,稍微弥补了一下。最后问了完成过的项目和最近克服过的困难,反问了小组的具体工作(第一轮竟然忘记问) 然后,5月29号,在楼主等待学而思面试的时候(熟悉的场景),直接收到了美团的offer邮件!感觉真的是非常幸运,可能是突然空出HC所以时隔这么久被捞起,遇到了两位非常和蔼的面试官,流程这么快速地就结束了,真的非常感谢美团愿意给我这样的小白一个机会。 总结 感觉0相关经验求职是真的很艰难,但是也不算无路可走,尽量在自己的各种经历中发掘与目标职位匹配的地方,去体现自己的能力,然后在不断的面试中积累经验,培养自己的思维。面试过程中在牛客刷大家分享的面经给了我很多准备素材和鼓励,所以也把自己的经历分享给大家。 大家秋招加油呀!
分享
3
先马后看
榴莲刘奶奶牛奶
香港城市大学·2022届

选职业就像找对象,没有最好只有最合适

笔者陈忠才,别名老碳,浙江大学本科、中科院博士、互联网连续创业者。基于笔者国内外名校求学、上市公司从业及多年创业的经历,分享求职就业和职场的经验。 (如果你是大学生/职场新人,有职场迷惘或希望更快找到好工作,欢迎与我交流~) 在企业战略定位方面有个经典的理论,它是经营管理大师吉姆柯林斯提出的三环理论,这个理论认为,企业在做战略定位时,根据如下三个问题画三个环,这三个问题分别是: 1.你能在什么方面成为世界上最优秀的? 2.是什么驱动你的经济引擎? 3.你对什么充满热情? 每个环里面可能都有多个选项,三个环交叉的部分,也就是同时符合三个要求的选项,就是企业要选择的战略定位。 现在我把这个三环理论应用到个人的职业定位中,用来分析个人在做行业选择的方法。 这三个问题分别是 1.热爱:你对什么行业充满激情? 2.需求:什么行业是社会需要的? 3.擅长:你擅长做什么? 画出这三个环,然后在每个环里面填上所有的选项,再找出三个环交叉的部分就是你最佳的职业选择。 下面逐一分析如何去回答每个环的选项: 一、热爱你会不会热爱做某件事情,或者喜不喜欢某个环境,取决于你的个性和价值观。 1.个性和价值观来自于一个人的基因和成长环境,它代表你是什么样的人。 个性是你的喜好和口味,你喜欢和人打交道还是喜欢琢磨事情;你喜欢自由宽松的环境还是喜欢等级分明的环境,你喜欢去企业工作还是愿意当公务员,你愿意去管理规范的传统企业还是愿意去追求创新的互联网企业……这些都由你的个性决定的。 2.价值观是你内心深处认可哪些事情,不认可哪些事情。 如果你在价值观上不认可一件事,你就不可能真正热爱,最多有可能沉迷,时间久了你就会想着逃离。一个人是否真正热爱一件事就像是否真正爱一个人一样,是不可能永远伪装的,你可以在一段时间内假装喜欢一件事,但是不可能在全部时间内假装。判断自己是不是热爱一件事,有一个简单的方法,就是你问自己:假设未来十年你只能做这一件事,只能专注在这一个行业不能换,愿不愿意?如果你的回答是肯定的,说明你热爱这件事,如果你的回答是否定的,或者犹豫不决,很可能你就不是热爱这件事。 二、需求这个需求是社会需求,就是你选择的行业,有没有为社会创造价值,是不是社会需要的。 判断一个行业是不是社会需要的,有一个简单的标准,就是这个行业是不是很赚钱。 赚钱的行业在时间维度上有两种可能的模式: 模式一:现在就很赚钱的行业,比如:2010年的房地产行业; 模式二:未来很赚钱的行业,比如2000年的互联网行业。 最好的选择是一个未来很赚钱的行业,也就是我们通常说的选择朝阳行业。 假如,你在2000年选择进入互联网行业,你很大的概率已经实现财务自由。如果运气再好点,加入了当时刚刚创办的阿里巴巴,那你有可能现在已经进入了财富榜。等而次之的选择是现在热门的行业。所谓热门行业,就是目前阶段社会非常需要的行业,比如现在互联网行业。 热门行业有可能是朝阳行业,也有可能不是。比如:房地产现在还是热门行业,但它很可能不是朝阳行业; 互联网现在是热门行业,但它依然还是个朝阳行业。 热门行业属于所有人都看得到的机会,选择它意味着现在有很多机会,但同时也意味着竞争激烈,属于红海市场; 朝阳行业属于只有少数人能看懂的机会,选择它意味着将来有很多机会,且面临的竞争相对少,属于蓝海市场。三、擅长选择行业时,需要考虑自己是否擅长。 你怎么判断自己是不是擅长某个领域? 可以从四个角度进行思考: 1.知识: 知识来自于学习,代表你知道什么。 回顾一下你自己过往所有的教育经历和自学经历,看看自己有没有在这个领域有过大量学习,比如你的大学专业、你看过的书、你参加过的培训。我们经常说的专业对口,就是指你选择的工作,在知识层面和自己过往的学习相匹配。 2.技能: 技能来自于实践,代表你会什么。 技能来自于你过往的工作和经历,简单来说就是你做过什么,积累了哪些经验。从职业角度,最重要的是你以前做过的行业、呆过的公司、干过的岗位、取得的业绩。招聘者判断一个求职者,是不是具备某项工作技能时,主要是从他的过往行业、公司、岗位、业绩这几个角度来考察。 3.资源: 资源是你在某个行业积累的人脉和社会资源,它来自于一个人的社会关系网络。 资源主要对于在职场上有一定资历的人来说,尤其是对那些在某个领域积累了深厚人脉和社会资源的人,资源对于他能否做成一件事至关重要。 很多大型企业,会聘请退休的官员,就是看中他过往积累的人脉和社会资源。 对于大学毕业生和职场新人来说,大家都是一张白纸,除了少部分家里有矿和有背景的,资源对于职场新人来说不是关键因素。 4.天赋: 天赋就是一个人的基因,这是先天的。 它是你的性格、思维特征、大脑素质等等的综合。比如,有些人性格外向、有亲和力、善于交际,那他可能适合做市场性的工作;有些人逻辑思维能力强,爱钻研事物原理,那他可能适合做研发等。 一个人选择的行业要符合他的天性,如果不符合天性,就很难做久和做好。 一个热爱自由、文艺范的人,如果听从他父母要求,勉强去考入一个等级分明、有各种条条框框的政府部门做公务员,那他未来的职业很可能充满苦闷,也做不好本职工作。 以上四个角度,可帮你判断自己是否擅长某个领域。总体来说,判断自己是否擅长一个领域,有一个简单的方法,就是问自己一个问题: 未来我有没有可能在这个领域里,成为高手,甚至于达到顶尖水平? 如果有可能,那就可以认为是你擅长的领域。但现实可能没有这么理想,有时候需要你在三个因素中进行取舍,比如,有一个职业你既热爱又擅长,但是社会的需求不强烈,可能是个夕阳行业,你要不要选择?再比如,有一个行业你非常感兴趣,又是热门行业,但是你一点都不擅长,甚至完全不懂,这个时候你又如何做选择?最后,如果有一个行业,你非常擅长,又是朝阳行业,赚钱对你来说,一点都不是难事,只可惜你对这个行业,毫无激情,甚至有那么一点厌恶,你要不要去做?这些问题没有标准答案,每个人可能会给出不同的选择。人生的选择就是人生的战略,定战略是重要又困难的事,所以选择也是困难的、是痛苦的,有时候甚至绞尽脑汁、左右为难,但是人必须要对自己负责,所以必须要做出选择。当你的职业选择迷惘时,除了独立思考,还可以找你身边信任的人沟通,比如你信任的导师、校友、和同学,尤其是那些有经验、有智慧、有资源,同时还了解你的人。 最后,你还要清楚,你可以征询很多人的建议,但最后的选择,一定要自己做出,你选择稳定,还是选择冒险;你选择别人的眼光,还是选择过自己的生活;你选择走大多数人走的路,还是选择走少有人走的路。无论最终选择了什么,你都要告诉自己,你做出的选择,它最坏的结果,你能否承受,你能无怨无悔面对你的选择吗?选择不一定有最正确的答案,但是你要确保自己做出“无悔的选择”,这就够了。 人的职业发展,就像一颗树,它是一个逐步成长的过程。每个人的人生都不是一开始就确定的,谁的青春不迷惘,关键是我们要尽快走出迷惘,更正确地探索自我。一个人要成为一个领域高手,还要做时间的朋友。认真选定好一个行业后,在一个行业深耕下去,拒绝各种各样的疑惑,努力成为某个领域的第一名,哪怕是一个行业里非常细小的领域。只要你在一个点上成为高手,你就会变得很值钱。 英国管理大师查尔斯·汉迪在《思想者》里说的那样:“成功的人生并不是在行动之前就知道自己想做什么,恰恰相反,只有行动、实践、质疑、再次行动,你才能发现自己是谁。”而好的选择,最终会更快地发现更好的自己。
分享
2
先马后看
绿水染清风
华南理工大学·2022届

在进游戏行业之前,我劝你这5件事情一定要想清楚!

如果你想要进入游戏运营行业,你应该想清楚的5件事。 01 你是不是真的热爱游戏? 老实说,当年因为游戏还不像今天这么多人关注讨论,真的非常热爱游戏这个维度不是当年最重要的指标,但现在这一点越来越是了。 越来越是的原因是有两方面,一方面是因为产品是伴随用户口味的提升越来越精品化的,伴随版号限制,曾经粗制滥造、简单换皮的时代已经越来越远去了,而产品要求的提升也会反向促使对产品运营人员,本身对游戏的理解越来越高。 另一方面是,现在更年轻一代越来越在意自己喜欢和成就感这件事,而这个不只是单个个体,是越来越成为群体效应,所以如果你真的不爱这个东西,你其实是很难获得成就感。 而且游戏一直是带有一些争议性的,虽然现在行业整体在越来越规范健康和绿色,但你仍然会有面临质疑的眼光问题,那如果你自己都不能说出游戏的好,游戏的价值和意义,你吃不了这个行业的苦,受不了这个行业的累,最终对企业对个人其实都是不利的。 但是如果是你真的热爱的,世界也都会给你让路。 图片来源于网络-哈咪猫 02 你到底是适合研发还是运营? 如果你想清楚了,你热爱游戏想进入这个行业,那下一步你要问自己的是你到底适合做什么? 如果你想的是创造一款很有乐趣的新的游戏,无论是你希望设计一个系统,还是你希望写出一段文字,还是你喜欢编排一个关卡。 你是那种沉浸式的,相对宅一点,愿意花很长的反复打磨一个东西,比较感性的,关注事情大于关注人,我建议你考虑研发,那对应要求的是你需要对游戏本身的理解分析思考,足够深入,横向的和纵向的,相对而言,和人沟通协作不是最重要的,创作出一个吸引人的东西是最重要的。 如果你想的是希望能帮助一款游戏有很多的人喜欢上,喜欢和玩这个游戏的人一起交流一起讨论,让越来越多的人能爱上这款游戏,帮助用户可以持续在游戏里收获快乐。 你是那种发散多元,相对外向一点,愿意去关注了解不同的看法,愿意通过分析,关注人大于事情,相对理性,我建议你考虑运营,那对应的要求是你本身对玩家的关注需要足够敏感,足够多元,不同维度的不同场景的,宏观与微观,相对而言和人的沟通协作是比较重要的,涉及到和各种人,和用户、和研发、和各种合作方。 03 你是否能扛得住压力? 相对于传统行业,互联网行业是一个高压行业,因为其实它是一个随时随地在线的行业,不像实体店铺,关门了就不营业了。 而游戏其实又是互联网里相对更高压的行业,以为它不是一个功能向的互联网产品,而是一个体验向的互联网产品,功能的持续性是很久的,很多可能季度、半年才更新一次,但是体验向其实是一个很短暂的,它需要持续有新的内容给到新的体验,这就决定了游戏整体的内容产出量是远快于其他互联网产品的,游戏基本是一个月一个新版本。 在这个节奏下,其实游戏行业是比较高压的一个行业,外显的是加班比较多,内显是事情比较多比较杂,当然也不是说全年一年四季都一样忙,也是有大小版本,只是说相对其他行业而言压力会更大,所以无论是对身体还是对心理都是需要有比较大的抗压和抗挫能力才可以。 老实说凌晨几点的北广上深,你总会经历了的,太阳君毕业第一年,印象中基本是晚上10点左右下班。 如果你真的想进入这个行业,希望你是提前真的做好了准备。 04 你是否不断学习尝试新东西? 上面以及提到过,游戏是一个体验性的产品,不是功能向的,它要求的是能不断的推陈出新,无论是美术风格、还是系统玩法、还是运营活动设计,虽然在不同的岗位属性上要求度不同,但整体他是一个不断需要新东西的行业,是一个创意行业。 虽然基础的框架是可以经验沉淀,但体验向产品,往往表层的包装也是至关重要的,很多体验玩过几次之后就腻味了,需要新的东西,这就是为什么游戏会要持续出新英雄、新外观、新活动等等,不像是医生,是越老越吃香的纵向积累,是需要持续的新鲜输入。 而这些创意其实是需要来源于对生活对世界各个方面多元尝试和关注的,所以为什么会说游戏是第九艺术,就是因为本质是一种持续的艺术创作,而这种创作不是单一的单向感受创作做,是一个双向交互的体验创作,这本身就是一个难度更高的创作。 其实不用勉强自己,不是说必须是这种一直好奇尝试的人还是好的,只是说可能这种是更匹配这个行业的,只是一个不断需要新想法爆出的地方。 05 你是否有足够的耐心? 其实游戏是一个赌博行业,为什么这么说了,因为大家可能看到了某个游戏大成,但其实还有很多的游戏上了没多久就没了,如果游戏真的成了,确实回报也是不错的,但游戏一旦跪了,其实几千万到上亿也就付之东流了。 游戏整体的研发投入相对于其他很多互联网产品,投入是要高很多的,一个游戏可能快的6个月到1年,慢的可能是2-3年,然后研发完到上线一般还要个大半年-一年,如果你是从事游戏研发,很可能你人生中3-4年是伴随着一款游戏的,如果运营是从0到1立项开始,那也是一样的,不过有很多运营是在快上线的一年才介入的,那相对还会好一点。 但整体而言,选择游戏不像是卖东西,它往往有很长的打磨期,而且基本游戏的研发过程或多或少都会经历一些推翻重改,迭代优化的经历,很少是一锤定音,所以你需要有足够的耐心。 所以游戏是一场赌博,你会把时间青春堵在一款游戏,你需要足够的耐心,去浇灌培养,如果你是那种即时反馈要求很高的,我要的现在就要的,那可能确实也不太适合。 当然还有些基础的职场技能,一个好的毕业生和普通毕业生拉开差距的一些点,字有点多,后续再分享,希望能对想进入这个行业的毕业生能有一些帮助。
分享
评论
先马后看
None
湘潭大学·2022届

交通银行数据中心

拿了offer里面最喜欢的一个了,你要问我为啥,薪资待遇,工作强度,团队氛围各方面。 负责校招的俞老师真的很好啊,见过最负责,最贴心的HR了,只要是有问题,老师都会解答,从刚开始的初面到最后面试,到签约 老师都组织的井井有条,(校招体验甩招商银行hr好几条街)。 工作内容虽然大多数运维方便的,但是运维除了不是特别好跳槽,好像其他没有啥不好的,至少工作压力没有软开那么大吧。 交行的假期很好,工作多一年就多一天,具体的福利待遇我就不多说了,到时候人力会介绍的很清楚的,大家想来上海这边的银行的,交行是个不错的选择吆。
分享
2
先马后看