C++socket编程

C++socket

  • socket 中文直译是插座
    • 在计算机硬件中,“Socket” 指的是插座或插槽,比如处理器插槽、插头插座等。这个术语借用了电气工程中插头和插座的概念,代表两个相互配合的部分,通过一个接口进行连接和通信。
    • 同样地,在网络编程中,“Socket” 是应用程序与网络协议栈之间的接口。应用程序通过创建一个 socket 连接到网络,就像插头插入插座一样,以便进行数据通信
  • 在网络中传输数据就像寄快递一样,拿 TCP 来说,寄快递需要源地址(主机 IP)和寄件人(端口对应的程序),收件地址(目标 IP)和收件人(目标计算机端口对应的应用程序)

创建一个 socket

int socket (int __domain, int __type, int __protocol);
socket参数详解
  • __domain

    • AF_INET:IPv4 协议,使用 IPv4 地址进行通信
    • AF_INET6:IPv6 协议,使用 IPv6 地址进行通信
    • AF_UNIX 或 AF_LOCAL:用于本地通信(进程间通信,IPC),在 Unix 系统中使用
    • AF_PACKET:用于底层网络接口访问,通常在 Linux 下用于直接访问网络设备
  • __type

    • SOCK_STREAM:提供面向连接的、可靠的字节流服务(TCP 协议)
    • SOCK_DGRAM:提供无连接、不可靠的数据报服务(UDP 协议)
    • SOCK_RAW:提供原始套接字访问,可以处理网络层数据包
    • SOCK_SEQPACKET:提供有序的、可靠的双向连接的传输
  • __protocol

    • IPPROTO_TCP:TCP 协议,通常与 SOCK_STREAM 一起使用
    • IPPROTO_UDP:UDP 协议,通常与 SOCK_DGRAM 一起使用
    • IPPROTO_ICMP:ICMP 协议,通常与原始套接字 SOCK_RAW 一起使用
    • IPPROTO_IP:IP 协议的基协议,通常与 SOCK_RAW 一起使用
    • 0:自适应协议,自动选择

    socket_in结构体

    struct sockaddr_in {
        sa_family_t sin_family;    // 地址族 (Address family)
        in_port_t sin_port;        // 16位端口号 (Port number)
        struct in_addr sin_addr;   // 32位IPv4地址 (IPv4 address)
        char sin_zero[8];          // 填充 (Padding)
    };
    

    大端和小端

  • 大端和小端的区别在于大端的字节按照顺序排序,小段按照逆序排序

    int n=1;
    //大端字节序:0x00000001
    //小端字节序:0x01000000
    

    网络字节序统一为大端字节序,所以在网络传输的情况下需要将本地字节序转到网络字节序

  • 字节序函数和对应全称

    htons() - Host to Network Short
    
    	•	功能:16 位短整数(short)从主机字节序转换为网络字节序。
    	•	原型: uint16_t htons(uint16_t hostshort);
    
    htonl() - Host to Network Long
    
    	•	功能:32 位长整数(long)从主机字节序转换为网络字节序。
    	•	原型: uint32_t htonl(uint32_t hostlong);
    
    ntohs() - Network to Host Short
    
    	•	功能:16 位短整数从网络字节序转换为主机字节序。
    	•	原型: uint16_t ntohs(uint16_t netshort);
    
    ntohl() - Network to Host Long
    
    	•	功能:32 位长整数从网络字节序转换为主机字节序。
    	•	原型: uint32_t ntohl(uint32_t netlong);
    

将信息绑定到 socket

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind函数参数详解
  • sockfd:这是由 socket() 函数返回的套接字文件描述符,表示要绑定的套接字
  • addr:指向 sockaddr 结构体的指针,包含了套接字需要绑定的本地地址。通常,这个结构体会被强制转换为特定协议族的地址结构体(如 sockaddr_in 用于 IPv4,sockaddr_in6 用于 IPv6)
  • addrlen:这个参数指定了 addr 结构体的长度(字节数)。对于 sockaddr_in 结构体

监听对应端口等待连接

int listen(int sockfd, int backlog);
listen函数参数详解
  • sockfd:套接字文件描述符,即通过 socket() 函数创建的套接字的文件描述符
  • backlog:待处理队列的最大长度
    • 带处理队列最大长度:意思是完成了 TCP 三次握手后但是还没有被accept函数取走的连接

取出连接

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept函数参数详解
  • socked:监听套接字的文件描述符,即之前通过 socket() 创建并通过 bind() 和 listen() 设置的套接字
  • addr:指向 sockaddr 结构体的指针,用于存储客户端的地址信息。如果不需要获取客户端地址,可以将此参数设为 nullptr
  • addrlen:指向一个 socklen_t 类型的变量的指针,初始时这个变量应该包含 addr 指向的缓冲区的长度。在函数返回时,它将被设置为实际存储的地址长度。如果 addr 为空,此参数也可以为空

通信操作

  • 可以凭借 accept 返回的 fd 使用 read/wired、send/recv进行读写操作,新手建议直接用read/wired

代码示例

  • 忽略报错,新手更容易读懂一点,但自己写代码要加上,socket 系列函数出错都是一致返回-1
    • socket()创建套接字时,如果出错(例如由于资源不足或参数错误),会返回 -1

    • bind()将套接字绑定到地址时,如果出错(例如地址和端口号已经被使用、权限问题等),会返回 -1

    • listen()将套接字设置为监听状态时,如果出错(例如套接字未绑定、参数无效等),会返回 -1

    • accept()接受连接时,如果出错(例如没有连接、套接字未监听等),会返回 -1

    • read()从套接字读取数据时,如果出错(例如连接已关闭、文件描述符无效等),会返回 -1

      • 返回值若是正数那就是读取到的字节数、若等于 0 那么是客户端关闭连接,也有可能是多次读取到了末尾
    • write()向套接字写入数据时,如果出错(例如连接已关闭、文件描述符无效等),会返回 -1

#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#include<cstring>

using namespace std;

int main() {
    char buff[1024];
    // 获取当前进程的 PID
    pid_t pid = getpid();
    std::cout << "Current Process ID: " << pid << std::endl;
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    sockaddr_in server_addr{};
    //指定为IPv4
    server_addr.sin_family = AF_INET;
    //INADDR_ANY的宏定义为0x00000000,代表监听本地所有网卡的端口
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    //设置监听端口为 9999
    server_addr.sin_port = htons(9999);
    bind(listenfd, (sockaddr *) &server_addr, sizeof(server_addr));
    listen(listenfd, 10);
    sockaddr_in cli{};
    socklen_t len = sizeof(cli);
    int cli_fd = accept(listenfd, (sockaddr *) &cli, &len);
    int n = 0;
    while (true) {
        //从网络中读取 1024字节
        n = read(cli_fd, buff, 1024);
        //因为如果客户端关闭连接,服务器会有一个读事件,但是大小为 0
        if (n == 0) { break; }
        cout << buff << endl;
        //将读取到的数据写回客户端
        write(cli_fd, buff, 1024);
    }
    close(listenfd);
    close(cli_fd);
}

  • 16
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值