惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Archives - TechRepublic
Security Archives - TechRepublic
N
News and Events Feed by Topic
C
CERT Recently Published Vulnerability Notes
NISL@THU
NISL@THU
C
Cybersecurity and Infrastructure Security Agency CISA
A
Arctic Wolf
S
Security @ Cisco Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
N
News and Events Feed by Topic
TaoSecurity Blog
TaoSecurity Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
I
Intezer
Forbes - Security
Forbes - Security
S
Securelist
爱范儿
爱范儿
V
Vulnerabilities – Threatpost
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Stack Overflow Blog
Stack Overflow Blog
云风的 BLOG
云风的 BLOG
博客园 - 【当耐特】
P
Privacy International News Feed
WordPress大学
WordPress大学
U
Unit 42
腾讯CDC
The Cloudflare Blog
T
Threatpost
L
LINUX DO - 热门话题
N
Netflix TechBlog - Medium
G
Google Developers Blog
K
Kaspersky official blog
Cyberwarzone
Cyberwarzone
S
Schneier on Security
I
InfoQ
T
The Exploit Database - CXSecurity.com
S
Secure Thoughts
Webroot Blog
Webroot Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Tor Project blog
F
Full Disclosure
GbyAI
GbyAI
MongoDB | Blog
MongoDB | Blog
月光博客
月光博客
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
L
LINUX DO - 最新话题
Martin Fowler
Martin Fowler
Hugging Face - Blog
Hugging Face - Blog
AWS News Blog
AWS News Blog
P
Palo Alto Networks Blog
Attack and Defense Labs
Attack and Defense Labs

风萧古道 - 勤学苦练,年复一年

游戏服务器开发经验(五)应对复杂需求 沉浸式体验东汉末年生活 - 《真三国无双 起源》玩后感 怒其不争!致2025年HLTV的Top18-NiKo Linux家用服务器维护指南 游戏服务器开发经验(四)避免写Bug的习惯、技巧和心态 游戏服务器开发经验(三)线上维护 游戏服务器开发经验(二)避免内存泄露 游戏服务器开发经验(一)道具防刷 35岁找不到工作,绝对不会是软件开发人员的结局 用爱发电项目开发两个月的心得体会 以魏延“子午谷奇谋”讨论软件需求可行性问题 MQTT协议中可变长度的具体计算方式(有计算过程解析) 关于游戏服务器配置表功能的探讨 Java并发编程中上锁的几种方式 如何用C++分割一个字符串? CSAPP第二章-信息的表示与处理 我的自我介绍 Windows XP虚拟机中文版无需激活下载 Java TreeSet的一些用法和特性 传统软件服务器与游戏服务器架构区别 独立个人项目开发心得 - 任务切分、挑战性、实用性和半途而废 使用Python实现简单UDP Ping 使用Python开发一个简单的web服务器 Kotlin手动实现一个最简单的哈希表 Kotlin实现二叉堆、大顶堆、优先级队列 搭建Spark实战环境(3台linux虚拟机集群)(一)样板机的搭建 Springboot操作MongoDB,包括增改查及复杂操作 Unison在Linux下的安装与使用 Java实现类似WINSCP访问远程Linux服务器,执行命令、上传文件、下载文件 一个被废弃的项目——自动爬取信息然后发给我自己邮箱上 Python连接MongoDB和Oracle实战 MongoDB常用查询语句 vue和springboot项目部署到Linux服务器 Python的一些用法(可能不定时更新) java正则表达式 - 双反斜杠(\)和Pattern的matches()与find() 简述爬虫对两种网站的不同爬取方式 Vue的路由配置及手动改地址栏为啥又跳转回来?? [JavaScript]JS基础知识 [Mybatis]逆向工程中Select语句查询不出‘TEXT’字段 [编译原理]FIRST、FOLLOW和SELECT [Spring]Spring学习笔记 [算法]分布估计算法 - 一种求解多维背包问题的混合分布估计算法_王凌 [日常]我做独立博客的原因 人间值得 深入理解计算机系统 想想就开心! 最重要的事,只有一件 藏书 被讨厌的勇气 关于 简历 朋友 尊重自己:给予与接收的心灵艺术
Linux C++ Socket实战
JonathanLin · 2022-04-30 · via 风萧古道 - 勤学苦练,年复一年

本文主要介绍Linux C++ 基础Socket网络编程。 大部分知识来自于网站:https://www.geeksforgeeks.org/socket-programming-cc/

从图中可以看到,服务端这边需要处理四步才能进入等待连接的状态,而客户端只要两步。

Socket编程中各函数简单解析

本解析仅为自己理解所用,可能有些纰漏,有则改之。 原文中的知识总结得比我更好,尽量参考原文,我的理解仅做辅助之用。

服务端

先说服务端。服务端需要指定好端口并监听,所以需要bind()绑定好端口,需要listen()进入监听状态,然后通过accept()阻塞等待客户端的消息。

引用表:

  • #include <sys/socket>
    • socket()
    • setsockopt()
    • bind()
    • listen()
    • accept()
  • #include <netinet/in.h>
    • struct sockaddr_in
  • #include <unistd.h>
    • read()
  • #include <arpa/inet.h>
    • inet_pton()

socket()

这个函数是用来创建一个socket,3个参数中,需要特别传的就是前两个。返回一个socket编号,是个int值。

int sockfd = socket(domain, type, protocol)

domain: IPV4 用 AF_INET, IPV6 用 AF_INET6 type: TCP 用 SOCK_STREAM, UDP 用 SOCK_UGRAM

setsockopt()

这个函数用来给上面那个socket()函数返回的socket设置属性,作为服务端,为了方便? 可以设置重用地址和端口号。

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); 为了重用地址和端口号,需要这么做:

  • level传SOL_SOCKET,代表你这次设置的属性值是给哪个模块用的
  • optname传SO_REUSEADDR|SO_REUSEPORT,代表你打算同时设置这两个属性
  • optval传一个int*指针,指向某一个数字
  • optlen传sizeof()上面的optval

C++ socket很多函数都需要你再传一个length长度,以确定你真正想传给这个函数的数据是多长。

sockaddr_in

那么地址在socket编程中是怎么表示的呢?是使用struct sockaddr_in来定义的。 用的时候需要设置3个值:sin_family, sin_addr的s_addr, sin_port

  • sin_family:和前面socket()的domain一样,AF_INET
  • sin_addr.s_addr:这个属性是将我们的点分十进制ip地址转化为一个数字,需要使用专门的函数来处理,比如inet_pton()
  • sin_port: 指定端口号,但是不是直接传一个int数字,需要用htons转成16进制的数字

bind()

这个函数用来给socket绑定地址信息。

上一节已经说了地址怎么设置,这一节讲socket绑定地址。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

照例将sockFd和address关联即可。

注意 这里的addr的类型是struct sockaddr * 而不是struct sockaddr_in *。 struct sockaddr的结构里并没有提供存放ip地址,端口号的属性,所以需要用struct sockaddr_in来强制类型转换。 在文档中,作者说struct sockaddr和struct sockaddr_in长度一定是一样的,所以一定可以强制类型转换,让大家不要担心。 https://www.gta.ufrj.br/ensino/eel878/sockets/sockaddr_inman.html 注意,后面的addrlen是一个值,不是指针。

listen()

int listen(int sockfd, int backlog); 这个函数将socket切换为被动模式,进入监听状态。第二个参数backlog指定消息等待队列的最大长度。

accept()

int new_socket= accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 函数执行之后,socket就会等待客户端的连接。当连接建立之后,返回一个用于通信的新的socket,这个新的socket用于客户端与服务端之间的通信。

注意:accept()的第三个参数socklen_t *addrlen和bind()的第三个参数socklen_t addrlen不一样,accept需要一个指针。 有人说是accept()的参数是双向参数,会更新地址的长度值,但也有人说是在accept()函数里,它不知道int能不能存的下这个长度,万一长度特别大就不好存了,为了统一存储结构,仅传一个指针即可。

send()

send()用于通过socket来给对方发送消息。

read()

read()函数是放在#include <unistd.h>中的。函数通过一个char数组来存储接收到的消息

客户端

再说服务端。客户端需要指定好ip地址和端口号,然后发起连接。

connect()

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

客户端也要创建一个socket,指定sockaddr_in然后强转。这个socket不仅负责发起连接,也负责发送,接收数据。

示例代码(没有错误处理)

目前我只想学习这些socket底层api,所以不想浪费过多精力去记住api的返回值可能意味着什么错误,仅专注于能正常实现一个最简单的server和client。

服务器server端

server.cpp

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
using namespace std;
#define PORT 8000

int main() {
    int sockFd, newSockFd, valread;
    int opt = 1;
    char buffer[1024] = {0};
    char* helloFromServer = "hello from server";
    struct sockaddr_in address;

    sockFd = socket(AF_INET, SOCK_STREAM, 0);
    setsockopt(sockFd, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt));
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    int addrlen = sizeof(address);
    bind(sockFd, (struct sockaddr*)&address, addrlen);
    listen(sockFd, 3);
    newSockFd = accept(sockFd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
    read(newSockFd, buffer, 1024);
    printf("receive: %s\n", buffer);
    send(newSockFd, helloFromServer, strlen(helloFromServer), 0);
    printf("server sent message\n");

    return 0;
}

客户端client端

client.cpp

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#define PORT 8000
using namespace std;

int main() {
    int sockFd = 0;
    char buffer[1024] = {0};
    char* helloFromClient = "hello from client";
    struct sockaddr_in address;
    address.sin_family = AF_INET;
    inet_pton(AF_INET, "127.0.0.1", &address.sin_addr.s_addr);
    address.sin_port = htons(PORT);
    sockFd = socket(AF_INET, SOCK_STREAM, 0);
    

    connect(sockFd, (struct sockaddr*)&address, sizeof(address));
    send(sockFd, helloFromClient, strlen(helloFromClient), 0);
    printf("client sent\n");
    read(sockFd, buffer, 1024);
    printf("read message:%s\n", buffer);
    return 0;
}

编译、运行和目标输出

在linxu命令行下,分别输入:

g++ -o server server.cpp
g++ -o client client.cpp

然后开两个控制台,分别输入:

目标输出为: server输出:

receive: hello from client
server sent message

client输出:

client sent message
receive: hello from server