Linux socket编程(2):socket函数介绍及C/S模型代码实现

上一节简单介绍了一下套接字、字节序和地址结构体的概念,算是对socket有一个入门的了解。这一节就实现一个客户端-服务端的代码,从这个例子中来学习socket函数的使用。

文章目录

  • 1 客户端/服务端模型
  • 2 套接字函数
    • 2.1 socket:创建套接字
    • 2.2 bind:绑定套接字
    • 2.3 listen:监听套接字
    • 2.4 accpet:接受客户端连接请求
    • 2.5 connect:与服务器套接字建立连接
    • 2.6 close:关闭套接字
    • 2.7 write和send:写数据
    • 2.8 read和recv:读数据
  • 3 客户端/服务端模型实现
    • 3.1 代码
    • 3.2 运行结果
    • 3.3 Bind failed:Address already in use

1 客户端/服务端模型

在客户端-服务器模型中,客户端和服务器之间的协作和通信是通过网络实现的,允许用户从远程位置访问和利用服务器上的资源和服务。这种模型的广泛应用使得它成为网络应用程序设计的基本框架之一。对于Linux的socket来说,建立通信的整体流程如下:

在这里插入图片描述

2 套接字函数

首先先来学习一下前面的流程图中出现的一些套接字函数

2.1 socket:创建套接字

我们可以使用socket函数创建一个套接字,套接字可以是不同类型,例如TCP套接字或UDP套接字。

int socket(int domain, int type, int protocol);
  1. domain(地址族,Address Family):常用的有AF_INET(IPv4地址族)、AF_INET6(IPv6地址族)和AF_UNIX(Unix域套接字,用于本地进程之间的通信)
  2. type(套接字类型,Socket Type),常用的有:
    • SOCK_STREAM:流式套接字,也称为面向连接的套接字。用于可靠的、面向连接的数据传输,例如TCP。
    • SOCK_DGRAM:数据报套接字,也称为无连接的套接字。用于无连接、不可靠的数据传输,例如UDP。
  3. protocol(协议):设置0时系统会自动选择适当的协议,例如,创建TCP套接字会使用IPPROTO_TCP协议。

2.2 bind:绑定套接字

bind函数用于将一个套接字绑定到一个特定的IP地址和端口号,以便套接字可以在该地址上接受传入的连接请求或数据包。这是在创建服务器程序时常用的操作,因为服务器通常需要监听特定的地址和端口以等待客户端的连接。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:要绑定的套接字描述符
  • addr:指向一个struct sockaddr类型的结构体,其中包含了要绑定的IP地址和端口信息。在上一篇文章中有介绍,这个结构体可以是通用的,也可以是特定的(如IPV4、IPV6特定的)
  • addrlenaddr结构体的长度,通常使用sizeof(struct sockaddr)来获取

2.3 listen:监听套接字

listen函数用于将服务器套接字(通常是TCP套接字)设置为监听状态,以便它可以接受客户端的连接请求。listen函数通常与bindaccept函数一起使用,以创建一个典型的服务器程序,用于等待客户端连接。

int listen(int sockfd, int backlog);
  • sockfd:要监听的套接字描述符,通常是一个已经通过bind绑定到特定地址和端口的套接字

  • backlog:指定在等待队列中可以排队等待的连接请求的最大数量,即服务器可以同时处理的连接请求的数量

SOMAXCONN 可以作为backlog的参数,它是一个系统常量,表示系统所支持的最大队列长度。SOMAXCONN 的具体值在内核编译时就已经指定,通常是一个相对较大的正整数。使用 SOMAXCONN 可以确保使用系统支持的最大队列长度,以应对高负载的服务器程序。

在这里插入图片描述

如果用户设置的backlog大于SOMAXCONN,则backlog就会设置为SOMAXCONN

2.4 accpet:接受客户端连接请求

accept 函数用于在服务器端套接字(通常是被动套接字)上接受客户端的连接请求,创建一个新的套接字,并在新的套接字上与客户端进行通信。


主动套接字和被动套接字?

主动套接字是客户端套接字,使用 connect 主动连接到服务器。被动套接字是服务器套接字,使用 accept 来接受客户端的连接请求。


accept 是套接字编程中非常重要的函数之一,用于建立服务器与客户端之间的通信通道。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd:要接受连接请求的套接字描述符,通常是服务器端套接字,即被动套接字
  • addr:一个指向struct sockaddr类型的结构体指针,用于存储客户端的地址信息(IP地址和端口号)。传入 NULL表示不关心客户端地址信息。
  • addrlen:一个指向socklen_t类型的指针,用于传入addr结构体的长度。通常,可以传入NULL

2.5 connect:与服务器套接字建立连接

connect函数用于客户端套接字建立与服务器套接字的连接。它是网络编程中非常重要的函数,用于建立通信链路,以便客户端可以与服务器进行数据传输。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • sockfd:要连接的客户端套接字的描述符
  • addr:指向一个struct sockaddr类型的结构体指针,其中包含了服务器的地址信息,包括 IP 地址和端口号。这个结构体的类型(AF_INETAF_INET6 等)应与客户端套接字的类型相匹配。
  • addrlenaddr 结构体的长度。

2.6 close:关闭套接字

当不再需要使用套接字时,使用close函数可以释放相关资源,包括文件描述符和系统内核资源。

int close(int sockfd);
  • sockfd:要关闭的套接字的文件描述符。

2.7 write和send:写数据

write函数:通用的文件写入函数,可以用于向文件描述符写入数据,也可以用于套接字。

ssize_t write(int fd, const void *buf, size_t count);
  • fd 是文件描述符,可以是套接字描述符,也可以是其他文件描述符。
  • buf 是包含要写入数据的缓冲区的指针。
  • count 是要写入的数据字节数。
  • 返回值是发送成功的字节数,如果发送失败则返回-1,并使用errno设置错误代码。

send函数:专门用于套接字通信的函数,它提供了更多的选项和控制来处理套接字数据发送。

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
  • sockfd 是套接字描述符。

  • buf 是包含要发送数据的缓冲区的指针。

  • len 是要发送的数据字节数。

  • flags 是一组标志,可以用来控制发送操作的行为,例如设置非阻塞发送等。(后续用到再介绍)

  • 返回值是发送成功的字节数,如果发送失败则返回-1。

2.8 read和recv:读数据

read函数:通用的文件读取函数,可以用于从文件描述符读取数据,也可以用于套接字。

ssize_t read(int fd, void *buf, size_t count);
  • fd 是文件描述符,可以是套接字描述符,也可以是其他文件描述符。
  • buf 是接收数据的缓冲区的指针。
  • count是要读取的数据字节数。
  • 返回值是接收成功的字节数,如果接收失败则返回-1,并使用全局变量errno设置错误代码。

recv函数:专门用于套接字通信的函数,它提供了更多的选项和控制来处理套接字数据接收。

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
  • sockfd是套接字描述符。
  • buf是接收数据的缓冲区的指针。
  • len是要接收的数据字节数。
  • flags是一组标志,可以用来控制接收操作的行为,例如设置非阻塞接收等。(后续用到再介绍)
  • 返回值是接收成功的字节数,如果接收失败则返回-1。

3 客户端/服务端模型实现

下面实现一个客户端/服务端模型的例子,具体功能为:客户端发一个字符串给服务端,服务端收到后打印。

3.1 代码

(1)服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main() {
    // 创建套接字
    int server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("Socket creation failed");
        exit(1);
    }

    // 绑定套接字到IP地址和端口
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(8080);
    server_address.sin_addr.s_addr = INADDR_ANY;
    //server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("Bind failed");
        close(server_socket);
        exit(1);
    }

    // 设置服务器套接字为监听状态
    if (listen(server_socket, 5) == -1) {
        perror("Listen failed");
        close(server_socket);
        exit(1);
    }

    printf("Server listening on port 8080...\n");

    // 接受客户端连接
    struct sockaddr_in client_address;
    socklen_t client_len = sizeof(client_address);
    int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_len);
    if (client_socket == -1) {
        perror("Accept failed");
        close(server_socket);
        exit(1);
    }
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    // 从客户端接收数据
    ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
    if (bytes_received == -1) {
        perror("Receive failed");
    } else {
        printf("Client says: %s\n", buffer);
    }
    // 发送响应给客户端
    const char *response = "Hello from server!";
    send(client_socket, response, strlen(response), 0);
    // 关闭套接字
    close(client_socket);
    close(server_socket);

    return 0;
}

(2)客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main() {
    // 创建套接字
    int client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket == -1) {
        perror("Socket creation failed");
        exit(1);
    }

    // 设置服务器地址和端口
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(8080);
    //server_address.sin_addr.s_addr = INADDR_ANY;
	server_address.sin_addr.s_addr = inet_addr("127.0.0.1");
    // 连接到服务器
    if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("Connection failed");
        close(client_socket);
        exit(1);
    }

    // 发送数据给服务器
    const char *message = "Hello from client!";
    send(client_socket, message, strlen(message), 0);

    // 接收服务器的响应
    char buffer[1024];
    memset(buffer, 0, sizeof(buffer));
    ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
    if (bytes_received == -1) {
        perror("Receive failed");
    } else {
        printf("Server says: %s\n", buffer);
    }

    // 关闭套接字
    close(client_socket);

    return 0;
}

服务端IP地址设置为INADDR_ANY

当服务器端将IP地址设置为 INADDR_ANY 时,假设服务器上有多个网卡,那么它将绑定到所有可用的网络接口和IP地址。这意味着服务器将监听所有网络接口上的传入连接请求,而不限制于特定的IP地址。这通常用于创建一个通用的服务器,它可以接受来自任何网络接口和任何IP地址的连接请求。

对于客户端来说,通常不会将IP地址设置为 INADDR_ANY,因为客户端通常是主动发起连接请求的一方,而不是绑定到特定的IP地址。客户端通常不需要关心绑定到哪个IP地址,而是根据服务器的IP地址和端口号来连接到服务器。

3.2 运行结果

编译后,运行server程序,服务端监听8080端口:

在这里插入图片描述

运行client程序后,client与server建立连接,客户端发送的Hello from client!,服务端收到后打印出来:

在这里插入图片描述

3.3 Bind failed:Address already in use

如下图所示,在服务端程序退出后马上再运行服务端,会提示Adress already in use。
在这里插入图片描述
当一个套接字绑定到一个特定的IP地址和端口后,如果你关闭该套接字,操作系统通常会保留一段时间,称为TIME_WAIT状态,以确保任何挂起的数据包都能够到达其目的地。这样可以避免在相同的地址和端口上立即重新绑定套接字时出现问题。

解决
在创建套接字时使用setsockopt函数设置SO_REUSEADDR选项,以允许在TIME_WAIT状态下重新绑定相同的地址和端口。

int reuse = 1;
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));

在前面的服务端代码中增加上面的代码:
在这里插入图片描述

可以看到,在服务端退出后,再马上运行服务端的程序不会出现Address already in use的提示
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/142513.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【PC】开发者日志:竞技比赛验证系统强化

各位玩家大家好&#xff01;欢迎收看本期开发者日志。 在11月1日发布的第26赛季第2轮更新公告中&#xff0c;我们提到了有关强化比赛验证系统的内容。想必各位玩家一定会对我们加强验证系统的背景和意图感到好奇&#xff0c;为此我们想通过今天这篇反作弊开发者日志来向大家更详…

Python高级语法----使用Python进行模式匹配与元组解包

文章目录 1. 模式匹配的新特性2. 高级元组解包技巧3. 数据类的匹配与应用1. 模式匹配的新特性 Python自3.10版本起引入了结构化模式匹配的新特性,这是一种强大的工具,允许开发者用更清晰、更直观的方式处理数据结构。模式匹配类似于其他编程语言中的switch-case语句,但它更…

C++字典树算法:找出强数对的最大异或值 II

涉及知识点 数学 字典树 题目 给你一个下标从 0 开始的整数数组 nums 。如果一对整数 x 和 y 满足以下条件&#xff0c;则称其为 强数对 &#xff1a; |x - y| < min(x, y) 你需要从 nums 中选出两个整数&#xff0c;且满足&#xff1a;这两个整数可以形成一个强数对&…

Python高级语法----Python C扩展与性能优化

文章目录 1. 编写Python C扩展模块示例代码编译和运行运行结果2. 利用Cython优化性能示例代码编译和运行运行结果3. Python性能分析工具示例代码分析结果1. 编写Python C扩展模块 Python C扩展模块允许你将C语言代码集成到Python程序中,以提高性能。这对于计算密集型任务特别…

20.1 platform 设备驱动

一、Linux 驱动的分离与分层 1. 驱动的分隔和分离 现在有三个平台&#xff0c;A、B 和 C&#xff0c;这三个平台都有 MPU6050 设备。编写最简单的驱动框架如下图&#xff1a; 每个平台下都有一个主机驱动和设备驱动&#xff0c;主机驱动是必要的&#xff0c;因为不同的平台 I2…

ceph修复pg inconsistent( scrub errors)

异常情况 1、收到异常情况如下: OSD_SCRUB_ERRORS 12 scrub errors PG_DAMAGED Possible data damage: 1 pg inconsistentpg 6.d is activeremappedinconsistentbackfill_wait, acting [5,7,4]2、查看详细信息 登录后复制 #ceph health detail HEALTH_ERR 12 scrub errors…

基于 PostgreSQL 构建 AI 电商产品图片相似度搜索方案

在这篇文章中&#xff0c;将介绍如何基于向量数据库&#xff0c;构建一个电商产品图片目录的向量相似度查询解决方案。我们将通过 Amazon SageMaker、pgvector 向量数据库扩展插件、小型语言模型助力 AI 图片搜索能力&#xff0c;从而在产品目录中查找到最符合条件的产品&#…

内网渗透(frp和proxychains4)

一、准备工作 需要三台机器&#xff0c;去哦这里准备的是win7&#xff08;目标主机&#xff09;&#xff0c;kali&#xff08;攻击者&#xff09;&#xff0c;红帽&#xff08;跳板&#xff09; 攻击机&#xff08;kali&#xff09;&#xff1a;192.168.10.15 跳板机&#xff0…

未来智能工厂如何从实时定位系统 (RTLS) 获取价值

实时定位系统数据技术取得了显着进步&#xff0c;制造商如何利用这些数据来优化流程并在其独特的环境中提高价值将推动他们在未来几年取得成功 以下信息源自 WISER 使用 (UWB) RTLS 解决方案在重工业环境中测试和验证的经验&#xff1a; 第 1 章&#xff1a;工业 4.0 中位置数…

桶装水订水送水app有哪些功能?

桶装水订水送水app是一款专为送水工量身打造的提供送水服务的软件&#xff0c;在这里&#xff0c;送水人员将更好的在线发布一些送水信息&#xff0c;在线接单等功能&#xff0c;极大的提高了工作效率&#xff0c;方便了日常生活。 系统的商户端&#xff0c;专为送水工日常送水…

如何使用Cpolar+Tipask,在ubuntu系统上搭建一个私人问答网站

文章目录 前言2.Tipask网站搭建2.1 Tipask网站下载和安装2.2 Tipask网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar临时数据隧道3.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3 Cpolar稳定隧道&#xff08;本地设置&#xff09; 4. 公网访问测试5. 结语 前…

关于财税体制什么是扁平化?三级财政西方的龙骑兵FIBW 10道练习题第五题

目录 关于财税体制什么是扁平化&#xff1f; 三级财政 西方的龙骑兵 FIBW 10道练习题 第五题 关于财税体制什么是扁平化&#xff1f; 在财税领域&#xff0c;"扁平化"&#xff08;Flattening&#xff09;通常指的是简化税收结构&#xff0c;减少税种和税率的层…

王道计网:网络层

转发是路由器内部 路由选择是路由器之间 一、概述和功能

Java版本+企业电子招投标系统源代码+支持二开+招投标系统+中小型企业采购供应商招投标平台

功能模块&#xff1a; 待办消息&#xff0c;招标公告&#xff0c;中标公告&#xff0c;信息发布 描述&#xff1a; 全过程数字化采购管理&#xff0c;打造从供应商管理到采购招投标、采购合同、采购执行的全过程数字化管理。通供应商门户具备内外协同的能力&#xff0c;为外部供…

牛客网:OR36 链表的回文结构

一、题目 函数原型&#xff1a; bool chkPalindrome(ListNode* A) 二、思路 判断一个单链表是否为回文结构&#xff0c;由于单链表不能倒序遍历&#xff0c;所以需要找到单链表的后半段&#xff0c;并将其逆置&#xff0c;再与前半段链表进行比较。 如何找到单链表的后半段呢&a…

对比国内主流开源 SQL 审核平台 Yearning vs Archery

Yearning, Archery 和 Bytebase 是目前国内最主流的三个开源 SQL 审核平台。其中 Yearning 和 Archery 是社区性质的项目&#xff0c;而 Bytebase 则是商业化产品。通常调研 Bytebase 的用户也会同时比较 Yearning 和 Archery。 下面我们就来展开对比一下 Yearning 和 Archery…

服务器数据恢复—磁盘出现坏道掉线导致raid5阵列崩溃的数据恢复案例

服务器数据恢复环境&#xff1a; 某品牌服务器中有一组16块SAS接口硬盘组建的raid5磁盘阵列。 服务器故障&检测&#xff1a; 服务器raid5阵列中有2块硬盘掉线&#xff0c;上层服务器应用崩溃&#xff0c;导致服务器数据丢失。丢失的数据主要是4个1.5TB大小的卷中的数据&am…

域名反查Api接口——让您轻松查询域名相关信息

在互联网发展的今天&#xff0c;域名作为网站的唯一标识符&#xff0c;已经成为了企业和个人网络营销中不可或缺的一部分。为了方便用户查询所需的域名信息&#xff0c;API接口应运而生。本文将介绍如何使用挖数据平台《域名反查Api接口——让您轻松查询域名相关信息》进行域名…

python中的切片操作

切片操作&#xff1a; 1.切片操作是访问元素序列的另一种方法&#xff0c;它可以访问一定范围内的元素。通过切片操作形成一个新序列 语法结构&#xff1a; 序列【start&#xff1a;end&#xff1a;step】 参数说明&#xff1a; start&#xff1a;表示切片的开始位置&#x…