如果你运行过(一)中的范例,你会发现,客户端与服务端未建立连接前会在accpet与connect之间等待,建立后进行读写时,不论是客户端还是服务端都会在read()处等待另一方发送。本节让我们来了解以下他们的阻塞等待原理。
阻塞机制
connnect()
客户端通过connect()主动向服务端建立连接,并等待服务端的反馈,实现三次握手。范例(一)中,我们的服务端都是稳定运行的,一旦客户端发起连接,就会快速响应,connect也随之会快速返回结果给客户。但如果listen()中监听队列已满,服务器迟迟不反馈给客户端,那connet()又会如何让处理呢?我们说过,connect()使一个发起三次握手阻塞函数,调用它会每隔一段时间发起一个SYN包,直至服务器反馈,若超过一定时间仍未收到服务器反馈则会显示响应超时。
accpet()
上节我们说过,connect与listen交互建立连接后,产生套接字会放入一个队列中,供accept取用。accept作用就是查看该队列是否有新生成的套接字,若有则立即返回,无则阻塞等待。
read()
read()的阻塞机制在于调用它时会询问内核缓存是否有数据可读入,若无则会等待内核缓存准备好数据直至递送给read()
非阻塞实现
我们可以看出,这些阻塞函数会一直等待结果,在阻塞期间程序会不能做其他的事,这样会使我们的时间利用率降低,因此我们可以将他们设置未非阻塞,在未等到结果时可以让程序做其他的事。
实现原理
轮子阻塞还是非阻塞的属性其实与函数本身无关,而是取决于放入的套接字,默认情况下,socket是阻塞式的,因此只需要改变socket本身的属性,就可以实现轮子的非阻塞调用了。
设置为非阻塞
1.创建时添入SOCK_NONBLOCK设置
int socket(int domain, int type, int protocol);
int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
2.使用fcntl函数设置
fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
3.使用ioctl函数设置
ioctl(sockfd, FIONBIO, 1); //1:非阻塞 0:阻塞
当我们将套接字设置为非阻塞后,accept与read会进行查询,若无结果或出错都会迅速返回。而connect较为特殊,如果我们调用非阻塞connect后,它会发送一个SYN包而迅速返回,那么之后的三次握手是否建立成功,就需要我们使用poll函数或者select函数检测套接字的状态是否可写,监测超时时间可以由我们自己决定。
服务端
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
unsigned short server_Port = 1234; // 服务器端口
const char *IP = "192.168.80.132"; // 服务器IP
/**字母转大写*/
void upper(char *buf)
{
char *pointer = buf;
while (*pointer != '\n')
{
if (*pointer >= 'a' && *pointer <= 'z')
*pointer -= 32;
pointer++;
}
return;
}
int main()
{
// 创建套接字
int server_Socket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); // 设置为非阻塞
struct sockaddr_in server_Addr;
server_Addr.sin_family = AF_INET;
server_Addr.sin_port = htons(server_Port);
int flag = inet_aton(IP, &(server_Addr.sin_addr));
if (!flag)
{
perror("IP transfer failed!\n");
}
// 绑定服务器IP地址端口号
bind(server_Socket, (struct sockaddr *)&server_Addr, sizeof(server_Addr));
listen(server_Socket, 0);
while (1)
{
struct sockaddr_in client_Addr;
int addrlen = sizeof(client_Addr);
int conn_Socket = accept(server_Socket, (struct sockaddr *)&client_Addr, &addrlen);
if (conn_Socket == -1)
{
continue;
} // 若未有连接就位,迅速返回错误结果
// 提取客户端信息
char *client_Ip = inet_ntoa(client_Addr.sin_addr);
unsigned short client_Port = ntohs(client_Addr.sin_port);
printf("客户端: %s:%hu已连接!\n", client_Ip, client_Port);
ioctl(conn_Socket, FIONBIO, 1);
while (1)
{
char buf[100];
memset(buf, '\0', 100);
int count = read(conn_Socket, buf, 99);
if (count == -1) // 若未有数据则返回报错
{
continue;
}
else if (count == 0)
{
printf("客户端: %s:%hu断开连接!\n", client_Ip, client_Port);
close(conn_Socket);
break;
}
else
{
printf("客户端: %s:%hu消息:%s", client_Ip, client_Port, buf);
upper(buf);
write(conn_Socket, buf, 99);
}
}
}
return 0;
}
客户端
include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
#include <errno.h>
#include <fcntl.h>
unsigned short server_Port = 1234; // 服务器端口
const char *IP = "192.168.80.132"; // 服务器IP
int main()
{
// 创建套接字
int client_Socket = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
struct sockaddr_in server_Addr;
server_Addr.sin_family = AF_INET;
server_Addr.sin_port = htons(server_Port);
int flag = inet_aton(IP, &(server_Addr.sin_addr));
if (!flag)
{
perror("IP transfer failed!\n");
}
flag = connect(client_Socket, (struct sockaddr *)&server_Addr, sizeof(server_Addr));//非阻塞连接
if (flag = -1)
{
struct pollfd fds[1];
fds[0].fd = client_Socket;
fds[0].events = POLLOUT;
int result = poll(fds, 1, 3000); // 设置检测套接字的时间
if (result == 0)
{
printf("Poll timeout\n");//若仍变为可写则超时
exit(0);
}
else if (result > 0)
{
if (fds[0].revents == POLLOUT)
printf("Poll successfully\n");//检测套接字状态变化是否为可写
}
else
{
printf("Poll error\n");//检测出错
}
}
while (1)
{
// 终端输入
printf("请输入消息:");
char buf[100];
fgets(buf, 99, stdin);
// input end quit!
if (strncmp(buf, "end", 3) == 0)
{
printf("用户退出!\n");
close(client_Socket);
break;
}
write(client_Socket, buf, 99);
int count = -1;
while (count == -1)
{
count = read(client_Socket, buf, 99);
}
if (count == 0)
{
printf("与服务器断开连接!\n");
close(client_Socket);
break;
}
else
{
printf("服务端返回消息:%s", buf);
}
}
return 0;
}
浙公网安备 33010602011771号