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

穿越時空
发布: 2025-07-19 08:20:02
原创
999人浏览过

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 <fcntl.h>
#include <unistd.h>

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

    云雀语言模型
    云雀语言模型

    云雀是一款由字节跳动研发的语言模型,通过便捷的自然语言交互,能够高效的完成互动对话

    云雀语言模型54
    查看详情 云雀语言模型
  2. 多进程: 类似于多线程,但使用进程来处理客户端连接。进程之间的隔离性更好,但创建和销毁进程的开销更大。

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

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

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

#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语言socket通信基础与示例的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

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