0

0

C语言中如何进行网络编程 C语言socket通信基础与示例

穿越時空

穿越時空

发布时间:2025-07-19 08:20:02

|

1035人浏览过

|

来源于php中文网

原创

c语言网络编程核心在于使用socket api进行通信。创建tcp客户端需按以下步骤:1. 使用socket()函数创建socket;2. 设置服务器地址结构体;3. 通过connect()连接服务器;4. 使用send()和recv()收发数据;5. 最后用close()关闭socket。socket默认为阻塞模式,可通过fcntl()设置为非阻塞模式,并配合select()或poll()实现i/o多路复用。处理多客户端并发连接的方法包括:1. 多线程/多进程处理每个连接;2. 使用select/poll/epoll实现单线程多连接处理;3. 使用线程池/连接池减少资源开销。常见安全问题包括缓冲区溢出、格式化字符串漏洞、拒绝服务攻击、中间人攻击、sql注入、跨站脚本攻击和命令注入,应采取相应防护措施以保障程序安全。

C语言中如何进行网络编程 C语言socket通信基础与示例

C语言进行网络编程,核心在于使用Socket API,它提供了一系列函数,允许程序创建网络连接、发送和接收数据。理解Socket通信的基础概念,并掌握相关API的使用,是C语言网络编程的关键。

C语言中如何进行网络编程 C语言socket通信基础与示例

Socket通信基础与示例

C语言中如何进行网络编程 C语言socket通信基础与示例

如何在C语言中创建一个简单的TCP客户端?

创建一个TCP客户端,你需要以下步骤:

立即学习C语言免费学习笔记(深入)”;

C语言中如何进行网络编程 C语言socket通信基础与示例
  1. 创建Socket: 使用socket()函数创建一个Socket,指定协议族(AF_INET for IPv4)和Socket类型(SOCK_STREAM for TCP)。

    int client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket == -1) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }
  2. 设置服务器地址: 创建一个sockaddr_in结构体,指定服务器的IP地址和端口号。

    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(PORT); // PORT是服务器监听的端口
    if (inet_pton(AF_INET, "127.0.0.1", &server_address.sin_addr) <= 0) { // 服务器IP地址
        perror("Invalid address/ Address not supported");
        exit(EXIT_FAILURE);
    }
  3. 连接服务器: 使用connect()函数连接到服务器。

    if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) < 0) {
        perror("Connection failed");
        exit(EXIT_FAILURE);
    }
  4. 发送和接收数据: 使用send()recv()函数与服务器进行数据交互。

    char *message = "Hello from client!";
    send(client_socket, message, strlen(message), 0);
    
    char buffer[1024] = {0};
    recv(client_socket, buffer, sizeof(buffer), 0);
    printf("Received from server: %s\n", buffer);
  5. 关闭Socket: 使用close()函数关闭Socket。

    close(client_socket);

C语言中如何处理Socket的阻塞与非阻塞模式?

Socket默认是阻塞模式,这意味着如果recv()函数没有收到数据,或者send()函数无法立即发送数据,程序会一直等待。 非阻塞模式则允许程序在没有数据可读或无法立即发送数据时,立即返回,避免程序卡死。

要将Socket设置为非阻塞模式,可以使用fcntl()函数:

#include 
#include 

int flags = fcntl(client_socket, F_GETFL, 0);
if (flags == -1) {
    perror("fcntl F_GETFL failed");
    exit(EXIT_FAILURE);
}

flags |= O_NONBLOCK;
if (fcntl(client_socket, F_SETFL, flags) == -1) {
    perror("fcntl F_SETFL failed");
    exit(EXIT_FAILURE);
}

在非阻塞模式下,recv()send()函数可能会返回EAGAINEWOULDBLOCK错误,表示操作无法立即完成。你需要使用select()poll()函数来监视Socket的状态,只有当Socket可读或可写时,才进行相应的操作。 select()poll() 都是I/O多路复用技术,允许单个线程监视多个Socket。

如何在C语言中处理多客户端并发连接?

处理多客户端并发连接,通常使用以下几种方法:

  1. 多线程: 每当有一个新的客户端连接,就创建一个新的线程来处理该客户端的请求。这种方法简单直接,但当客户端数量很多时,会消耗大量的系统资源。

    美图AI开放平台
    美图AI开放平台

    美图推出的AI人脸图像处理平台

    下载
  2. 多进程: 类似于多线程,但使用进程来处理客户端连接。进程之间的隔离性更好,但创建和销毁进程的开销更大。

  3. I/O多路复用 (select/poll/epoll): 使用select()poll()epoll()函数来监视多个Socket的状态,当有Socket可读或可写时,才进行相应的操作。这种方法允许单个线程处理多个客户端连接,效率更高。epoll是Linux特有的,性能通常优于selectpoll,特别是在高并发场景下。

一个使用select()的简单示例:

#include 
#include 
#include 
#include 
#include 
#include 

#define PORT 8080
#define MAX_CLIENTS 30

int main() {
    int server_fd, new_socket, client_sockets[MAX_CLIENTS], max_clients = MAX_CLIENTS;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    fd_set readfds;

    // 初始化client_sockets数组
    for (int i = 0; i < max_clients; i++) {
        client_sockets[i] = 0;
    }

    // 创建服务器Socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定Socket到指定端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听连接
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Listening on port %d\n", PORT);

    while (1) {
        // 清空Socket集合
        FD_ZERO(&readfds);

        // 添加服务器Socket到集合
        FD_SET(server_fd, &readfds);
        int max_sd = server_fd;

        // 添加客户端Sockets到集合
        for (int i = 0; i < max_clients; i++) {
            int sd = client_sockets[i];
            if (sd > 0) {
                FD_SET(sd, &readfds);
            }
            if (sd > max_sd) {
                max_sd = sd;
            }
        }

        // 使用select等待Socket活动
        int activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);

        if ((activity < 0) && (errno != EINTR)) {
            perror("select error");
        }

        // 如果服务器Socket有活动,说明有新的连接
        if (FD_ISSET(server_fd, &readfds)) {
            if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
                perror("accept");
                exit(EXIT_FAILURE);
            }

            printf("New connection , socket fd is %d , ip is : %s , port : %d\n", new_socket, inet_ntoa(address.sin_addr), ntohs(address.sin_port));

            // 将新的Socket添加到client_sockets数组
            for (int i = 0; i < max_clients; i++) {
                if (client_sockets[i] == 0) {
                    client_sockets[i] = new_socket;
                    printf("Adding to list of sockets as %d\n", i);
                    break;
                }
            }
        }

        // 处理客户端Sockets
        for (int i = 0; i < max_clients; i++) {
            int sd = client_sockets[i];

            if (FD_ISSET(sd, &readfds)) {
                char buffer[1024] = {0};
                int valread;
                if ((valread = read(sd, buffer, 1024)) == 0) {
                    // 客户端断开连接
                    getpeername(sd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
                    printf("Host disconnected , ip %s , port %d \n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));

                    close(sd);
                    client_sockets[i] = 0;
                } else {
                    // 处理客户端发送的数据
                    buffer[valread] = '\0';
                    printf("Received from client %d: %s\n", sd, buffer);
                    send(sd, buffer, strlen(buffer), 0); // Echo back the message
                }
            }
        }
    }

    return 0;
}
  1. 线程池/连接池: 预先创建一组线程或连接,当有新的客户端连接时,从池中获取一个线程或连接来处理该客户端的请求。这种方法可以减少线程或连接的创建和销毁开销,提高性能。

选择哪种方法取决于具体的应用场景和性能需求。 在高并发、低延迟的场景下,epoll通常是最佳选择。

C语言网络编程中常见的安全问题有哪些?

C语言网络编程中常见的安全问题包括:

  1. 缓冲区溢出: 当接收到的数据超过缓冲区的大小时,会导致缓冲区溢出,覆盖相邻的内存区域,可能导致程序崩溃或被恶意利用。 使用strncpy()snprintf()等函数可以避免缓冲区溢出。

  2. 格式化字符串漏洞: 当使用printf()等函数时,如果格式化字符串由用户提供,可能会导致格式化字符串漏洞,允许攻击者读取或写入任意内存地址。 避免使用用户提供的字符串作为格式化字符串。

  3. 拒绝服务攻击 (DoS/DDoS): 攻击者通过发送大量的请求,耗尽服务器的资源,导致服务器无法正常提供服务。 可以使用防火墙、负载均衡等技术来防御DoS/DDoS攻击。

  4. 中间人攻击 (MITM): 攻击者拦截客户端和服务器之间的通信,篡改数据,或者冒充客户端或服务器。 可以使用SSL/TLS等加密协议来保护通信安全。

  5. SQL注入: 如果应用程序使用SQL数据库,并且用户提供的输入没有经过正确的过滤,可能会导致SQL注入攻击,允许攻击者执行任意SQL命令。 使用参数化查询或预编译语句可以避免SQL注入。

  6. 跨站脚本攻击 (XSS): 如果Web应用程序没有对用户提供的输入进行正确的转义,可能会导致XSS攻击,允许攻击者在用户的浏览器中执行恶意脚本。 对用户提供的输入进行HTML转义可以避免XSS攻击。

  7. 命令注入: 如果应用程序允许用户执行系统命令,并且用户提供的输入没有经过正确的过滤,可能会导致命令注入攻击,允许攻击者执行任意系统命令。 避免直接执行用户提供的命令,如果必须执行,则需要对输入进行严格的验证和过滤。

在进行C语言网络编程时,需要充分考虑这些安全问题,并采取相应的措施来保护应用程序的安全。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

392

2023.06.20

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

617

2023.07.25

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

353

2023.08.02

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

257

2023.08.09

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

597

2023.09.05

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

524

2023.09.20

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

640

2023.09.20

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

600

2023.09.22

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

72

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 4.7万人学习

Rust 教程
Rust 教程

共28课时 | 4.5万人学习

Django 教程
Django 教程

共28课时 | 3.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号