要实现一个兼容流式套接字(TCP)和数据包套接字(UDP)的回射服务器,我们可以使用 select 模型或事件选择模型来进行编程。
这里采用select模型
一、基本步骤:
-
创建套接字(
socket()
) -
绑定地址(
bind()
) -
监听连接(
listen()
) -
使用
select()
监视套接字 -
接受连接(
accept()
) -
读取数据(
recv()
) -
发送数据(
send()
) -
关闭套接字(
close()
)
二、具体实现:
(1)创建tcp 和udp两个套接字
int tcp_sock, udp_sock;
struct sockaddr_in tcp_addr, udp_addr;
int addr_len = sizeof(struct sockaddr_in);
tcp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
udp_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
(2)为了提高性能和避免“地址已被使用”的错误,我们通常需要设置一些套接字选项。
int opt = 1;
setsockopt(tcp_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
setsockopt(udp_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
这段代码是用来设置套接字选项的,具体是设置 SO_REUSEADDR
选项,它允许套接字立即重新绑定到一个最近被用来绑定的端口上。这在网络编程中很有用,特别是在开发阶段,因为当一个服务器程序异常退出时,绑定的端口可能会在一定时间内被操作系统保持(处于 TIME_WAIT 状态),如果没有这个选项,新的服务器进程将无法立即绑定到同一个端口。
(3)绑定地址和端口
tcp_addr.sin_family = AF_INET;
tcp_addr.sin_addr.s_addr = INADDR_ANY;
tcp_addr.sin_port = htons(TCP_PORT);
udp_addr.sin_family = AF_INET;
udp_addr.sin_addr.s_addr = INADDR_ANY;
udp_addr.sin_port = htons(UDP_PORT);
bind(tcp_sock, (struct sockaddr *)&tcp_addr, sizeof(tcp_addr));
bind(udp_sock, (struct sockaddr *)&udp_addr, sizeof(udp_addr));
(4)监听TCP
listen(tcp_sock, 5);
(5)使用 select
模型监控套接字
fd_set readfds;
FD_ZERO(&readfds);
FD_SET(tcp_sock, &readfds);
FD_SET(udp_sock, &readfds);
int max_fd = udp_sock > tcp_sock ? udp_sock : tcp_sock;
(6)循环等待处理事件
while (1) {
fd_set fds = readfds;
int activity = select(max_fd + 1, &fds, NULL, NULL, NULL);
if ((activity < 0) && (errno != EINTR)) {
error_handling("select error");
}
// TCP连接请求
if (FD_ISSET(tcp_sock, &fds)) {
int tcp_clnt_sock = accept(tcp_sock, (struct sockaddr *)&tcp_