Linux网络编程:套接字编程

1.Socket套接字编程

1.1.什么是socket套接字编程

Socket套接字编程 是一种基于网络层和传输层网络通信方式,它允许不同主机上的应用程序之间进行双向的数据通信。Socket是网络通信的基本构件,它提供了不同主机间的进程间通信端点的抽象。一个Socket就是一个通信端点,它提供了应用程序访问网络通信协议(如TCP/IP)的接口。并且Socket编程基于客户端(Client)和服务器端(Server)进行全双工通信!!!

  • 客户端:通常指的是发起连接请求的一方,它使用Socket API创建一个Socket对象,并指定要连接的服务器地址和端口号,然后向服务器发送连接请求。连接建立后,客户端就可以通过Socket发送和接收数据了。
  • 服务器端:则是监听来自客户端的连接请求的一方。服务器端也使用Socket API创建一个Socket对象,并绑定到一个指定的地址和端口号上,然后开始监听来自客户端的连接请求。当有客户端连接时,服务器端会接受这个连接,并创建一个新的Socket对象来与这个客户端进行通信。

简单来说,Socket编程就是使用Socket API(应用程序接口)来编写网络应用程序。这些网络应用程序可以是客户端,也可以是服务器端,它们通过Socket进行数据的发送和接收。

1.2.如何进行socket套接字编程

首先socket套接字编程是基于TCP、IP四层网络协议栈实现的,而在传输层协议中UDP协议是无连接、面向数据报的,TCP协议是有链接、面向字节流的,因此系统维护了两套Socket套接字编程接口,给UDP场景和TCP场景使用!!!

1.2.1.UDP的套接字编程

// UDP服务器
{
    int port;                                     // 服务器开放的端口号
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); // 网络通信,UDP套接字

    // 填充sockaddr结构体对象
    struct sockaddr_in local;
    bzero(&local, sizeof(local)); // 初始化结构体

    local.sin_family = AF_INET;         // 绑定网络通信
    local.sin_port = htons();           // 绑定端口
    local.sin_addr.s_addr = INADDR_ANY; // 允许所有外来ip访问

    // 绑定指定网络信息和指定的文件系统
    int n = ::bind(sock_fd, (struct sockaddr *)&local, sizeof(local));

    // 获取客户端信息
    char buff_r[1024];
    sockaddr_in peer;
    socklen_t len = sizeof(peer);
    ssize_t n = recvfrom(sock_fd, buff_r, sizeof(buff_r) - 1, 0, (struct sockaddr *)&peer, &len);

    // 给客户端发送信息
    std::string buffer;
    ssize_t m = sendto(sock_fd, buffer.c_str(), buffer.size(), 0, (struct sockaddr *)&peer, &len);
}

服务器进行套接字编程流程:

  1. 通过socket函数获取套接字,其中SOCK_DGRAM对应UDP协议
  2. 构建一个sockaddr_in对象,并绑定端口和设置允许任意的IP地址访问服务器
  3. 接着通过bind函数显性绑定套接字和sockaddr_in对象
  4. 接下来就可以和客户端进行IO通信了!!!
/ UDP客户端
{
    int port_server;                              // 链接服务器的端口号
    int ip_server;                                // 链接服务器的端口号
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0); // 网络通信,UDP套接字

    struct sockaddr_in server;
    bzero(&server, sizeof(server));                        // 初始化结构体
    server.sin_family = AF_INET;                           // 设置为网络协议
    server.sin_port = htons(port_server);                  // 绑定服务器端口
    server.sin_addr.s_addr = inet_addr(ip_server.c_str()); // 实现ip的动态绑定

    // 客户端不用通过bind函数显性绑定套接字
    // 因为服务器先启动,已经获得了套接字,只要绑定服务器的ip和端口就能使用这个套接字

    // 向服务器发送信息
    std::string buffer;
    ssize_t n = sendto(sock_fd, buffer.c_str(), buffer.size(), 0, (struct sockaddr *)&server, sizeof(server));

    // 从服务端获取信息
    char buff_r[1024];
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    ssize_t m = recvfrom(sock_fd, buff_r, sizeof(buff_r) - 1, 0, (struct sockaddr *)&client, &len);
}

客户端进行套接字编程的流程:

  1. 通过socket函数获取套接字,其中SOCK_DGRAM对应UDP协议
  2. 绑定服务器端口和服务器ip地址
  3. 直接进行和服务器的IO通信

服务端和客户端Socket编程的异同

  • 相同的是:都需要调用socket函数来获取套接字,设置网络协议为AF_INET和SOCK_DGRAM,并且需要设置sockaddr_in结构体,初始化这个结构体的内置变量。均共用一套IO的接口sendto和recvfrom。
  • 不同的是:服务端的IP地址设置为INADDR_ANY,表示可以绑定多个IP地址,这也符合服务器需要和多台客户端进行IO的特性。另外服务端需要显性地绑定socket_fd(套接字文件描述符)和sockaddr_in(IPV4套接字结构)。而客户端需要绑定唯一一个服务器的IP,并且不需要显性的绑定socket_fd和sockaddr_in。

1.2.2.TCP套接字编程

TCP协议是面向连接的,所以与UDP套接字流程相比,除了绑定套接字,TCP需要在通信之前先建立连接,具体来说就是:服务器监听客户端发出链接请求请求,接着客户端发出connect请求,最终服务器接收请求,获取一个通信的套接字,最终完成链接的建立。接着再进行IO通信!!!

// TCP服务器
{
    // 创建监听套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);

    // 定义并配置本地
    struct sockaddr_in local;
    bzero(&local, sizeof(local));

    local.sin_family = AF_INET;
    local.sin_port = htons(_port);
    local.sin_addr.s_addr = INADDR_ANY;

    int n = ::bind(listen_sock, (struct sockaddr *)&local, sizeof(local));

    // TCP是面向连接的,需要监听client的链接
    int m = listen(listen_sock, 5); // 对listen这个套接字进行监听是否完成链接,设置全连接队列为5

    // 获取连接
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);
    // accept返回的新的套接字(通信套接字)
    int sock_fd = accept(_listen_sock, (struct sockaddr *)&peer, &len); // 用于数据通信,accept未接收会阻塞(未完成通信)!!

    // 通过read、write函数进行IO通信

    std::string buffer;
    ssize_t m = write(sock_fd, buffer.c_str(), buffer.size());

    char buffer_read[1024];
    ssize_t n = read(sock_fd, buffer_read, sizeof(buffer_read));
}

TCP服务端套接字编程的流程:

  1. 创建监听的套接字,然后设置协议为AF_INET和SOCK_STREAM(TCP专用)
  2. 定义并配置套接字结构体,最后进行监听套接字和网络套接字的结构体绑定
  3. 进行监听(在此期间等待客户端的connect请求)
  4. 设置网络套接字来接收客户端的信息,并通过accept函数获取到新的通信套接字
  5. 进行通信IO
// TCP客户端
{
    // 创建套接字
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);

    // 绑定服务器
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);

    // TCP链接服务器
    int n = connect(sock_fd, (struct sockaddr *)&server, sizeof(server));

    // 通过read、write函数进行IO通信

    std::string buffer;
    ssize_t m = write(sock_fd, buffer.c_str(), buffer.size());

    char buffer_read[1024];
    ssize_t n = read(sock_fd, buffer_read, sizeof(buffer_read));
}

TCP客户端套接字编程流程:

  1. 创建套接字,并将网络套接字结构体绑定到服务器
  2. 调用connect发起链接服务器的请求(此时处于服务端监听状态)
  3. 完成链接,进行IO通信

到了这里,我们已经知道如何用代码来构建UDP、TCP通信最基本的架构了,而TCP是面向连接的,这也体现在listen、connect、accept这三个函数中(跟三次握手紧密相关,但不等同)。

如图为TCP中服务端和客户端建立通信的过程。

2.理解Socket套接字编程结构

我们在1.2中学习了如何搭建Socket套接字编程的结构,但是我们还不知道什么是Socket、什么是sockaddr_in和为什么要将sockaddr_in类型强转为(struct sockaddr*)等等,所以在有了对Socket编程的使用理解的基础上,我们来讲一下原理!!!

2.1.网络字节序

我们知道主机的地址排布,也就是字节序是可能存在不同的,大端机的字节序排列为高地址,小端机的字节序排列为低地址,那么这样就会导致在网络通信时,字节序读取不一致导致数据不一致问题。

例如在大端机中,32位整数,0x12345678,地址排布为:78563412,小端机则表示为:12345678

所以为了统一字节序的读取,在套接字编程中需要对网络字节序进行规定,以大端字节序为网络字节序标准,进行读取。我们在回到我们的代码中:

local.sin_port = htons(port);    // htons即为字节序转换函数 

而这些htons函数为系统提供的转换字节序的接口函数!

#include <arpa/inet.h>//必须包含的头文件
// 主机序列转网络序列
uint32_t htonl(uint32_t hostlong);//将主机上unsigned int类型的数据转换成对应网络字节序
uint16_t htons(uint16_t hostshort);//将主机上unsigned short类型的数据转换成对应网络字节序
// 网络序列转主机序列
uint32_t ntohl(uint32_t netlong);//将从网络中读取的unsigned int类型的数据转换成当前计算机字节序
uint16_t ntohs(uint16_t netshort);//将从网络中读取的unsigned short类型的数据转换成当前计算机字节序

所以当我们在网络中获取了一些字节序数据,我们需要对他进行大端字节序的转换成符合本机的字节序列。

这里即为将网络获取的数据字节序转化为本机的数据字节序,而htons即为将本机的字节序数据转化为网络字节序(大端)

2.2.网络套接字结构体

我们之前在1.2.提及了一个新名称"网络套接字结构体",而这个结构体用于标识网络通信的端点,包括IP地址、端口号和地址族等信息。

// 通用网络套接字结构
struct sockaddr
{
  __SOCKADDR_COMMON(sa_); /* Common data: address family and length.  */
  char sa_data[14];       /* Address data.  */
};

// 网络套接字结构
struct sockaddr_in
{
  __SOCKADDR_COMMON(sin_);
  in_port_t sin_port;      /* Port number.  */
  struct in_addr sin_addr; /* Internet address.  */

  /* Pad to size of `struct sockaddr'.  */
  unsigned char sin_zero[sizeof(struct sockaddr) -
                         __SOCKADDR_COMMON_SIZE -
                         sizeof(in_port_t) -
                         sizeof(struct in_addr)];
};

如图:我们在网络进行IO通信时,就是传输这个通用结构体对象sockaddr,所以我们在1.2.中的代码中也经常看到类型转换为(struct sockaddr *)。而这里也可以看作是C语言实现的多态,其中sockaddr为基类、sockaddr_in和sockaddr_un为派生类。

3.文件+socket+系统+网络

66-2小时33分

相关推荐
LUCIAZZZ3 分钟前
简单说一下什么是RPC
java·网络·网络协议·计算机网络·spring cloud·rpc
千墨9 分钟前
VMware安装Centos 9虚拟机+设置共享文件夹+远程登录
linux·运维·centos
一勺菠萝丶24 分钟前
计算机专业知识【深入理解子网中的特殊地址:为何 192.168.0.1 和 192.168.0.255 不能随意分配】
网络·智能路由器
s_fox_31 分钟前
Nginx Embedded Variables 嵌入式变量解析(4)
java·网络·nginx
ChinaRainbowSea1 小时前
1. Linux下 MySQL 的详细安装与使用
linux·数据库·sql·mysql·adb
etcix1 小时前
实现一个简单的拉取网络todo app
网络
网络安全(华哥)1 小时前
网络安全服务实施流程管理 网络安全服务体系
运维·服务器·网络
致奋斗的我们2 小时前
Nginx反向代理及负载均衡
linux·运维·mysql·nginx·负载均衡·shell·openeluer
百锦再2 小时前
在Linux上创建一个Docker容器并在其中执行Python脚本
linux·python·docker
忧虑的乌龟蛋2 小时前
嵌入式 Linux:使用设备树驱动GPIO全流程
linux·服务器·嵌入式·imx6ull·gpio·点灯·pinctrl