当前位置: 首页 > article >正文

【Linux】:Linux套接字Socket网络编程

Socket 编程预备

  1. 理解源 IP 地址和目的 IP 地址
    • IP 在网络中,用来标识主机的唯一性
    • 注意:后面我们会讲 IP 的分类,后面会详细阐述 IP 的特点
    但是这里要思考一个问题:数据传输到主机是目的吗?不是的。因为数据是给人用
    的。比如:聊天是人在聊天,下载是人在下载,浏览网页是人在浏览?
    但是人是怎么看到聊天信息的呢?怎么执行下载任务呢?怎么浏览网页信息呢?通过
    启动的 qq,迅雷,浏览器。
    而启动的 qq,迅雷,浏览器都是进程。换句话说,进程是人在系统中的代表,只要把
    数据给进程,人就相当于就拿到了数据。
    所以:数据传输到主机不是目的,而是手段。到达主机内部,在交给主机内的进程,
    才是目的。
    但是系统中,同时会存在非常多的进程,当数据到达目标主机之后,怎么转发给目标
    进程?这就要在网络的背景下,在系统中,标识主机的唯一性
    在这里插入图片描述
  2. 认识端口号
    端口号(port)是传输层协议的内容. • 端口号是一个 2 字节 16 位的整数; • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来
    处理; • IP 地址 + 端口号能够标识网络上的某一台主机的某一个进程; • 一个端口号只能被一个进程占用
    在这里插入图片描述
    端口号范围划分
    • 0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的
    端口号都是固定的. • 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作
    系统从这个范围分配的.
    理解 “端口号” 和 “进程 ID” 我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也
    是唯一表示一个进程. 那么这两者之间是怎样的关系?
    10086 例子
    另外, 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定; • 进程 ID 属于系统概念,技术上也具有唯一性,确实可以用来标识唯一的一个进
    程,但是这样做,会让系统进程管理和网络强耦合,实际设计的时候,并没有选择这
    样做。 理解源端口号和目的端口号
    传输层协议(TCP 和 UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 “数据是谁发的, 要发给谁”;
    理解 socket • 综上,IP 地址用来标识互联网中唯一的一台主机,port 用来标识该主机上唯一的
    一个网络进程
    • IP+Port 就能表示互联网中唯一的一个进程
    • 所以,通信的时候,本质是两个互联网进程代表人来进行通信,{srcIp,
    srcPort,dstIp,dstPort}这样的 4 元组就能标识互联网中唯二的两个进程
    • 所以,网络通信的本质,也是进程间通信
    • 我们把 ip+port 叫做套接字 socket
C++
socket
n.
(电源)插座;(电器上的)插口,插孔,管座;槽;窝;托座;臼;孔穴
vt. 把…装入插座;给…配插座
  1. 传输层的典型代表
    • 如果我们了解了系统,也了解了网络协议栈,我们就会清楚,传输层是属于内核
    的,那么我们要通过网络协议栈进行通信,必定调用的是传输层提供的系统调用,来
    进行的网络通信
    在这里插入图片描述
    认识 TCP 协议
    此处我们先对 TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识;
    后面我们再详细讨论 TCP 的一些细节问题. • 传输层协议
    • 有连接
    • 可靠传输
    • 面向字节流
    认识 UDP 协议
    此处我们也是对 UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后
    面再详细讨论. • 传输层协议
    • 无连接
    • 不可靠传输
    • 面向数据报
    因为我们暂时还没有深入了解 tcp、udp 协议,此处只做了解即可
  2. 网络字节序
    我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的
    多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之
    分. 那么如何定义网络数据流的地址呢?
    发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出; • 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从
    低到高的顺序保存; • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高
    地址. • TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节. • 不管这台主机是大端机还是小端机, 都会按照这个 TCP/IP 规定的网络字节序来
    发送/接收数据; • 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即
    可;
    在这里插入图片描述
    为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运
    行,可以调用以下库函数做网络字节序和主机字节序的转换。
    在这里插入图片描述
    这些函数名很好记,h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位
    短整数。
    • 例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将 IP 地
    址转换后准备发送。
    • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回; • 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
    socket编程接口
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器) 
int bind(int socket, const struct sockaddr *address,
 socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
 socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
 socklen_t addrlen);

sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain
Socket. 然而, 各种网络协议的地址格式并不相同
在这里插入图片描述
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16
位端口号和32位IP地址.
IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,
不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好
处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
简单的UDP网络程序
实现一个简单的英译汉的功能
备注: 代码中会用到 地址转换函数 . 参考接下来的章节.
封装 UdpSocket
udp_socket.hpp

#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cassert>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
class UdpSocket {
public: 
 UdpSocket() : fd_(-1) {
 }
 bool Socket() {
 fd_ = socket(AF_INET, SOCK_DGRAM, 0);
 if (fd_ < 0) {
 perror("socket");
 return false;
 }
 return true;
 }
 bool Close() {
 close(fd_);
 return true;
 }
 bool Bind(const std::string& ip, uint16_t port) {
 sockaddr_in addr;
 addr.sin_family = AF_INET;
 addr.sin_addr.s_addr = inet_addr(ip.c_str());
 addr.sin_port = htons(port);
 int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
 if (ret < 0) {
 perror("bind");
 return false;
 }
 return true;
 }
 bool RecvFrom(std::string* buf, std::string* ip = NULL, uint16_t* port = NULL) {
 char tmp[1024 * 10] = {0};
 sockaddr_in peer;
 socklen_t len = sizeof(peer);
 ssize_t read_size = recvfrom(fd_, tmp,
 sizeof(tmp) - 1, 0, (sockaddr*)&peer, &len);
 if (read_size < 0) {
 perror("recvfrom");
 return false;
 }
 // 将读到的缓冲区内容放到输出参数中
 buf->assign(tmp, read_size);
 if (ip != NULL) {
 *ip = inet_ntoa(peer.sin_addr);
 }
 if (port != NULL) {
 *port = ntohs(peer.sin_port);
 }
 return true;
 }
 bool SendTo(const std::string& buf, const std::string& ip, uint16_t port) {
 sockaddr_in addr;
 addr.sin_family = AF_INET;
 addr.sin_addr.s_addr = inet_addr(ip.c_str());
 addr.sin_port = htons(port);
 ssize_t write_size = sendto(fd_, buf.data(), buf.size(), 0, (sockaddr*)&addr, sizeof(addr));
 if (write_size < 0) {
 perror("sendto");
 return false;
 }
 return true;
 }
private:
 int fd_;
};

http://www.kler.cn/a/458488.html

相关文章:

  • Dockerfile基本原理
  • 解决springdoc-openapi-ui(Swagger3)跳转默认界面问题
  • 关于 PCB线路板细节锣槽问题 的解决方法
  • c# WaitSleepJoin状态的线程如何自动恢复
  • ArcGIS计算矢量要素集中每一个面的遥感影像平均值、最大值等统计指标
  • 改变HTML元素的方式有哪些?如何在HTML中添加/替换或删除元素?
  • 微服务保护-sentinel
  • 大模型-Ollama使用相关的笔记
  • 网络:常用的以太网PHY芯片
  • Shader数学基础16-齐次除法
  • sql group by 多个字段例子
  • 《2023-2024网络安全产业发展核心洞察与趋势预测》
  • 使用PyTorch进行自动微分
  • Effective C++ 条款 20:宁以 pass-by-reference-to-const 替换 pass-by-value
  • C++ 设计模式:单例模式(Singleton Pattern)
  • 如何使用爬虫工具Selenium
  • 腾讯PHP经典面试题(附答案)
  • zookeeper+kafka的windows下安装
  • 【服务器项目部署】⭐️将本地项目部署到服务器!
  • 鸿蒙项目云捐助第二十七讲云捐助项目首页分类导航的联动效果