深入解析 epoll 并实现聊天室
1.epoll简介
在Linux系统下,epoll
是一种高效的 I/O 事件通知机制,通常用于并发编程和网络编程。它允许程序监视多个文件描述符(File Descriptor,简称 FD)上的 I/O 事件,并在这些文件描述符就绪时通知应用程序,从而实现高效的 I/O 多路复用。
epoll
能够处理大量的并发连接而不会消耗太多系统资源,因为它采用了事件驱动的模式,只有当 I/O 事件准备就绪时才会通知应用程序,而不是像传统的阻塞式 I/O 那样需要不断地轮询检查文件描述符状态。
在通信领域,特别是在构建聊天室等即时通讯应用程序时,epoll
在管理和处理大量并发连接方面显得非常重要。epoll
是用于管理大量文件描述符的事件机制,特别适用于需要高效处理大量并发连接的网络应用程序。epoll
基于事件驱动,利用内核维护的事件表来监视文件描述符。应用程序使用 epoll_ctl()
向内核注册需要监视的文件描述符,并设置所关注的事件类型。内核在文件描述符上有事件发生时通知应用程序,通过 epoll_wait()
返回就绪文件描述符及事件类型,应用程序进行相应处理。
1.1. epoll
的优势
- 高效性: 在大量并发连接时,
epoll
比传统的select
和poll
更高效,因为它只关注活跃的文件描述符,避免了遍历所有文件描述符的开销。 - 边缘触发模式: 支持边缘触发模式,只在状态变化时通知应用程序,避免了频繁的通知和处理。
- 避免遍历: 与
select
和poll
不同,epoll
不需要遍历所有文件描述符,因此在处理大规模并发连接时效率更高。
1.2. 创建 Socket 和 epoll
实例
当将 epoll
与 Socket 编程结合使用时,可以实现高效的 I/O 多路复用,用于管理大量并发连接。以下是将 epoll
与 Socket 编程结合的基本步骤:
创建 epoll
实例
使用 epoll_create()
函数创建一个 epoll
实例。
int epoll_fd = epoll_create(EPOLL_SIZE);
将 Socket 添加到 epoll
实例中
使用 epoll_ctl()
函数将需要监听的 Socket 文件描述符添加到 epoll
实例中。
struct epoll_event event;
event.events = EPOLLIN; // 设置事件类型为可读
event.data.fd = server_socket; // 监听服务器端 Socket
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) < 0) {
perror("epoll_ctl failed");
exit(EXIT_FAILURE);
}
等待事件发生
使用 epoll_wait()
函数等待就绪事件发生。
struct epoll_event events[MAX_EVENTS]; // 存放就绪事件的数组
int ready_fds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (ready_fds < 0) {
perror("epoll_wait failed");
exit(EXIT_FAILURE);
}
处理就绪事件
针对每一个就绪事件,执行相应的操作,例如接受连接、处理数据等。
for (int i = 0; i < ready_fds; ++i) {
if (events[i].data.fd == server_socket) {
// 处理新连接
// accept()...
} else {
// 处理已连接的 Socket 数据
// recv() / send()...
}
}
关闭 epoll
实例
在程序结束时,关闭 epoll
实例对应的文件描述符,释放资源。
close(epoll_fd);
epoll
在 Socket 编程中的应用,通过 epoll_ctl()
将需要监听的 Socket 添加到 epoll
实例中,并使用 epoll_wait()
等待就绪事件。这样可以实现高效的事件驱动 I/O 处理,有效管理和处理大量并发连接,提高网络应用程序的性能。
在一个聊天室应用中,epoll
可以有效地处理大量连接,同时实现即时通讯。例如,当有新消息到达时,服务器可以通过 epoll
发现哪些连接已经准备好接收数据,从而实现高效的消息分发和即时通信。
2.所用知识及分析
Socket 编程是一种计算机网络通信的编程技术,允许应用程序在不同计算机之间进行数据传输和通信。它是一种基于套接字(socket)的编程模型,在网络通信中被广泛应用。以下是与 Socket 编程相关的理论知识:
2.1 套接字(Socket)
套接字是一种通信端点,通过网络进行进程间通信的一种机制。它是网络通信的基础,用于在网络上发送和接收数据。
a.套接字类型:
- 流式套接字(Stream Socket): 使用 TCP 协议,在网络中实现可靠的双向通信。TCP 提供面向连接的、可靠的数据流传输。
- 数据报套接字(Datagram Socket): 使用 UDP 协议,以数据包形式在网络上传输数据。UDP 提供无连接的、不可靠的数据传输。
b. 套接字编程基本步骤
-
创建套接字: 使用
socket()
函数创建一个套接字,指定协议族(如 AF_INET 表示 IPv4 地址族)、套接字类型(如 SOCK_STREAM 表示流式套接字)和协议(通常为 0)。 -
绑定地址和端口: 使用
bind()
函数将套接字和特定的 IP 地址及端口号绑定。 -
监听连接请求(仅适用于服务器): 对于服务器端,使用
listen()
函数开始监听来自客户端的连接请求。 -
连接到服务器(仅适用于客户端): 对于客户端,使用
connect()
函数连接到服务器的套接字。 -
发送和接收数据: 使用
send()
和recv()
函数发送和接收数据,分别用于发送和接收数据流或数据报。 -
关闭套接字: 使用
close()
或closesocket()
函数关闭套接字,释放资源。
2.2 TCP 和 UDP
- TCP(Transmission Control Protocol):
- 提供可靠的、面向连接的数据传输。
- 通过三次握手建立连接,确保数据可靠性和顺序传输。
- 适用于需要可靠传输和数据完整性的应用,如文件传输、网页访问等。
- UDP(User Datagram Protocol):
- 提供不可靠的、无连接的数据传输。
- 无需建立连接,直接发送数据报。
- 适用于实时性要求高的应用,如视频流、音频通话等。
- TCP 数据传输流程:
- 建立连接:客户端和服务器通过三次握手建立连接。
- 数据传输:数据通过 TCP 连接传输,保证可靠性和顺序性。
- 断开连接:通过四次挥手断开连接。
- UDP 数据传输流程:
- 直接发送数据包,无需建立连接。
- 传输不保证可靠性,可能会有数据丢失或乱序。
- 适用于实时性要求高、对数据完整性要求不高的场景。
Socket 编程是实现网络通信的重要手段,通过使用合适的协议和函数,可以实现可靠的数据传输和通信。 TCP 提供可靠的数据传输,适用于需要数据完整性和可靠性的场景,而 UDP 则适用于对实时性要求较高、对数据完整性要求不高的场景。
2.3 多线程编程
在 Linux 下,epoll
是一种高效的 I/O 事件通知机制,常用于实现高性能的网络服务器。epoll
支持非阻塞 I/O 和多路复用,能够有效处理大量连接和 I/O 操作,其结合多线程可以进一步提升性能。 epoll
多线程相关的理论知识:
-
epoll
是 Linux 下的多路复用机制之一,与select
和poll
相比,epoll
在大规模连接时具有更好的性能和扩展性。 -
多线程可以进一步提高 I/O 并发处理能力,配合
epoll
实现多个线程同时处理大量的 I/O 事件。 -
可以将多个文件描述符分配给不同的线程处理,以实现更高的并发处理能力。
-
在多线程环境下,每个线程都可以拥有自己的 epoll 实例。
-
文件描述符可以按照某种规则(如哈希、轮询等)分配给不同的 epoll 实例或不同的线程。
-
每个线程使用独立的事件循环,调用
epoll_wait()
等待事件的发生,并处理所监视的文件描述符上的 I/O 事件。 -
epoll
机制下,I/O 操作是非阻塞的,可以避免因为 I/O 操作阻塞导致的性能下降。epoll
在多线程环境下能够提高系统的并发处理能力和性能,但需要注意线程安全性和同步机制的设计,以及合理地分配文件描述符给不同的线程,避免负载不均和性能下降的情况发生。
2.4 字符串处理
- 使用字符串处理函数如
fgets()
、strcmp()
和strlen()
处理用户输入的消息和对消息进行判断。 - 通过字符串比较检查是否为退出指令。
- 使用字符串处理函数对用户输入的消息进行处理和格式化。
2.5 系统函数和错误处理
- 使用函数如
exit()
、printf()
、closesocket()
、WSACleanup()
等进行程序的管理和错误处理。 - 使用
WSAGetLastError()
获取套接字错误信息。
3.基于 epoll 的聊天室服务端实现
3.1. createServerSocket()
函数
// 创建服务器 Socket
int createServerSocket(int port) {
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket < 0) {
perror("Error creating socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddress.sin_port = htons(port);
int bind_result = bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
if (bind_result < 0) {
perror("Error binding socket");
exit(EXIT_FAILURE);
}
int listen_result = listen(serverSocket, SOMAXCONN);
if (listen_result < 0) {
perror("Error listening on socket");
exit(EXIT_FAILURE);
}
return serverSocket;
}
createServerSocket()
函数用于创建服务器的 Socket。首先使用 socket()
函数创建一个 TCP Socket。然后设置服务器地址信息,包括 IP 地址和端口号。使用 bind()
将 Socket 与指定地址和端口绑定。listen()
函数用于启动监听,开始接受客户端连接请求。如果出现错误,函数会打印错误信息并退出程序。
3.2. createEpollInstance()
函数
// 创建 epoll 实例
int createEpollInstance() {
int epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
perror("Error creating epoll instance");
exit(EXIT_FAILURE);
}
return epoll_fd;
}
createEpollInstance()
函数用于创建 epoll 实例。使用 epoll_create1()
函数创建一个 epoll 实例。如果创建失败,函数会打印错误信息并退出程序。
3.3. addSocketToEpoll()
函数
// 将 Socket 添加到 epoll 实例中
void addSocketToEpoll(int epoll_fd, int socket_fd) {
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = socket_fd;
int epoll_ctl_result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event);
if (epoll_ctl_result < 0) {
perror("Error adding socket to epoll");
exit(EXIT_FAILURE);
}
}
addSocketToEpoll()
函数用于将指定的 Socket 添加到 epoll 实例中进行监听。首先,设置 epoll_event
结构体 event
,指定所关注的事件类型为可读。使用 epoll_ctl()
函数将指定的 Socket 添加到 epoll 实例中。如果添加失败,函数会打印错误信息并退出程序。
3.4. acceptClientConnection()
函数
// 接受客户端连接
int acceptClientConnection(int serverSocket) {
struct sockaddr_in clientAddr;
socklen_t clientAddrLen = sizeof(clientAddr);
int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
if (clientSocket < 0) {
perror("Error accepting connection");
exit(EXIT_FAILURE);
}
return clientSocket;
}
acceptClientConnection()
函数用于接受客户端的连接请求,并返回客户端的套接字描述符。使用 accept()
函数从 serverSocket
中接受连接,并返回与客户端通信的新的 Socket。如果出现错误,函数会打印错误信息并退出程序。
3.5. main()
函数
int main() {
int serverSocket = createServerSocket(9090);
int epoll_fd = createEpollInstance();
addSocketToEpoll(epoll_fd, serverSocket);
std::map<int, ClientInfo> clients;
while (true) {
struct epoll_event events[MAX_EVENTS];
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (num_events < 0) {
perror("Error in epoll_wait");
break;
}
for (int i = 0; i < num_events; ++i) {
int current_fd = events[i].data.fd;
// 消息处理
}
}
close(epoll_fd);
close(serverSocket);
return 0;
}
main()
函数是整个程序的主入口,包含了整个服务端的主逻辑。首先创建服务器 Socket,并添加到 epoll 实例中进行监听。进入一个无限循环,使用 epoll_wait()
监听事件。对每个就绪事件进行处理,接受新连接或处理已连接的客户端的消息通信。在程序退出之前,关闭了 epoll 实例和服务器 Socket。
3.6服务端完整代码
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <map>
const int MAX_EVENTS = 1024;
struct ClientInfo {
int clientSocket;
std::string name;
};
int createServerSocket(int port) {
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket < 0) {
perror("Error creating socket");
exit(EXIT_FAILURE);
}
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddress.sin_port = htons(port);
int bind_result = bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
if (bind_result < 0) {
perror("Error binding socket");
exit(EXIT_FAILURE);
}
int listen_result = listen(serverSocket, SOMAXCONN);
if (listen_result < 0) {
perror("Error listening on socket");
exit(EXIT_FAILURE);
}
return serverSocket;
}
int createEpollInstance() {
int epoll_fd = epoll_create1(0);
if (epoll_fd < 0) {
perror("Error creating epoll instance");
exit(EXIT_FAILURE);
}
return epoll_fd;
}
void addSocketToEpoll(int epoll_fd, int socket_fd) {
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = socket_fd;
int epoll_ctl_result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &event);
if (epoll_ctl_result < 0) {
perror("Error adding socket to epoll");
exit(EXIT_FAILURE);
}
}
int acceptClientConnection(int serverSocket) {
struct sockaddr_in clientAddr;
socklen_t clientAddrLen = sizeof(clientAddr);
int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
if (clientSocket < 0) {
perror("Error accepting connection");
exit(EXIT_FAILURE);
}
return clientSocket;
}
int main() {
int serverSocket = createServerSocket(9090);
int epoll_fd = createEpollInstance();
addSocketToEpoll(epoll_fd, serverSocket);
std::map<int, ClientInfo> clients;
while (true) {
struct epoll_event events[MAX_EVENTS];
int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (num_events < 0) {
perror("Error in epoll_wait");
break;
}
for (int i = 0; i < num_events; ++i) {
int current_fd = events[i].data.fd;
if (current_fd == serverSocket) {
int clientSocket = acceptClientConnection(serverSocket);
addSocketToEpoll(epoll_fd, clientSocket);
ClientInfo client;
client.clientSocket = clientSocket;
client.name = "";
clients[clientSocket] = client;
}
else {
char buffer[1024];
int bytes_received = read(current_fd, buffer, sizeof(buffer));
if (bytes_received < 0) {
perror("Error reading from client");
close(current_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, current_fd, 0);
clients.erase(current_fd);
}
else if (bytes_received == 0) {
close(current_fd);
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, current_fd, 0);
clients.erase(current_fd);
}
else {
std::string msg(buffer, bytes_received);
if (clients[current_fd].name.empty()) {
clients[current_fd].name = msg;
}
else {
std::string name = clients[current_fd].name;
for (auto& client : clients) {
if (client.first != current_fd) {
std::string message = "[" + name + "]: " + msg;
write(client.first, message.c_str(), message.size());
}
}
}
}
}
}
}
close(epoll_fd);
close(serverSocket);
return 0;
}
4.基于socket的windows客户端代码实现
4.1. InitializeSocket()
void InitializeSocket() {
// Winsock 初始化
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
exit(-1);
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
exit(-1);
}
// 创建套接字
hSocket = socket(AF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET) {
printf("套接字创建失败\n");
WSACleanup();
exit(-1);
}
}
InitializeSocket()
函数主要负责 Winsock 的初始化和套接字的创建。使用 WSAStartup()
初始化 Winsock,检查是否成功,若失败则退出程序。创建 TCP 套接字 (SOCK_STREAM
)。如果套接字创建失败,则输出错误信息并退出程序。
4.2. ConnectToServer()
void ConnectToServer() {
// 服务器地址设定
SOCKADDR_IN serverAddress;
memset(&serverAddress, 0, sizeof(serverAddress));
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(服务器端口号);
inet_pton(AF_INET, "服务器ip", &serverAddress.sin_addr);
// 连接服务器
if (connect(hSocket, (SOCKADDR*)&serverAddress, sizeof(serverAddress)) == SOCKET_ERROR) {
printf("连接错误 : %d\n", WSAGetLastError());
closesocket(hSocket);
WSACleanup();
exit(-1);
}
}
ConnectToServer()
函数用于连接服务器。设置服务器地址信息。使用 connect()
连接指定 IP 地址和端口号的服务器。如果连接出现错误,则输出错误信息并退出程序。
4.3. GetUsernameAndSend()
void GetUsernameAndSend() {
printf("epoll实现聊天室,请输入你的用户名:\n");
fgets(username, BUF_SIZE, stdin);
username[strlen(username) - 1] = '\0';
send(hSocket, username, strlen(username), 0);
}
GetUsernameAndSend()
函数用于获取用户输入的用户名并发送给服务器。打印提示信息要求输入用户名。使用 fgets()
获取用户输入的用户名,并发送给服务器端。
4.4. SendMessageThread()
和 ReceiveMessageThread()
DWORD WINAPI SendMessageThread(void* arg) {
while (1) {
fgets(messageBuffer, BUF_SIZE, stdin);
if (strcmp(messageBuffer, "quit\n") == 0 || strcmp(messageBuffer, "QUIT\n") == 0) {
closesocket(hSocket);
exit(0);
}
send(hSocket, messageBuffer, strlen(messageBuffer), 0);
}
return 0;
}
SendMessageThread()
函数是一个线程函数,通过 CreateThread()
创建一个新线程用于发送消息。该函数使用无限循环 while (1)
,持续接收用户在控制台输入的消息。使用 fgets()
从标准输入中获取用户输入的消息,并存储在 messageBuffer
中。如果用户输入了 “quit” 或 “QUIT”,则关闭套接字并退出程序。否则,使用 send()
函数将消息发送给服务器。
DWORD WINAPI ReceiveMessageThread(void* arg) {
while (1) {
int receivedLen = recv(hSocket, messageBuffer, sizeof(messageBuffer) - 1, 0);
if (receivedLen <= 0) {
closesocket(hSocket);
exit(0);
}
messageBuffer[receivedLen] = '\0';
if (messageBuffer[0] == '[') {
printf("%s", messageBuffer);
}
else {
printf("[%s]: %s\n", username, messageBuffer);
}
}
return 0;
}
ReceiveMessageThread()
函数是另一个线程函数,用于接收服务器发送的消息。同样使用无限循环 while (1)
,持续接收服务器发送的消息。使用 recv()
函数从套接字接收消息,并将其存储在 messageBuffer
中。如果接收的消息长度小于等于 0,则关闭套接字并退出程序。接收到消息后,首先将其设为字符串结束符,并检查消息内容的开头字符。
这两个函数构成了聊天客户端的消息发送和接收功能。SendMessageThread()
负责接收用户输入并发送消息,而 ReceiveMessageThread()
负责接收服务器发来的消息并在控制台显示。
4.5. StartChat()
void StartChat() {
HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);
HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);
WaitForSingleObject(sendThread, INFINITE);
WaitForSingleObject(receiveThread, INFINITE);
}
StartChat()
函数是聊天开始的函数,用于创建发送消息和接收消息的线程并等待其结束。
4.6客户端完整代码
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <Windows.h>
#include <stdio.h>
#pragma comment(lib,"Ws2_32.lib")
#define BUF_SIZE 1024
char username[BUF_SIZE];
char messageBuffer[BUF_SIZE];
SOCKET hSocket;
void InitializeSocket() {
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
exit(-1);
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
WSACleanup();
exit(-1);
}
hSocket = socket(AF_INET, SOCK_STREAM, 0);
if (hSocket == INVALID_SOCKET) {
printf("套接字创建失败\n");
WSACleanup();
exit(-1);
}
}
void ConnectToServer() {
SOCKADDR_IN serverAddress;
memset(&serverAddress, 0, sizeof(serverAddress));
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(9090);
inet_pton(AF_INET, "172.26.145.237", &serverAddress.sin_addr);
if (connect(hSocket, (SOCKADDR*)&serverAddress, sizeof(serverAddress)) == SOCKET_ERROR) {
printf("连接错误 : %d\n", WSAGetLastError());
closesocket(hSocket);
WSACleanup();
exit(-1);
}
}
void GetUsernameAndSend() {
printf("epoll实现聊天室,请输入你的用户名:\n");
fgets(username, BUF_SIZE, stdin);
username[strlen(username) - 1] = '\0';
send(hSocket, username, strlen(username), 0);
}
DWORD WINAPI SendMessageThread(void* arg) {
while (1) {
fgets(messageBuffer, BUF_SIZE, stdin);
if (strcmp(messageBuffer, "quit\n") == 0 || strcmp(messageBuffer, "QUIT\n") == 0) {
closesocket(hSocket);
exit(0);
}
send(hSocket, messageBuffer, strlen(messageBuffer), 0);
}
return 0;
}
DWORD WINAPI ReceiveMessageThread(void* arg) {
while (1) {
int receivedLen = recv(hSocket, messageBuffer, sizeof(messageBuffer) - 1, 0);
if (receivedLen <= 0) {
closesocket(hSocket);
exit(0);
}
messageBuffer[receivedLen] = '\0';
if (messageBuffer[0] == '[') {
printf("%s", messageBuffer);
}
else {
printf("[%s]: %s\n", username, messageBuffer);
}
}
return 0;
}
void StartChat() {
HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);
HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, NULL, 0, NULL);
WaitForSingleObject(sendThread, INFINITE);
WaitForSingleObject(receiveThread, INFINITE);
}
int main() {
InitializeSocket();
ConnectToServer();
GetUsernameAndSend();
StartChat();
closesocket(hSocket);
WSACleanup();
return 0;
}
5.聊天室结果展示
成功实现聊天功能!
项目仓库地址
6.实验总结与结课感悟
开发聊天室涉及多个技术方面,其中 Socket 编程是关键。使用 Socket API 实现了服务器与客户端之间的实时通信,允许多个用户连接并交换消息。通过 Epoll(Linux)技术实现多路复用,提高服务器性能和处理效率。架构设计遵循 Client-Server 模型,服务器负责接收和分发消息,实现消息广播以供群聊使用。
最后,感谢孟宁老师的课程,让我对网络通信有了更深刻的理解。在他的课上,我学到了许多关键概念。在课堂上对WebSocket、Socket API、Linux 内核网络协议栈以及 gRPC 等内容的进行了深入的学习。
本次课程,我学到了 关于 WebSocket 的重要知识,包括如何建立和管理 WebSocket 连接,以及实现实时通信的基本原理。另外,在 Socket API 和 Linux 内核网络协议栈的学习中,老师以深入浅出的方式讲解了网络通信的基本原理和操作,使我对套接字、协议栈等概念有了更清晰的认识。这些知识让我更加熟悉网络通信的工作流程,为未来的网络应用开发打下了坚实的基础。并且了解了 gRPC,这是一个高性能的远程过程调用框架,通过gRPC可以通过protobuf来定义接口来实现通讯。对 gRPC 的学习让我理解了其优势和适用场景,为将来设计和开发分布式系统提供了重要的启示。
特别感谢老师的耐心教导和深入浅出的讲解,这些知识将成为我未来学习和实践的宝贵财富,对我的职业发展有着重要的指导作用。