c++ socket基于TCP

linux网络编程基础api

  1. socket 地址api:ip地址和端口对,成为 soccket 地址。

  2. socket 基础api: sys/socket.h 中,包括创建、命名、监听 socket ;接受连接、发起连接、读写数据、获取地址信息、检测带外标记、读取设置 socket 选项

  3. 网络信息api:主机名和ip地址之间的转换、服务名称和端口号之间的转换。 netdb.h 中。

socket通信过程

  1. server端
    创建socket()->绑定地址和端口bind()->监听连接listen()->接受连接accept()->发送数据send()->关闭socket close()
  2. client
    创建socket()->IP地址转换inet_pton()->连接到服务端connect()->接收数据read()->关闭socket close()
  • TCP:可靠传输,三次握手建立连接,传出去一定接受的到(如聊天软件);

  • UDP:不可靠传输,不需要建立连接,只管发送,实时性好(如视频会议);

  • 套接字:表示通信的端点。就像用电话通信,套接字相当于电话,IP地址相当于总机号码,而端口号则相当于分机号码。

TCP:

 UDP:

socket地址

  1. 协议族和地址
    +=========+=========+==============+
    |     协议族     |     地址族    |          描   述          |
    +=========+=========+==============+
    |  PF_UNIX   |  AF_UNIX   |UNIX本地域协议族|
    +=========+=========+==============+
    |  PF_INET    |   AF_INET   |  TCP/IPv4协议族  |
    +=========+=========+==============+
    |  PF_INET6  |  AF_INET6 |  TCP/IPv6协议族   |
    +=========+=========+==============+

  2. tcp/ip 协议族有 sockaddr_in  和 sockaddr_in6两个专用socket地址结构体,分别用于ipv4和ipv6

// TCP/IPv4协议族
struct sockaddr_in {
    sa_family_t sin_family; /* 地址族 */
    in_port_t sin_port;  /* Port number.*/
    struct in_addr sin_addr; /*Internet address.  */
};

struct in_addr {
    in_addr_t s_addr;
};

// TCP/IPv6协议族
struct sockaddr_in6 {
    sa_family_t sin6_family; /* 地址族 */
    in_port_t sin6_port;	 /* Transport layer port # */
    uint32_t sin6_flowinfo;	 /* IPv6 flow information  = 0*/
    struct in6_addr sin6_addr;	/* IPv6 address */
    uint32_t sin6_scope_id;	 /* IPv6 scope-id */
};

struct in6_addr {
    unsigned charsa_addr[16]; /* IPv6要用网络字节序表示 */
};

ip转换函数

  1. 将点分十进制的 IPv4 地址转换为网络字节序整数表示的 IPv4 地址,是为了方便在网络通信中使用。整数形式的 IP 地址更容易存储、传输和比较。

  2. “192.168.1.1” 转换为010101···的值

# include <arpa/inet.h>

in_addr_t inet_addr( const char* strptr);   //将点分十进制的ipv4转化为网络字节序整数表示的ipv4

int inet_aton(const char * cp, struct in_addr* inp); //与上功能相同,但是转化结果存储与参数imp的地址结构中。返回1或0 表示成功或失败。

char* inet_ntoa( struct in_addr in );   //将网络字节序整数转化为点分十进制ipv4,该函数内部用一个静态变量存储静态结果,因此是不可重入的。

创建socket

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

int server_fd = socket(AF_INET, SOCK_STREAM, 0);
  • domain:指定要使用的协议族,常用的有 AF_INET(IPv4 地址族)和 AF_INET6(IPv6 地址族)。

  • type:指定套接字的类型,常用的有 SOCK_STREAM(流套接字,用于 TCP)和 SOCK_DGRAM(数据报套接字,用于 UDP)。

  • protocol:指定要使用的协议,通常为 0,表示使用默认协议。

  • 返回值:成功创建返回 0,失败返回 -1。

函数作用:socket 函数用于创建一个套接字(socket),并返回一个文件描述符,该描述符可以用于后续的套接字操作,如绑定地址、监听连接、发送和接收数据等。

绑定地址和端口

    struct sockaddr_in address;
    address.sin_family = AF_INET;     // TCP/IPv4   AF_INET6:TCP/IPv6
    address.sin_addr.s_addr = INADDR_ANY; // 地址
    address.sin_port = htons(8080); // 端口号

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) == -1) {
        std::cerr << "Error: Failed to bind\n";
        return 1;
    }
  1. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:要绑定地址的套接字文件描述符。

  • addr:要绑定的地址信息,通常是一个 struct sockaddr 结构体指针,需要进行类型转换。

  • addrlen:地址信息的长度。

  • 返回值:成功绑定返回 0,失败返回 -1。

函数作用:bind 函数将一个套接字绑定到一个地址,使得该套接字可以在网络中被标识。对于服务器程序来说,通常在调用 bind 后还需要调用 listen 函数开始监听连接。对于客户端程序来说,通常不需要显式调用 bind 函数。

监听连接

    int listen(int sockfd, int backlog);

    if (listen(server_fd, 3) == -1) {
        std::cerr << "Error: Failed to listen\n";
        return 1;
    }
  • sockfd:套接字文件描述符,即要监听的套接字。
  • backlog:指定在拒绝新连接之前,操作系统可以挂起的最大连接数。
  • 返回值:成功返回 0,失败返回 -1。

函数作用:listen 函数将指定的套接字设置为监听状态,使其可以接受连接。backlog 参数指定操作系统可以挂起的最大连接数,超过这个数目的连接将被拒绝。通常在服务器端使用,用于开始接受客户端的连接请求。

接受连接

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


    int addrlen = sizeof(address);
    int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
    
    if (new_socket == -1) {
        std::cerr << "Error: Failed to accept connection\n";
        return 1;
    }
  • sockfd:监听套接字的文件描述符,即调用 listen 函数后返回的套接字。

  • addr:指向 sockaddr 结构的指针,用于存储连接到的客户端的地址信息。

  • addrlensockaddr 结构的大小,在调用 accept 函数前,需将其初始化为 sizeof(struct sockaddr)

  • 返回值:返回一个新的套接字文件描述符,用于与客户端通信。如果出现错误,则返回 -1。

函数作用:accept 函数用于接受客户端的连接请求。当有客户端连接到服务器时,accept 函数会创建一个新的套接字,并返回该套接字的文件描述符,服务器可以使用该文件描述符与客户端进行通信。同时,addr 参数用于存储连接到的客户端的地址信息。

发送数据给客户端

    const char *message = "Hello from server";
    send(new_socket, message, strlen(message), 0);
    std::cout << "Message sent to client\n";
  • sockfd:要发送数据的套接字文件描述符。
  • buf:指向要发送数据的缓冲区。
  • len:要发送数据的长度。
  • flags:发送标志,通常设置为 0。
  • 返回值:成功时返回发送的字节数,失败时返回 -1。

函数作用:send 函数用于向指定套接字发送数据。它将缓冲区 buf 中的 len 字节数据发送到套接字 sockfd。函数的 flags 参数通常设置为 0,表示没有特殊标志

关闭socket

    close(new_socket);
    close(server_fd);

使用 close 函数关闭这个套接字,释放相关的资源。

  1. 释放系统资源:关闭套接字会释放操作系统为其分配的资源,包括文件描述符等。
  2. 避免资源泄漏:如果不关闭套接字,可能会导致资源泄漏,消耗系统资源并降低性能。
  3. 表示通信结束:关闭套接字可以告知对方通信结束,同时也能清理本地的通信状态。

在这段代码中,new_socket 是一个整数类型的变量,但它关联了一个与客户端通信的套接字文件描述符。在调用 accept 函数后,操作系统会创建一个新的套接字并返回其文件描述符,我们将这个文件描述符保存在 new_socket 中。因此,虽然 new_socket 是一个整数类型的变量,但它实际上是用来代表与客户端通信的套接字的文件描述符。

连接到服务端

    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);

    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        std::cerr << "Invalid address/ Address not supported\n";
        return 1;
    }

    if (connect(client_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "Connection failed\n";
        return 1;
    }
  1. int inet_pton(int af, const char *src, void *dst);
  • af:指定地址族,常用的有 AF_INET(IPv4 地址族)和 AF_INET6(IPv6 地址族)。
  • src:要转换的点分十进制或十六进制字符串形式的 IP 地址。
  • dst:用于存储转换后的二进制形式 IP 地址的内存地址。
  • 返回值:如果转换成功,返回 1(对于 IPv4 地址)或 0(对于 IPv6 地址)。如果发生错误,返回 -1。

函数作用:inet_pton 函数用于将点分十进制或十六进制字符串形式的 IP 地址转换为二进制形式的 IP 地址表示。在这个示例中,它将字符串形式的 IPv4 地址 "127.0.0.1" 转换为二进制形式,并将结果存储在 serv_addr.sin_addr 中。如果转换成功,则条件 inet_pton(...) <= 0 将为假,否则为真。

  1. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:套接字文件描述符,表示要连接的套接字。
  • addr:指向要连接的目标地址信息的 sockaddr 结构体指针。
  • addrlensockaddr 结构体的大小。
  • 返回值:成功返回 0,失败返回 -1。

函数作用:connect 函数用于向指定的目标地址发起连接请求。在客户端中,通常在创建套接字后调用 connect 函数,指定要连接的服务器的地址和端口号,以建立与服务器的连接。如果连接成功建立,则可以通过该套接字进行数据传输。

从服务端接收数据

    ssize_t read(int fd, void *buf, size_t count);

    read(client_fd, buffer, 1024);
    std::cout << "Message from server: " << buffer << std::endl;
  • fd:文件描述符,即要从中读取数据的文件或套接字。
  • buf:指向存储读取数据的缓冲区的指针。
  • count:要读取的最大字节数。
  • 返回值:返回实际读取的字节数。如果返回 0,表示已经读到文件末尾(对套接字则表示连接已关闭)。如果返回 -1,表示出现错误。

函数作用:read 函数用于从文件描述符 fd 指定的文件或套接字中读取数据,将读取的数据存储到 buf 指向的缓冲区中。它会尽可能读取 count 指定的字节数,但可能会因为文件末尾或其他原因而读取少于 count 字节的数据。

代码

server.cpp

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

int main() {
    // 创建socket
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        std::cerr << "Error: Failed to create socket\n";
        return 1;
    }

    // 绑定地址和端口
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    address.sin_family = inet_addr("100.10.10.1");//AF_INET;     // TCP/IPv4   AF_INET6:TCP/IPv6
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8080);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) == -1) {
        std::cerr << "Error: Failed to bind\n";
        return 1;
    }

    // 监听连接
    if (listen(server_fd, 3) == -1) {
        std::cerr << "Error: Failed to listen\n";
        return 1;
    }

    // 接受连接
    int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
    if (new_socket == -1) {
        std::cerr << "Error: Failed to accept connection\n";
        return 1;
    }

    // 发送数据给客户端
    const char *message = "Hello from server";
    send(new_socket, message, strlen(message), 0);
    std::cout << "Message sent to client\n";

    // 关闭socket
    close(new_socket);
    close(server_fd);

    return 0;
}

client.cpp

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

int main() {
    // 创建socket
    int client_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_fd == -1) {
        std::cerr << "Error: Failed to create socket\n";
        return 1;
    }

    // 连接到服务端
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);

    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { //IP地址转换
        std::cerr << "Invalid address/ Address not supported\n";
        return 1;
    }

    if (connect(client_fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "Connection failed\n";
        return 1;
    }

    // 从服务端接收数据
    char buffer[1024] = {0};
    read(client_fd, buffer, 1024);
    std::cout << "Message from server: " << buffer << std::endl;

    // 关闭socket
    close(client_fd);

    return 0;
}

上面内容比较简单基础,这篇socket网络通信包含TCP和UDP,以供参考 

【C++】基础:网络编程介绍与TCP&UDP示例_c++网络编程-CSDN博客

抛弃别人的目光才是自由的开始!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值