Sylar C++高性能服务器学习记录19 【Socket模块-知识储备篇】

早在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
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值