在socket編程中,經常使用recv函數阻塞等待接收數據。
如果對方GG了(接收到你的數據之後並沒有返回,你這裏會一直等待下去),顯然我們是不希望出現這種情況的。
一般情況下,考慮到對方的數據處理時間,我們可以設置一個超時時間,比如10s,10s之後如果對方還沒返回消息,我們就應該做相應的處理。
核心代碼:
struct timeval tv_out;
tv_out.tv_sec = 5;
tv_out.tv_usec = 0;
setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out));
下方是一個demo,其中使用了ulog組件可以方便的查看日誌時間,我們把超時時間設置的5s,可以看到server監聽後,socket號爲1的客戶端連接上了,我們設置5s超時時間,開始接收:
(1)一直沒有數據發過來,5s時間之後,我們recv超時返回-1,接下來我們進行相應操作,主動斷開這個連接;
(2)如果有數據發過來,recv返回數據長度,進行正確數據處理後繼續recv,此時又重新開始5s倒計時;
(3)如果等待過程中客戶端主動關閉socket連接,那麼recv返回0,進行退出操作。
/*
* Copyright (c) 2006-2019, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2020-02-20 ShineRoyal the first version
*/
#include <sys/socket.h>
#include <netdev.h>
#include <netdb.h>
#include <string.h>
#include <stdlib.h>
#define DBG_TAG "tcpserver"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
static const char *send_data = "hello RT-Thread\n";
struct client_info
{
int socketnum; //socket 號
struct sockaddr_in addr; //socket客戶端的ip和port信息
int sockaddrlen; //socketaddr的長度信息
};
void client_thread_entry(void *param)
{
struct client_info* client = param;
LOG_D("[%d]%s:%d is connect...", client->socketnum, inet_ntoa(client->addr.sin_addr),
ntohs(client->addr.sin_port));
send(client->socketnum, (const void* )send_data, strlen(send_data), 0);
struct timeval tv_out;
tv_out.tv_sec = 5;
tv_out.tv_usec = 0;
setsockopt(client->socketnum, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out));
LOG_D("[%d]set timeout %d.%03ds",client->socketnum, tv_out.tv_sec, tv_out.tv_usec);
while (1)
{
char str[100];
rt_memset(str, 0, sizeof(str));
int bytes = recv(client->socketnum, str, sizeof(str), 0);
LOG_D("bytes:%d", bytes);
if (bytes == 0)
goto __exit;
else if (bytes == -1)
goto __error;
LOG_D("[%d]%s:%d=>%s...", client->socketnum, inet_ntoa(client->addr.sin_addr),
ntohs(client->addr.sin_port), str);
send((int )client->socketnum, (const void * )str, (size_t )strlen(str), 0);
}
__exit: LOG_D("[%d]%s:%d is disconnect...", client->socketnum, inet_ntoa(client->addr.sin_addr),
ntohs(client->addr.sin_port));
rt_free(client);
closesocket(client->socketnum);
return;
__error: LOG_D("[%d]%s:%d is error...", client->socketnum, inet_ntoa(client->addr.sin_addr),
ntohs(client->addr.sin_port));
rt_free(client);
closesocket(client->socketnum);
return;
}
void tcpserver(int argc, char **argv)
{
rt_thread_t tid = RT_NULL;
int sock_listen, sock_connect, port;
struct hostent *host;
struct sockaddr_in listen_addr;
struct sockaddr_in connect_addr;
const char *url;
url = "192.168.1.42"; //localhost ip
port = 5000;
/* 通過函數入口參數 url 獲得 host 地址(如果是域名,會做域名解析) */
host = (struct hostent *) gethostbyname(url);
if ((sock_listen = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
LOG_D("Socket error");
return;
}
struct netdev *netdev = RT_NULL;
netdev = netdev_get_by_family(AF_INET);
if (netdev == RT_NULL)
{
LOG_D("get network interface device by AF_INET failed.");
}
LOG_D("localip:%s", inet_ntoa(netdev->ip_addr));
/* 初始化預連接的服務端地址 */
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons(port);
listen_addr.sin_addr = *((struct in_addr *) host->h_addr);
listen_addr.sin_addr.s_addr = netdev->ip_addr.addr;
rt_memset(&(listen_addr.sin_zero), 0, sizeof(listen_addr.sin_zero));
if (bind(sock_listen, (struct sockaddr * )&listen_addr, sizeof(struct sockaddr)) < 0)
{
LOG_D("Bind fail!");
goto __exit;
}
listen(sock_listen, 3);
LOG_D("begin listing...");
while (1)
{
int sin_size = sizeof(struct sockaddr_in);
sock_connect = accept(sock_listen, (struct sockaddr* )&connect_addr, (socklen_t* )&sin_size);
if (sock_connect == -1)
{
LOG_D("no socket,waitting others socket disconnect.");
continue;
}
char tid_name[10] = "cli";
char tid_num[10];
itoa(sock_connect, tid_num, 10);
strcat(tid_name, tid_num);
struct client_info *client;
client = rt_malloc(sizeof(struct client_info));
client->socketnum = sock_connect;
rt_memcpy(&client->addr, &connect_addr, sizeof(struct sockaddr_in));
client->sockaddrlen = sin_size;
tid = rt_thread_create(tid_name, client_thread_entry, (void*) client, 4096, 25, 10);
if (tid == RT_NULL)
{
LOG_D("no memery for thread %s startup failed!", tid_name);
rt_free(client);
continue;
}
rt_thread_startup(tid);
}
__exit: LOG_D("close listener...");
/* 關閉這個 socket */
closesocket(sock_listen);
return;
}
MSH_CMD_EXPORT(tcpserver, tcpserver);