Linux C编程:端口监听实现方法 (linux c 端口监听)
在网络通信中,端口是用来区分不同应用程序的标识符。每个应用程序都需要侦听一个端口来接收来自网络的数据传输。对于Linux C 编程而言,实现端口监听是非常重要的一项功能。本文将介绍Linux C编程中常见的端口监听实现方法。
1. 使用socket系统调用
socket(套接字)是Linux系统中用于网络通信的重要API之一。通过socket,应用程序可以创建一个网络套接字,并通过这个套接字与网络进行通信。
在Linux C编程中,我们可以使用socket系统调用来创建一个监听套接字,从而实现端口的监听。下面是具体的步骤:
① 创建监听套接字
通过socket()函数创建一个监听套接字。这个函数的之一个参数指定协议族(比如,IPv4或IPv6),第二个参数指定套接字的类型(比如,TCP或UDP),第三个参数指定协议类型(通常为0,表示使用默认协议)。
int listening_socket = socket(AF_INET, SOCK_STREAM, 0);
② 绑定套接字到端口
使用bind()函数将监听套接字绑定到指定端口。这个函数的之一个参数是监听套接字,第二个参数是一个sockaddr结构体指针,用来描述端口和IP地址等信息。需要注意的是,sockaddr结构体的成员必须按照网络字节序进行填充。下面是一个示例:
struct sockaddr_in server_address;
memset(&server_address, 0, sizeof(server_address)); // 清空结构体
server_address.sin_family = AF_INET;
server_address.sin_port = htons(port_number); // 将端口号转换成网络字节序
server_address.sin_addr.s_addr = htonl(INADDR_ANY); // 等价于0.0.0.0,表示绑定到所有网卡上
int ret = bind(listening_socket, (struct sockaddr*)&server_address, sizeof(server_address));
③ 开始监听
使用listen()函数将监听套接字设置成被动监听状态,等待客户端发起连接请求。这个函数的之一个参数是监听套接字,第二个参数是等待连接请求的队列长度(通常为5)。
int ret = listen(listening_socket, 5);
④ 处理客户端连接
使用accept()函数接收客户端连接请求,并返回一个新的套接字,用于与客户端进行通信。这个函数的之一个参数是监听套接字,第二个参数是一个指向sockaddr结构体的指针,用于存储客户端的IP地址和端口号。需要注意的是,accept()函数是一个阻塞函数,如果没有客户端连接请求,程序将一直阻塞在这里。
struct sockaddr_in client_address;
socklen_t client_address_len = sizeof(client_address);
int client_socket = accept(listening_socket, (struct sockaddr*)&client_address, &client_address_len);
2. 使用select系统调用
在多并发的网络通信中,使用socket系统调用会带来一个重要的问题:阻塞。如果只有一个客户端连接请求,我们的程序仍然会阻塞在accept()函数处,无法响应其他客户端的请求。为了解决这个问题,可以使用select系统调用。
select()函数可以监听多个文件描述符,当其中有文件描述符就绪(比如可以读写)时,它会返回,并且可以通过FD_ISSET()宏判断哪些文件描述符已经就绪。下面是使用select()函数实现端口监听的基本步骤:
① 创建监听套接字
同样地,通过socket()函数创建一个监听套接字。
② 绑定套接字到端口
同样地,使用bind()函数将监听套接字绑定到指定端口。
③ 开始监听
同样地,使用listen()函数将监听套接字设置成被动监听状态,等待客户端发起连接请求。但是这里我们不会阻塞在accept()函数处。
int max_fd = listening_socket;
fd_set read_set;
fd_set ready_set;
FD_ZERO(&read_set);
FD_SET(listening_socket, &read_set);
while (1) {
ready_set = read_set;
int ret = select(max_fd+1, &ready_set, NULL, NULL, NULL);
if (ret
perror(“select”);
exit(-1);
}
if (FD_ISSET(listening_socket, &ready_set)) {
// 有连接请求,处理连接
} else {
// 有其他数据到达,处理数据
}
}
在循环中,我们先使用FD_ZERO()和FD_SET()宏将监听套接字添加到read_set中,然后不断监听中的文件描述符是否就绪。如果有文件描述符就绪,则使用FD_ISSET()宏判断具体是哪一个文件描述符就绪,然后进行相应的处理。需要注意的是,我们需要保证每次循环都重新赋值ready_set,因为这个会被select()函数修改。
3. 使用epoll系统调用
select系统调用虽然可以避免单线程、单进程网络编程中的阻塞问题,但是在高并况下性能和扩展性较差。为了解决这个问题,Linux内核引入了epoll系统调用。
epoll()函数可以同时监听多个文件描述符,并且只会将就绪的文件描述符加入到epoll实例中。这样,我们可以轻松地实现高并发网络编程。下面是使用epoll()函数实现端口监听的基本步骤:
① 创建epoll实例
使用epoll_create()函数创建一个epoll实例,返回一个文件描述符。这个函数的参数可以用来指定epoll实例中更大可以监听的文件描述符数量。
int epoll_fd = epoll_create(1024);
② 将监听套接字添加到epoll实例中
使用epoll_ctl()函数将监听套接字添加到epoll实例中,用于接收客户端连接请求。这个函数的之一个参数是epoll实例的文件描述符,第二个参数是指令类型(比如添加、删除、修改等),第三个参数是要监听的文件描述符,第四个参数是一个epoll_event结构体,用于指定事件类型和处理方式。
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.events = EPOLLIN | EPOLLET; // 监听可读事件,边缘触发模式
event.data.fd = listening_socket; // 监听套接字的文件描述符
int ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listening_socket, &event);
③ 循环监听套接字文件描述符
使用epoll_wt()函数循环监听epoll实例中的文件描述符,将就绪的文件描述符交给工作线程进行处理。这个函数的参数可以用来指定最多等待多长时间、最多等待多少文件描述符等。
struct epoll_event events[1024];
while (1) {
int n_ready = epoll_wt(epoll_fd, events, 1024, -1);
if (n_ready
perror(“epoll_wt”);
exit(-1);
}
for (int i = 0; i
int fd = events[i].data.fd;
if (fd == listening_socket) {
// 监听套接字就绪,处理连接请求
} else {
// 其他文件描述符就绪,处理数据
}
}
}
需要注意的是,epoll实例提供了两种触发模式:边缘触发模式和水平触发模式。边缘触发模式在文件描述符从未就绪变为就绪的瞬间触发,处理方式更为灵活。而水平触发模式则在文件描述符已经就绪的情况下持续触发,适用于稳定传输数据的场景。
端口监听是Linux C编程中非常重要的一项功能。本文介绍了三种常见的端口监听实现方法,分别是使用socket系统调用、select系统调用和epoll系统调用。读者可以根据自己的需求选择合适的方法进行开发。需要注意的是,网络编程中容易出现阻塞和死锁等问题,需要谨慎调试和处理,保证应用程序的稳定性和可靠性。