在网络编程中,使用C语言编写简单的服务器是一个基础且重要的任务,以下是关于如何用C语言编写简单服务器的详细介绍:
1、函数原型:int socket(int domain, int type, int protocol)
。
2、参数说明:
domain
:指定协议族,常用的有AF_INET
(IPv4)和AF_INET6
(IPv6)。
type
:指定套接字类型,常用的有SOCK_STREAM
(流套接字,用于TCP)和SOCK_DGRAM
(数据报套接字,用于UDP)。
protocol
:通常为0,表示使用默认协议,对于TCP套接字,此参数应为0;对于UDP套接字,此参数也应为0。
3、示例代码:
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
这段代码创建了一个IPv4的TCP套接字,如果创建失败,会返回-1,可以通过perror()
函数输出错误信息。
1、结构体定义:需要定义一个sockaddr_in
结构体来存储服务器的地址信息。
2、设置结构体成员:
sin_family
:设置为AF_INET
,表示使用IPv4地址。
sin_port
:使用htons()
函数将端口号转换为网络字节序后赋值给该成员,如果要绑定到8080端口,可以这样写:server_address.sin_port = htons(8080);
。
sin_addr.s_addr
:可以设置为INADDR_ANY
,表示监听所有可用的网络接口上的客户端请求。
3、绑定函数:使用bind()
函数将套接字与指定的IP地址和端口号绑定。
int bind(int sockfd, struct sockaddr addr, socklen_t addrlen)
。
sockfd
是之前创建的套接字描述符。
addr
是指向包含有本机IP地址及端口号的sockaddr_in
结构体的指针。
addrlen
是地址结构体的长度,对于sockaddr_in
结构体,其长度是sizeof(struct sockaddr_in)
。
bind(server_fd, (struct sockaddr)&server_address, sizeof(server_address));
。
1、函数原型:int listen(int sockfd, int backlog)
。
2、参数说明:
sockfd
:已经绑定的套接字描述符。
backlog
:指定在请求队列中允许的最大请求数,即最多允许多少个客户端连接到服务器但尚未处理。
3、示例代码:
listen(server_fd, 5);
这行代码表示开始监听来自客户端的连接请求,最多允许5个客户端同时在请求队列中等待处理,如果监听失败,同样会返回-1,并可以使用perror()
函数输出错误信息。
1、函数原型:int accept(int sockfd, struct sockaddr cliaddr, socklen_t addrlen)
。
2、参数说明:
sockfd
:正在监听的套接字描述符。
cliaddr
:指向一个sockaddr_in
结构体,用于存储客户端的地址信息。
addrlen
:是一个整型变量的地址,用于存储客户端地址结构体的长度。
3、示例代码:
int client_fd = accept(server_fd, (struct sockaddr)&client_address, &client_addr_len);
当有客户端连接时,accept()
函数会返回一个新的套接字描述符client_fd
,用于与该客户端进行通信。client_address
中会存储客户端的地址信息,client_addr_len
中会存储客户端地址结构体的长度,如果没有客户端连接,accept()
函数会阻塞等待,直到有客户端连接为止。
1、接收数据:
函数原型:ssize_t recv(int sockfd, void buf, size_t len, int flags)
。
参数说明:
sockfd
:已连接的套接字描述符。
buf
:指向接收数据的缓冲区。
len
:缓冲区的长度。
flags
:通常设置为0。
示例代码:
char buffer[1024];
recv(client_fd, buffer, sizeof(buffer), 0);
这行代码从客户端接收数据并存储到buffer
中。
2、发送数据:
函数原型:ssize_t send(int sockfd, const void buf, size_t len, int flags)
。
参数说明:
sockfd
:已连接的套接字描述符。
buf
:指向要发送的数据缓冲区。
len
:要发送的数据长度。
flags
:通常设置为0。
示例代码:
send(client_fd, "Hello, Client!", strlen("Hello, Client!"), 0);
这行代码向客户端发送一段字符串"Hello, Client!"。
1、关闭客户端套接字:当与客户端的通信完成后,需要关闭与客户端连接的套接字。
函数原型:int close(int sockfd)
。
示例代码:
close(client_fd);
这行代码关闭了与客户端连接的套接字。
2、关闭服务器套接字:在服务器程序结束前,也需要关闭监听套接字。
示例代码:
close(server_fd);
这行代码关闭了服务器的监听套接字。
以下是一个简单的C语言编写的服务器示例代码,它实现了上述功能:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define PORT 8080 #define BACKLOG 5 #define BUFFER_SIZE 1024 int main() { int server_fd, client_fd; struct sockaddr_in server_address, client_address; socklen_t client_addr_len = sizeof(client_address); char buffer[BUFFER_SIZE]; ssize_t bytes_received; // 创建套接字 server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd == -1) { perror("socket"); exit(EXIT_FAILURE); } // 绑定套接字到端口 memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; server_address.sin_port = htons(PORT); server_address.sin_addr.s_addr = INADDR_ANY; if (bind(server_fd, (struct sockaddr )&server_address, sizeof(server_address)) == -1) { perror("bind"); close(server_fd); exit(EXIT_FAILURE); } // 监听连接请求 if (listen(server_fd, BACKLOG) == -1) { perror("listen"); close(server_fd); exit(EXIT_FAILURE); } printf("Server is listening on port %d... ", PORT); // 接受客户端连接并通信 while (1) { client_fd = accept(server_fd, (struct sockaddr )&client_address, &client_addr_len); if (client_fd == -1) { perror("accept"); continue; } printf("Client connected from %s:%d... ", inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port)); // 接收数据 bytes_received = recv(client_fd, buffer, BUFFER_SIZE 1, 0); if (bytes_received == -1) { perror("recv"); close(client_fd); continue; } else if (bytes_received == 0) { printf("Client disconnected. "); close(client_fd); continue; } else { buffer[bytes_received] = '