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

Socket通信基础与示例

创建一个TCP客户端,你需要以下步骤:
立即学习“C语言免费学习笔记(深入)”;

创建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);
}设置服务器地址: 创建一个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);
}连接服务器: 使用connect()函数连接到服务器。
if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) < 0) {
perror("Connection failed");
exit(EXIT_FAILURE);
}发送和接收数据: 使用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);关闭Socket: 使用close()函数关闭Socket。
close(client_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()函数可能会返回EAGAIN或EWOULDBLOCK错误,表示操作无法立即完成。你需要使用select()或poll()函数来监视Socket的状态,只有当Socket可读或可写时,才进行相应的操作。 select()和poll() 都是I/O多路复用技术,允许单个线程监视多个Socket。
处理多客户端并发连接,通常使用以下几种方法:
多线程: 每当有一个新的客户端连接,就创建一个新的线程来处理该客户端的请求。这种方法简单直接,但当客户端数量很多时,会消耗大量的系统资源。
多进程: 类似于多线程,但使用进程来处理客户端连接。进程之间的隔离性更好,但创建和销毁进程的开销更大。
I/O多路复用 (select/poll/epoll): 使用select()、poll()或epoll()函数来监视多个Socket的状态,当有Socket可读或可写时,才进行相应的操作。这种方法允许单个线程处理多个客户端连接,效率更高。epoll是Linux特有的,性能通常优于select和poll,特别是在高并发场景下。
一个使用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;
}选择哪种方法取决于具体的应用场景和性能需求。 在高并发、低延迟的场景下,epoll通常是最佳选择。
C语言网络编程中常见的安全问题包括:
缓冲区溢出: 当接收到的数据超过缓冲区的大小时,会导致缓冲区溢出,覆盖相邻的内存区域,可能导致程序崩溃或被恶意利用。 使用strncpy()、snprintf()等函数可以避免缓冲区溢出。
格式化字符串漏洞: 当使用printf()等函数时,如果格式化字符串由用户提供,可能会导致格式化字符串漏洞,允许攻击者读取或写入任意内存地址。 避免使用用户提供的字符串作为格式化字符串。
拒绝服务攻击 (DoS/DDoS): 攻击者通过发送大量的请求,耗尽服务器的资源,导致服务器无法正常提供服务。 可以使用防火墙、负载均衡等技术来防御DoS/DDoS攻击。
中间人攻击 (MITM): 攻击者拦截客户端和服务器之间的通信,篡改数据,或者冒充客户端或服务器。 可以使用SSL/TLS等加密协议来保护通信安全。
SQL注入: 如果应用程序使用SQL数据库,并且用户提供的输入没有经过正确的过滤,可能会导致SQL注入攻击,允许攻击者执行任意SQL命令。 使用参数化查询或预编译语句可以避免SQL注入。
跨站脚本攻击 (XSS): 如果Web应用程序没有对用户提供的输入进行正确的转义,可能会导致XSS攻击,允许攻击者在用户的浏览器中执行恶意脚本。 对用户提供的输入进行HTML转义可以避免XSS攻击。
命令注入: 如果应用程序允许用户执行系统命令,并且用户提供的输入没有经过正确的过滤,可能会导致命令注入攻击,允许攻击者执行任意系统命令。 避免直接执行用户提供的命令,如果必须执行,则需要对输入进行严格的验证和过滤。
在进行C语言网络编程时,需要充分考虑这些安全问题,并采取相应的措施来保护应用程序的安全。
以上就是C语言中如何进行网络编程 C语言socket通信基础与示例的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号