c++ socket基于TCP

embedded/2024/10/18 8:38:15/

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转化为网络字节序整数表示的ipv4int 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/IPv6address.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() {// 创建socketint 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/IPv6address.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";// 关闭socketclose(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() {// 创建socketint 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;// 关闭socketclose(client_fd);return 0;
}

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

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

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


http://www.ppmy.cn/embedded/38196.html

相关文章

Web Component fancy-components

css-doodle 组件库 fancy-components 组件库使用 yarn add fancy-components使用&#xff1a; import { FcBubbles } from fancy-components new FcBubbles() //要用哪个就new哪个 new 这里可能会报错eslink,eslintrc.js中处理报错 module.exports {rules: {no-new: off} …

JavaScript对象方法详解

在JavaScript中&#xff0c;对象是一种复杂的数据类型&#xff0c;它允许我们存储多个不同类型的值&#xff08;属性&#xff09;&#xff0c;并且可以通过函数&#xff08;方法&#xff09;来操作这些值。对象在JavaScript编程中扮演着至关重要的角色&#xff0c;因为它们提供…

23_Scala集合Set

文章目录 Set1.构建方式2.可变Set集合3.可变集合的增删改查 Set –无序,数据不可重复集合 –Set是特质&#xff0c;不能直接构建&#xff0c;默认是不可变集合 1.构建方式 // 1.构建方式 val set Set.apply(2,2,2,2,3,4,5) println(set) //Set(2, 3, 4, 5)2.可变Set集…

Qt简单离线音乐播放器

有上传本地音乐文件&#xff0c;播放&#xff0c;暂停&#xff0c;拖拉进度条等功能的播放器。 mainwindow.cpp #include "mainwindow.h" #include "ui_mainwindow.h" #include <QMediaPlayer> #include <QFileDialog> #include <QTime&g…

Java中ArrayList、LinkedList和Vector的底层原理

ArrayList Java中的ArrayList底层原理主要涉及其数据结构、扩容机制、线程安全性以及元素存储和访问方式。以下是对ArrayList底层原理的总结&#xff1a; 数据结构 ArrayList的底层数据结构是一个动态数组。这意味着ArrayList可以根据需要自动增长其容量&#xff0c;从而存储…

VitePress快速上手

完整教程&#xff1a;https://blog.share888.top/note/front-end/vitePress/01-vitePress%E5%AE%89%E8%A3%85.html https://blog.share888.top/ VitePress快速上手 官方文档&#xff1a;https://vitepress.dev/zh/guide/markdown VitePress中文网&#xff1a;https://vitejs…

SparkSql介绍

概述 SparkSQL&#xff0c;顾名思义&#xff0c;就是Spark生态体系中的构建在SparkCore基础之上的一个基于SQL的计算模块。SparkSQL的前身不叫SparkSQL&#xff0c;而叫Shark&#xff0c;最开始的时候底层代码优化&#xff0c;sql的解析、执行引擎等等完全基于Hive&#xff0c…

Leetcode—706. 设计哈希映射【简单】(constexpr)

2024每日刷题&#xff08;127&#xff09; Leetcode—706. 设计哈希映射 数组实现代码 class MyHashMap { public:MyHashMap() {memset(arr, -1, sizeof(arr));}void put(int key, int value) {arr[key] value;}int get(int key) {if(arr[key] -1) {return -1;} return arr…