[英]C socket blocking call
我想知道socket在阻塞和非阻塞操作上的行為。 當套接字阻塞模式改變時,套接字上阻塞的線程會發生什么? 這是場景; thread1(T1)創建一個UDP套接字和
fd = socket(AF_INET , SOCK_DGRAM, 0);
T1等待(休眠)接收
recv(fd, buf , sizeof(buf) , 0);
和thread2(T2)在套接字接收任何數據之前將套接字模式更改為非阻塞
fcntl(fd, F_SETFL, O_NONBLOCK);
T1怎么了? 是否發出信號,因為套接字不再阻塞?
該行為實際上未指定: fcntl
不需要取消阻止任何線程。
Linux只是在文件描述 struct file
設置標志,並且在不解除阻塞任何阻塞線程的情況下返回。
已經在recv
阻止的線程可以安排為僅在以下情況下運行:
FIN
/ RST
,套接字讀取超時,TCP保持活動失敗,文件描述符由另一個線程close
); SA_RESTART
; pthread_cancel
領導。 您嘗試更改另一個線程的文件描述符的標志這一事實表明您的設計需要審核。 理想情況下,線程不得共享任何數據而不是互相攻擊狀態,而是應該使用消息傳遞來相互通信。
你讓我很好奇。 首先,很明顯,因為沒有標准規定套接字應該被喚醒,所以它不會被喚醒,因為實現它會非常麻煩(因為非阻塞標志位於不同的層,而不是socket正在阻塞)。 所以我們可以非常自信地說,套接字在收到數據包之前不會被喚醒。 或者會嗎?
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <err.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>
int sock;
static void
sighand(int s)
{
write(1, "sig\n", 4);
}
static void *
rcv(void *v)
{
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = sighand;
sa.sa_flags = SA_RESTART;
if (sigaction(SIGUSR1, &sa, NULL) == -1)
err(1, "sigaction");
char buf[64];
ssize_t sz = recv(sock, buf, sizeof(buf), 0);
printf("recv %d\n", (int)sz);
return NULL;
}
pthread_t t1, t2;
static void *
beeper(void *v)
{
for (;;) {
nanosleep(&((struct timespec){.tv_sec = 1}), NULL);
if (pthread_kill(t1, SIGUSR1))
errx(1, "pthread_kill");
printf("beep\n");
}
}
int
main(int argc, char **argv)
{
if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) == -1)
err(1, "socket");
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(4711);
sin.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) == -1)
err(1, "bind");
if (pthread_create(&t1, NULL, rcv, NULL))
errx(1, "pthread_create");
if (pthread_create(&t2, NULL, beeper, NULL))
errx(1, "pthread_create");
/* pretend that this is correct synchronization. */
nanosleep(&((struct timespec){.tv_sec = 3}), NULL);
printf("setting non-block\n");
if (fcntl(sock, F_SETFL, O_NONBLOCK) == -1)
err(1, "fcntl");
printf("set\n");
nanosleep(&((struct timespec){.tv_sec = 3}), NULL);
return 0;
}
上面的代碼(抱歉,不能縮短它)。 在recv中阻塞線程,等待一點,然后在文件描述符上設置非阻塞。 正如所料,沒有任何事 但后來我加了一個轉折。 接收線程有一個信號處理程序,每秒喚醒一次SA_RESTART
。 當然,既然我們有SA_RESTART
recv
就不應該醒來。
在OSX上,如果我們認為任何行為正確,這將“行為不端”。 我很確定這在所有BSD上的行為都是一樣的。 在Linux上也一樣。 實際上,通常實現SA_RESTART
的方式我很確定這會在任何地方“行為不端”。
滑稽。 這當然並不意味着上面的代碼對任何事情都有用,但這是一個有趣的好奇心。 要回答你的問題,這是未指明的,但在大多數情況下它不會被喚醒,除非它會。 請不要做這樣奇怪的事情。
我認為POSIX規范對此很清楚:
如果套接字上沒有消息可用,並且套接字的文件描述符上沒有設置
O_NONBLOCK
,則recv()
將阻塞,直到消息到達。 如果套接字上沒有消息可用,並且套接字的文件描述符上設置了O_NONBLOCK
,則recv()
將失敗並將errno設置為[EAGAIN]
或[EWOULDBLOCK]
。
當O_NONBLOCK
尚未設置時調用recv
,它應該阻塞直到消息到達(直到模式改變)。
暢通線程(還不阻止因讀書時,在阻斷模式)的行為就好像它一直是非阻塞模式。
如果套接字上沒有可用的消息,並且套接字是非阻塞的,則返回值-1,並將外部變量errno設置為EAGAIN或EWOULDBLOCK。
在更改為非阻塞之前阻塞的線程(在阻塞模式下讀取時阻塞)。 正如@Maxim共享未喚醒線程的函數代碼所指出的那樣,阻塞的線程只會在寫入完成后被喚醒(數據可用)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.