早在19年5月就在某站上看到sylar的视频了,一直认为这是一个非常不错的视频。
由于本人一直是自学编程,基础不扎实,也没有任何人的督促,没能坚持下去。
每每想起倍感惋惜,遂提笔再续前缘。
为了能更好的看懂sylar,本套笔记会分两步走,每个系统都会分为两篇博客。
分别是【知识储备篇】和【代码分析篇】
(ps:纯粹做笔记的形式给自己记录下,欢迎大家评论,不足之处请多多赐教)
QQ交流群:957100923
B站视频:https://b23.tv/YusP39I
Socket模块-知识储备篇
一、使用原生Socket实现服务端与客户端的互通
为了更好的理解Sylar对于socket的封装,我们需要先了解C++原生socket。
最好的了解方式就是用一个案例:
服务端: server.cc
#include <iostream>
using namespace std;
#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
void server() {
const unsigned short SERVERPORT = 53556;
const int BACKLOG = 10; // 10 个最大的连接数
const int MAXSIZE = 1024;
// 创建socket和address
int sock, client_fd;
sockaddr_in myAddr;
sockaddr_in remoteAddr;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
cerr << "[server] socket create fail!" << endl;
exit(1);
}
myAddr.sin_family = AF_INET;
myAddr.sin_port = htons(SERVERPORT);
myAddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(myAddr.sin_zero), 8);
// 将socket和address绑定
if (bind(sock, (sockaddr *)(&myAddr), sizeof(sockaddr)) == -1) {
cerr << "[server] bind error!" << endl;
exit(1);
}
// 开启监听
if (listen(sock, BACKLOG) == -1) {
cerr << "[server] listen error" << endl;
exit(1);
}
// 使用循环来接收客户端的连接
while (true) {
unsigned int sin_size = sizeof(sockaddr_in);
if ((client_fd = accept(sock, (sockaddr *)(&remoteAddr), &sin_size)) == -1) {
cerr << "[server] accept error!" << endl;
continue;
}
// 接收到客户端的连接
cout << "[server] recv: a connection from " << static_cast<char *>(inet_ntoa(remoteAddr.sin_addr)) << endl;
int rval;
char buf[MAXSIZE];
if ((rval = read(client_fd, buf, MAXSIZE)) < 0) {
cout << "[server] Reading stream error!\n";
continue;
}
// 收到来自客户端的消息
cout << "[server] recv: " << buf << endl;
// 向客户端发送信息
const char *msg = "Hello, I am server. You are connected !";
if (send(client_fd, const_cast<char *>(msg), strlen(msg), 0) == -1)
cerr << "[server] send error!" << endl;
// 关闭连接
close(client_fd);
}
}
int main() {
std::cout << "\n---[server] start---" << std::endl;
server();
}
客户端: client.cc
#include <iostream>
using namespace std;
#include <string.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
void client() {
const unsigned short SERVERPORT = 53556;
const int MAXSIZE = 1024;
const char *SERVER_IP = "127.0.0.1";
const char *DATA = "Hello, I am client !";
int sock, recvBytes;
char buf[MAXSIZE];
// 创建socket 和 address
sockaddr_in serv_addr;
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
cerr << "[client] socket create fail!" << endl;
exit(1);
}
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVERPORT);
serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
// 创建与服务端的连接
if (connect(sock, (sockaddr *)&serv_addr, sizeof(sockaddr)) == -1) {
cerr << "[client] connect error" << endl;
exit(1);
}
// 向服务端发送数据
write(sock, const_cast<char *>(DATA), strlen(DATA));
// 监听服务端返回的消息
if ((recvBytes = recv(sock, buf, MAXSIZE, 0)) == -1) {
cerr << "[client] recv error!" << endl;
exit(1);
}
buf[recvBytes] = '\0';
cout << "[client] recv: " << buf << endl;
// 关闭socket
close(sock);
}
int main() {
std::cout << "\n---[client] start---" << std::endl;
client();
}
使用命令运行两个服务:
//由于我是虚拟机没法开两个shell,所以我这里后台执行服务端
./bin/server &
---[server] start---
./bin/client
---[server] start---
// 服务端收到连接
[server] recv: a connection from 127.0.0.1
// 服务端收到来自客户端的消息
[server] recv: Hello, I am client !
// 客户端收到服务端返回的消息
[client] recv: Hello, I am server. You are connected !
二、原生Socket的不足与我们的期望
上述代码是C++原生Socket实现的客户端与服务端交互,可以说是及其简单的demo。
以上代码有很多不足的点,我们列举几个比较重要的来说:
1.sockaddr_in 的创建比较繁琐
原生创建 sockaddr_in :
const unsigned short SERVERPORT = 53556;
sockaddr_in myAddr;
myAddr.sin_family = AF_INET;
myAddr.sin_port = htons(SERVERPORT);
myAddr.sin_addr.s_addr = INADDR_ANY;
bzero(&(myAddr.sin_zero), 8);
我们期望的:
// 只需要一行,指定ip和端口就行
auto addr = LookupAnyIPAddress("0.0.0.0:12345");
2.Socket 创建比较繁琐
原生创建 socket :
// 虽然只有一行但是要手动指定很多参数
int sock = socket(AF_INET, SOCK_STREAM, 0);
我们期望的:
// 在创建时不需要指定相关参数,在后续bind的时候自动识别
auto socket = CreateTCPSocket();
3.bind 方法比较繁琐
原生 bind:
// 需要有很多参数和判断逻辑
if (bind(sock, (sockaddr *)(&myAddr), sizeof(sockaddr)) == -1) {
std::cerr << "[server] bind error!" << std::endl;
exit(1);
}
我们期望的:
// 将socket和address绑定,无需太多的参数
int ret = socket->bind(addr);
4.listen 方法比较繁琐
原生 listen :
// 需要多个参数
if (listen(sock, BACKLOG) == -1) {
std::cerr << "[server] listen error" << std::endl;
exit(1);
}
我们期望的:
// 只需要调用监听方法,无需任何参数
int ret = socket->listen();
5.accept 方法比较繁琐
原生 accept :
// 有较多的参数需要传递
unsigned int sin_size = sizeof(sockaddr_in);
if ((client_fd = accept(sock, (sockaddr *)(&remoteAddr), &sin_size)) == -1) {
std::cerr << "[server] accept error!" << std::endl;
}
我们期望的:
// 无需任何参数,十分干净
auto client = socket->accept();
6.connect 方法比较繁琐
原生 connect :
// 依旧有很多的参数需要手动指定
if (connect(sock, (sockaddr *)&serv_addr, sizeof(sockaddr)) == -1) {
std::cerr << "[client] connect error" << std::endl;
exit(1);
}
我们期望的:
// 无需过多的参数,只需要将address对象传入
int ret = socket->connect(addr);
7.send 方法比较繁琐
原生 send :
// 需要指定过多的参数
int ret = send(client_fd, const_cast<char *>(msg), strlen(msg), 0);
我们期望的:
// 只需要内容和长度
client->send("hello world", strlen("hello world"));
8.recv 方法比较繁琐
原生 recv :
const int MAXSIZE = 1024;
char buf[MAXSIZE];
int recvBytes = recv(sock, buf, MAXSIZE, 0));
我们期望的:
std::string buffer;
buffer.resize(1024);
socket->recv(&buffer[0], buffer.size());
三、总结
综上所述,原生socket有很多的繁琐操作,这里仅仅列出了一小部分就已经让人头大了。
可想而知,如果不对socket进行封装,对于后续开发来说简直是地狱级的灾难。
下一篇我们来讲具体的代码实现(感觉本篇应该放在Address模块之前更好)。
四、谈谈心
今天是 2024年 5月29日,是自己的生日,先祝自己生日快乐!!!
自己的经历比较坎坷,多年前的高考失利让我失去了很多机会,让我对现在的自己产生了不满。
没有学历的敲门砖,没能从事自己喜欢的行业,四处奔波又重回故里,最终一事无成。
但是生活还要继续,既然不能把自己的爱好当工作,那么就让它一直成为我的爱好,一直那么纯粹。
【最后求关注、点赞、转发】
QQ交流群:957100923


576

被折叠的 条评论
为什么被折叠?



