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的提示
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构-堆排序及其复杂度计算

目录 1.堆排序 1.1 向上调整建堆 1.2 向下调整建堆 2. 两种建堆方式的时间复杂度比较 2.1 向下调整建堆的时间复杂度 2.2 向上调整建堆的时间复杂度 Topk问题 上节内容&#xff0c;我们讲了堆的实现&#xff0c;同时还包含了向上调整法和向下调整法&#xff0c;最后我们…

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

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

二十四、W5100S/W5500+RP2040树莓派Pico<PHY的状态模式控制>

文章目录 1. 前言2. 相关简介2.1 简述2.2 原理2.3 优点&应用 3. WIZnet以太网芯片4. PHY模式配置测试4.1 程序流程图4.2 测试准备4.3 连接方式4.4 相关代码4.5 测试现象 5. 注意事项6. 相关链接 1. 前言 W5100S/W5500不仅支持自动PHY自动协商&#xff0c;而且支持用户自定义…

vue3 ref 与shallowRef reactive与shallowReactive

ref 给数据添加响应式&#xff0c;基本类型采用object.defineProperty进行数据劫持&#xff0c;对象类型是借助reactive 实现响应式&#xff0c;采用proxy 实现数据劫持&#xff0c;利用reflect进行源数据的操作 let country ref({count:20,names:[河南,山东,陕西],objs:{key…

C/C++交换输出 2021年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C交换输出 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C交换输出 2021年9月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 输入两个整数a,b&#xff0c;将它们交换输出 2、输入输…

概率论和数理统计(三)数理统计基本概念

前言 “概率论”是给定一个随机变量X的分布F(x),然后求某事件A概率 P ( x ∈ A ) P(x \in A) P(x∈A)或者随机变量X的数字特征.“统计”是已知一组样本数据 { x 1 , x 2 , . . . x n } \{x_1,x_2,...x_n\} {x1​,x2​,...xn​},去求分布F(x) 统计的基本概念 在统计中&#x…

Android framework添加自定义的Product项目,lunch目标项目

文章目录 Android framework添加自定义的Product项目1.什么是Product&#xff1f;2.定义自己的Product玩一玩 Android framework添加自定义的Product项目 1.什么是Product&#xff1f; 源码目录下输入lunch命令之后&#xff0c;简单理解下面这些列表就是product。用于把系统编…

飞天使-template模版相关知识

遇到报错django.template.exceptions.TemplateSyntaxError: ‘staticfiles’ is not a registered tag library. Must ROOT_URLCONF TEMPLATES [{BACKEND: django.template.backends.django.DjangoTemplates,DIRS: [os.path.join(BASE_DIR, templates)],APP_DIRS: True,OPTI…

c++四种类型转换

首先我们要先引入上行转换和下行转换的概念 所谓上行转换&#xff0c;即将原来的子类指针转换成父类指针&#xff1b; 下行转换即将原来的父类指针转换成子类指针 由于子类对象的空间较大&#xff0c;所以把子类强制转换父类给父类指针赋值时&#xff0c;父类指针对象能读取…

ISP图像处理Pipeline

参考&#xff1a;1. 键盘摄影(七)——深入理解图像信号处理器 ISP2. Understanding ISP Pipeline3. ISP图像处理流程介绍4. ISP系统综述5. ISP(图像信号处理)之——图像处理概述6. ISP 框架7. ISP(图像信号处理)算法概述、工作原理、架构、处理流程8. ISP全流程简介9. ISP流程介…

1.jvm基本知识

目录 概述jvm虚拟机三问jvm是什么&#xff1f;java 和 jvm 的关系 为什么学jvm怎么学习为什么jvm调优?什么时候jvm调优调优调什么 结束 概述 相关文章在此总结如下&#xff1a; 文章地址jvm类加载系统地址双亲委派模型与打破双亲委派地址运行时数据区地址 jvm虚拟机三问 j…

Python 日志记录器logging 百科全书 之 日志回滚

Python 日志记录器logging 百科全书 之 日志回滚 前言 在之前的文章中&#xff0c;我们学习了关于Python日志记录的基础配置。 本文将深入探讨Python中的日志回滚机制&#xff0c;这是一种高效管理日志文件的方法&#xff0c;特别适用于长时间运行或高流量的应用。 知识点&…