C语言网络编程深入研究

news2024/11/23 2:17:46

在这里插入图片描述
网络编程是现代软件开发中的一个重要部分,它允许不同计算机之间相互通信和交换数据。本指南将深入探讨使用C语言进行网络编程的技术细节,特别是TCP/IP协议族的核心概念和技术实现。我们将通过具体的代码示例来讨论如何创建客户端和服务器程序,并探讨一些高级主题,如多线程、非阻塞I/O以及信号处理。

1. 引言

随着互联网的普及,网络编程已成为一种基本技能。无论是构建分布式系统还是开发Web应用程序,理解如何使用底层网络API都是非常重要的。本指南旨在提供一个全面的框架,帮助读者深入了解网络编程的基本原理和实践技巧。

2. 网络编程基础

2.1 地址结构

在开始编写网络程序之前,我们需要了解网络地址是如何表示的。sockaddr结构体用于存储网络套接字地址信息。最常见的类型包括sockaddr_in(IPv4)和sockaddr_in6(IPv6)。这些结构体包含了必要的信息以确定通信的目的地。

sockaddr_in 结构体详解
#include <sys/socket.h>

struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_port = htons(8080); // 目标端口
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 目标IP地址
  • sin_family:指定地址家族,AF_INET代表IPv4,AF_INET6代表IPv6。地址家族指定了套接字的地址格式,AF_INET用于IPv4地址,而AF_INET6用于IPv6地址。
  • sin_port:目标端口号,使用htons()函数转换为主机字节序。这是因为端口号在网络上传输时采用网络字节序,而主机字节序可能是不同的,因此需要转换。网络字节序是一种大端字节序,而主机字节序可能是小端字节序,具体取决于主机硬件架构。
  • sin_addr:目标IP地址,使用inet_addr()函数转换为32位整数格式。inet_addr()函数会将点分十进制字符串转换成32位整数,即网络字节序表示的IP地址。这个转换确保了地址的一致性和正确性。

2.2 创建套接字

创建一个网络连接的第一步是使用socket()函数创建一个套接字。这个函数需要三个参数:地址家族、套接字类型和协议。

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
    perror("socket");
    exit(EXIT_FAILURE);
}
  • AF_INET:IPv4地址家族。地址家族指定了套接字使用的地址格式,AF_INET用于IPv4地址。
  • SOCK_STREAM:TCP套接字类型。TCP提供了一种可靠的、面向连接的服务,适合于需要可靠传输的应用场景。
  • 0:默认协议(TCP),通常这里填入0,表示使用默认的协议栈。实际上,TCP协议编号是6,但是这里使用0表示让内核根据套接字类型自动选择合适的协议。

2.3 配置套接字选项

除了创建套接字之外,我们还可以配置一些套接字选项以改善性能或者满足特定需求。例如,我们可以设置套接字为重用地址(SO_REUSEADDR),这样即使套接字还没有完全释放,也可以重新绑定相同的地址和端口。

int enable = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
  • SOL_SOCKET:表示这些选项适用于套接字层。这个级别包含了一些通用的选项,可以应用于任何类型的套接字。
  • SO_REUSEADDR:允许在短时间内重复使用同一个地址和端口。这对于快速重启服务非常有用,因为在某些情况下,即使服务停止运行,其地址也可能暂时不可用,因为内核可能仍然保留着地址信息一段时间。

2.4 设置超时

有时,为了防止程序无限制地等待连接或数据传输完成,可以设置超时时间。这可以通过设置套接字选项来实现。

struct timeval timeout;
timeout.tv_sec = 5; // 5 seconds
timeout.tv_usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const void *)&timeout, sizeof(timeout));
  • SO_RCVTIMEO:设置接收超时。这意味着如果在指定时间内没有接收到数据,套接字将返回一个错误,通常是一个EAGAINEWOULDBLOCK错误。
  • SO_SNDTIMEO:设置发送超时。如果在指定时间内没有发送完数据,则同样会返回一个错误。

3. 建立连接

3.1 服务器端

服务器通常监听特定端口上的连接请求。首先需要将服务器地址绑定到套接字上,然后调用listen()来启动监听。

绑定服务器地址到套接字
// 绑定服务器地址到套接字
int bind_status = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (bind_status == -1) {
    perror("bind");
    close(sockfd);
    exit(EXIT_FAILURE);
}
  • bind():将服务器地址绑定到套接字上。这一步非常重要,因为它告诉操作系统哪些网络地址可以用来接收连接。如果失败,通常是因为地址已经被占用,或者地址格式不正确。
  • sizeof(server_addr):传递地址大小。这是为了让操作系统知道地址结构体的大小,从而正确地绑定地址。
启动监听
// 开始监听连接
int listen_status = listen(sockfd, SOMAXCONN);
if (listen_status == -1) {
    perror("listen");
    close(sockfd);
    exit(EXIT_FAILURE);
}
  • SOMAXCONN:最大待处理连接数,由内核决定。这个值通常由内核自动设置,代表可以排队的最大连接数。如果超过这个数值,新的连接请求就会被拒绝。这个值通常是64或128,取决于操作系统。

3.2 客户端

客户端则需要主动发起连接请求。使用connect()函数可以建立与服务器的连接。

int connect_status = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (connect_status == -1) {
    perror("connect");
    close(sockfd);
    exit(EXIT_FAILURE);
}
  • connect():尝试与服务器建立连接。客户端使用这个函数来发起连接请求。如果成功,客户端就与服务器建立了连接。如果失败,可能是因为服务器未监听指定端口,或者网络问题导致连接无法建立。

4. 数据传输

一旦建立了连接,就可以使用read()write()函数来进行数据交换。

发送数据
const char *message = "Hello, Server!";
ssize_t bytes_sent = write(sockfd, message, strlen(message));
if (bytes_sent == -1) {
    perror("write");
    close(sockfd);
    exit(EXIT_FAILURE);
}
  • write():发送数据到服务器。当客户端或服务器要发送数据时,使用write()函数将数据写入套接字。如果失败,通常是因为套接字已关闭或出现其他错误。
接收数据
char buffer[1024];
ssize_t bytes_received = read(sockfd, buffer, sizeof(buffer) - 1);
if (bytes_received > 0) {
    buffer[bytes_received] = '\0'; // Null terminate the string
    printf("Received: %s\n", buffer);
}
  • read():从服务器接收数据。当客户端或服务器要读取数据时,使用read()函数从套接字中读取数据。注意,读取的数据长度可能小于请求的长度,因为TCP是流式协议,数据可能会分段传输。此外,如果读取失败,可能是由于连接断开或套接字关闭。

5. 高级主题

在这里插入图片描述

5.1 多线程

为了处理多个并发连接,可以使用多线程技术。每当有新的连接到达时,创建一个新的线程来处理该连接。

创建线程处理函数
void *handle_client(void *arg) {
    int client_sock = *(int *)arg;
    char buffer[1024];
    ssize_t bytes_received = read(client_sock, buffer, sizeof(buffer) - 1);
    if (bytes_received > 0) {
        buffer[bytes_received] = '\0'; // Null terminate the string
        printf("Received from client: %s\n", buffer);
    }
    close(client_sock);
    pthread_exit(NULL);
}
  • pthread_create():创建新线程来处理客户端连接。每当有一个新的客户端连接到来时,服务器可以创建一个新的线程来处理这个连接。这种方式提高了服务器的响应能力,因为服务器可以同时处理多个客户端连接。
  • pthread_join():等待线程结束。如果服务器需要等待所有线程处理完毕后再退出,可以使用pthread_join()函数来等待特定线程结束。
主函数中接受连接并创建线程
while (1) {
    int client_sock;
    socklen_t addr_size = sizeof(server_addr);
    client_sock = accept(sockfd, (struct sockaddr *)&server_addr, &addr_size);
    if (client_sock == -1) {
        perror("accept");
        continue;
    }
    // 创建线程处理客户端连接
    pthread_t thread_id;
    pthread_create(&thread_id, NULL, handle_client, &client_sock);
}
  • accept():接受新的连接。当服务器监听到新的连接请求时,使用accept()函数接受这个连接。accept()函数会返回一个新的套接字描述符,专门用于处理这个新连接。如果接受失败,可能是由于资源不足或其他原因。
  • pthread_create():为每个连接创建新线程。这使得服务器可以同时处理多个客户端连接。每个连接都有自己的线程,因此可以独立地处理数据。

5.2 非阻塞I/O

非阻塞I/O允许程序在没有数据可读或写的情况下立即返回,而不是等待。这可以通过设置套接字选项来实现。

设置非阻塞I/O模式
int flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
  • fcntl(sockfd, F_GETFL, 0):获取文件状态标志。使用fcntl()函数获取套接字的当前文件状态标志。这一步是为了检查当前的文件状态标志,并准备设置非阻塞标志。
  • fcntl(sockfd, F_SETFL, flags | O_NONBLOCK):设置非阻塞标志。添加O_NONBLOCK标志,使套接字进入非阻塞模式。非阻塞模式下,当没有数据可读或写时,read()write()函数会立即返回,而不是阻塞等待。

5.3 信号处理

处理信号可以让程序更加健壮。例如,在接收到SIGINT信号时优雅地关闭套接字。

设置信号处理器
void signal_handler(int signum) {
    if (signum == SIGINT) {
        printf("Caught interrupt, shutting down...\n");
        close(sockfd);
        exit(EXIT_SUCCESS);
    }
}

int main() {
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);
    // ...
}
  • sigaction():设置信号处理函数。使用sigaction()函数注册信号处理函数,以便在接收到特定信号时执行特定操作。这种方法比使用signal()函数更安全,因为它提供了更多的控制选项。
  • SIGINT:中断信号。当用户按下Ctrl+C时,程序会接收到SIGINT信号,此时可以调用注册的信号处理函数来执行清理操作,比如关闭套接字。这有助于避免资源泄露和程序异常终止的情况。

6. 结论

本文介绍了使用C语言进行网络编程的基础知识,并探讨了一些高级主题。网络编程是一个复杂但非常有趣的领域,掌握这些技能对于任何从事系统开发的人来说都是极其宝贵的。通过上述例子,你已经了解了如何创建简单的TCP客户端和服务器程序,并且能够处理一些复杂的场景,如多线程和非阻塞I/O。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

渗透测试 之 AD域渗透 【Kerberoasting】 攻击技术讲解 对应得工具详细介绍哟~ 以及相关示例 按照步骤做你也会哟

说明 Kerberoasting 攻击发生在Kerberos协议的TGS_REP阶段&#xff0c;KDC的TGS服务返回一个由服务Hash加密的ST给客户端。由于该ST是用服务Hash进行加密的&#xff0c;因此客户端在拿到该ST后可以用于本地离线爆破。 攻击的过程 攻击者提供一个正常的域用户密码对域进行身份…

拆解学习【STC宏晶MCU-CM1020电池保护】(一)

MIJIA米家USB-C充气宝1S: 米家这款充气宝内置2串18650锂电池为电机和控制板供电。控制板采用STC宏晶MCU进行气压测量和电机控制以及压力显示&#xff0c;内部电池保护板采用创芯微CM1020进行电池保护&#xff0c;并采用捷捷微MOS管进行开关控制。 LED数码管采用贴片LED二极管…

NVM 切换Node.js版本工具

大家好我是苏麟&#xff0c;今天聊聊NVM切换版本工具。 切换 node 版本工具 &#xff1a; GitHub - nvm-sh/nvm: Node Version Manager - POSIX-compliant bash script to manage multiple active node.js versions 查看node版本 node -v 查看 nvm 版本 nvm -v 查看可安装的Nod…

JavaScript进阶笔记--深入对象-内置构造函数及案例

深入对象 创建对象三种方式 利用对象字面量new Object&#xff08;{…}&#xff09;利用构造函数 // 1. 字面量创建对象const obj1 {name: pig,age: 18};console.log(obj1); // {name: "pig", age: 18}// 2. 构造函数创建对象function Pig(name, age) {this.name…

RVIZ2可视化移动机器人模型

RVIZ2可视化移动机器人模型 上一节讲完joint和link&#xff0c;我们来把我们上面定义的简单的URDF(包含身体和雷达)用RVIZ2显示出来&#xff0c;直观的感受下&#xff0c;我们的机器人模型。 URDF可视化的步骤如下&#xff1a; 1建立机器人描述功能包 2建立urdf文件夹编写…

子组件向父组件传值$emit

点击子组件的按钮&#xff0c;将子组件的值传递给父组件&#xff0c;并进行提示。 子组件 <template><div><button click"emitIndex">clickme</button></div> </template> <script> export default {methods: {emitInde…

计算机毕业设计Django+Vue.js豆瓣图书推荐系统 图书评论情感分析 豆瓣图书可视化大屏 豆瓣图书爬虫 数据分析 图书大数据 大数据毕业设计 机器学习

《DjangoVue.js豆瓣图书推荐系统》开题报告 一、研究背景与意义 1. 研究背景 随着数字化时代的来临&#xff0c;图书资源日益丰富&#xff0c;用户面临着信息过载的问题。如何在海量图书中快速找到符合个人兴趣和需求的书籍成为了亟待解决的问题。传统的图书检索方式往往基于…

【含开题报告+文档+PPT+源码】基于SpringBoot的景区酒店点评系统的设计与实现

开题报告 旅游业的快速发展使得越来越多的人选择旅游作为休闲和放松的方式。景区酒店作为旅游的重要组成部分&#xff0c;承担着提供住宿和服务的重要角色。然而&#xff0c;对于游客来说&#xff0c;在选择合适的景区酒店时往往存在信息不对称的问题&#xff0c;缺乏可靠的点…

Windows 下安装 jdk8

一、简介 JDK&#xff1a;Java SE Development Kit&#xff08;Java 开发工具&#xff09;。JRE&#xff1a;Java Runtime Environment &#xff08;Java 运行环境&#xff09;。 如果想进行 Java 编程&#xff08;开发人员&#xff09;&#xff0c;需要安装 JDK&#xff1b;如…

【含开题报告+文档+PPT+源码】基于过滤协同算法的城市旅游网站的设计与实现

开题报告 几年的疫情对生活的各个领域都产生了巨大的影响&#xff0c;疫情之后&#xff0c;随着国内经济的加速复苏&#xff0c;旅游业也迅速回暖。2023 年我国旅游需求迅速增多&#xff0c;一季度旅游人次为12.16 亿人次&#xff0c;较 2022 年同期增长了 46.5%。在当今数字化…

79 NAT-NAT444端口块静态映射

NAT444&#xff08;Network Address Translation 444&#xff09;是一种网络地址转换技术&#xff0c;用于将私有IP地址转换为公有IP地址&#xff0c;实现私有网络与公有网络之间的通信。 端口块静态映射是NAT444中的一种映射方式&#xff0c;它将一组端口范围映射到一个公有I…

GO 语言协程知识点学习笔记

GO 语言协程知识点学习笔记 是个人从互联网上学习整理的笔记。因个人技艺不精&#xff0c;如有纰漏&#xff0c;还请斧正。 协程&#xff1f; 协程并不是 GO 语言特有的机制&#xff0c;像 Lua、Ruby、Python、Kotlin、C/C 也都有协程的支持。区别在于有些是从语言层面支持&a…

【2024最新】基于springboot+vue的xxxx平台lw+ppt

作者&#xff1a;计算机搬砖家 开发技术&#xff1a;SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;Java精选实战项…

linux源码安装slurm以及mung和openssl

一、源码安装munge 1、编译安装munge &#xff08;1&#xff09;下载munge地址&#xff1a;https://github.com/dun/munge/releases &#xff08;2&#xff09;解压编译安装&#xff1a; 1 2 3 4 5 6 7 8 创建/data目录 复制文件munge-0.5.15.tar.xz 到/data目录下 tar -Jx…

模型 知识诅咒

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。知者难悟无知惑。 1 知识诅咒案例 1.1 会议室的误解 李经理是一家科技公司的产品经理&#xff0c;他负责领导一个新产品的开发项目。项目团队由不同背景和经验的成员组成&#xff0c;包括新入职的员…

php 生成随机数

记录:随机数抽奖 要求:每次生成3个 1 - 10 之间可重复(或不可重复)的随机数,10次为一轮,每轮要求数字5出现6次、数字4出现3次、…。 提炼需求: 1,可设置最小数、最大数、每次抽奖生成随机数的个数、是否允许重复 2,可设置每轮指定数字的出现次数 3,可设置每轮的抽奖…

(32)噪声信号的时域分析:均值、方差、与功率

文章目录 前言一、生成噪声信号并画图二、计算信号的均值、方差、与功率三、结果分析 前言 本文对叠加了高斯白噪声的一段整周期余弦信号进行时域分析&#xff0c;使用MATLAB进行信号生成&#xff0c;并计算其均值、方差、与功率。最后给出对计算结果的分析&#xff0c;阐明均…

组装首页:其他组件html、css移入JS中再引入首页

组装首页 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>组装首页</title><style>* …

Java-Exception与RuntimeException

&#x1f496;简介 &#x1f4d6;Exception Exception 类是所有非致命性异常的基类。这些异常通常是由于编程逻辑问题或外部因素&#xff08;如文件不存在、网络连接失败等&#xff09;导致的&#xff0c;可以通过适当的编程手段来恢复或处理。Exception 可以进一步分为两大类…

分享一些常用的数据库性能监测工具

以下是一些常用的数据库性能监测工具&#xff1a; 一、MySQL MySQL Enterprise Monitor&#xff1a; 由 MySQL 官方推出&#xff0c;提供全面的数据库性能监控、诊断和优化功能。可以监控数据库的各种指标&#xff0c;如查询性能、连接数、缓存命中率等&#xff0c;并提供警报…