Linux下TCP超时问题

quickSort 2015-08-16 05:52:39
使用TCP的超时选项,目的是在socket读写时防止一直阻塞,在指定的超时时间内仍然不成功则返回错误。
流程:
服务端等待连接,然后开启新线程处理这个连接,处理过程为:先读取一个整数,表示后续需要读的数据长度,然后继续读取这么长的数据,成功后返回一个整数,告诉客户端读到了多少数据。
客户端:主动连接,先发送一个整数,表示后序要发送的数据长度,然后发送这么多数据,然后读取服务器的相应数据(也是一个整数),判断相应是否和预期一致。

出现问题:在2台机器上测试,如果拔掉网线,超时时间总是我设定的2倍,偶尔也会出现3倍;

但是第一次出错后,后面如果继续发送数据,则在指定的超时时间出错返回。
(如果不是拔掉网线,而是直接终止程序,一切正常,总能立刻出错返回,和预期一样)
代码如下:
公共代码:
 //#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>

#include <errno.h>
#include <arpa/inet.h>

#ifndef _SOCKET_H
#define _SOCKET_H

const static int TIMEOUT = 5; /* second */

#define unlikely(x) (x)

static int connect_to(char *ip, unsigned int port)
{
int fd, ret, value = 1;
struct sockaddr_in addr;
struct linger linger_opt = {1, 0};
struct timeval timeout_opt = {TIMEOUT, 0};

addr.sin_family = AF_INET;
addr.sin_port = htons(port);

if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
ret = -1;
goto err;
}

fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
ret = -1;
goto err;
}

ret = setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger_opt,
sizeof(linger_opt));
if (ret < 0)
goto err_close;

ret = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value));
if (ret < 0)
goto err_close;

ret = setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeout_opt,
sizeof(timeout_opt));
if (ret < 0)
goto err_close;

ret = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeout_opt,
sizeof(timeout_opt));
if (ret < 0)
goto err_close;

ret = connect(fd, (struct sockaddr *)&addr, sizeof(addr));
if (ret < 0)
goto err_close;

return fd;

err_close:
close(fd);
err:
fprintf(stderr, "Failed errno:%d(%m)\n", errno);
return ret;
}

static int64_t net_read(int fd, void *buf, int64_t count)
{
char *p = buf;
int64_t sum = 0;
while(count > 0) {
int64_t loaded = 0;
while(1) {
loaded = read(fd, p, count);
if (unlikely(loaded < 0) && (errno == EINTR))
continue;
break;
}

if (unlikely(loaded < 0))
return -1;
if (unlikely(loaded == 0))
return sum;

count -= loaded;
p += loaded;
sum += loaded;
}

return sum;
}

static int64_t net_write(int fd, void *buf, int64_t count)
{
char *p = buf;
int64_t sum = 0;
while (count > 0) {
int64_t written = 0;
while (1) {
written = write(fd, p, count);
if (unlikely(written < 0) && (errno == EINTR))
continue;
break;
}

if (unlikely(written < 0))
return -1;
if (unlikely(written == 0))
return -1;

count -= written;
p += written;
sum += written;
}

return sum;
}

#endif



服务端代码如下:

#include "htime.h"
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>

#include "mysocket.h"

int init_server(int port);
void *recv_work(void *data);
void sig_handler(int signo);


int main(int argc, char *argv[])
{
if(argc != 2)
{
fprintf(stderr, "usage: server port\n");
return 0;
}

int fd = init_server(atoi(argv[1]));
if(fd < 0)
{
fprintf(stderr, "server start failed\n");
return 0;
}

int client;

while(1)
{
fprintf(stderr, "%s waiting for client...\n", gettime());
client = accept(fd, NULL, NULL);
if(client == -1)
{
fprintf(stderr, "%s accept failed : %m\n", gettime());
continue;
}

pthread_t pid;
pthread_create(&pid, NULL, recv_work, &client);

}
}


void *recv_work(void *data)
{
int fd = *(int*)data, ret, len=0;
fprintf(stderr, "client fd: %d\n", fd);
char *buf = NULL;

while(1)
{
ret = net_read(fd, &len, sizeof(int));
fprintf(stderr, "get info from client: %d(%d byte)[%m]\n", len, ret);

if(ret == 0) {
fprintf(stderr, "%s read 0, client seems to be dead...\n", gettime());
break;
} else if(ret < 0) {
fprintf(stderr, "ret=%d, errno=%d:%m\n",ret, errno);
if (errno == EINTR) continue;
if (errno == EAGAIN)
fprintf(stderr, "would block or time out\n");
} else {
buf = malloc(len); if(!buf) {fprintf(stderr, "OOM\n"); break;}
ret = net_read(fd, buf, len);
fprintf(stderr, "get data from client ret:%d ", ret);
if ( ret > 20 ) {
char t[20] = {};
memcpy(t, buf, 20);
fprintf(stderr, "(%s)\n", t);
}

ret = net_write(fd, &ret, sizeof(int));
// free(buf);
// buf = NULL;
}
}
fprintf(stderr, "close client fd: %d\n", fd);
pthread_detach(pthread_self());
pthread_exit(NULL);
}

void sig_handler(int signo)
{
fprintf(stderr, "receivec sig: %d\n", signo);
}

int init_server(int port)
{
struct sockaddr_in ser;
int fd, value = 1;
struct linger linger_opt = {1, 0};

ser.sin_family = AF_INET;
ser.sin_port = htons(port);
ser.sin_addr.s_addr = INADDR_ANY;
bzero(&(ser.sin_zero), 8);

fd = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger_opt,
sizeof(linger_opt));
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &value, sizeof(value));

if(bind(fd, (struct sockaddr*)&ser, sizeof(struct sockaddr))== -1)
{
fprintf(stderr, "bind error\n");
return -1;
}
if (listen(fd, 20) == -1){
fprintf(stderr, "listening failed\n");
return -1;
}
signal(SIGPIPE, sig_handler);

return fd;
}




客户端代码如下,:


#include "htime.h"
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>

#include "mysocket.h"

void *recv_work(void *data);
void sig_handler(int signo);


int main(int argc, char *argv[])
{

int p1=8888, p2=8889;
int fd;
if (argc == 2)
fd = connect_to(argv[1], p1);
else
fd = connect_to("10.254.4.23", p1);
if(fd < 0)
{
// fprintf(stderr, "server start failed\n");
return 0;
}
signal(SIGPIPE, sig_handler);
int MAX = 1024*1024*10;
char *buf = malloc(MAX);// 20MB
if (buf == NULL) { fprintf(stderr, "OOM\n"); return 0;}
int ret;

while(1)
{
fprintf(stderr, "press ENTER to continue...");
getchar();
strcpy(buf,gettime());
printf("%s\n", gettime());

ret = net_write(fd, &MAX, sizeof(int));
if (ret != sizeof(int)) {
fprintf(stderr, "%s write failed pos1: ret=%d\n", gettime(), ret);
// continue;
}

ret = net_write(fd, buf, MAX);
if (ret != MAX) {
fprintf(stderr, "%s write failed pos2: ret=%d\n", gettime(), ret);
// continue;
}

int rsp = -1;//
ret = net_read(fd, &rsp, sizeof(int));

fprintf(stderr, "%s ", gettime());
if (rsp != MAX) {
fprintf(stderr, "rsp error: ret=%d,rsp=%d,req=%d \n", ret, rsp, MAX);
} else {
fprintf(stderr, "req & rsp is ok(data length: %d) \n", rsp);
}
}
}

void sig_handler(int signo)
{
fprintf(stderr, "receivec sig: %d\n", signo);
}
...全文
493 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
nswcfd 2015-08-26
  • 打赏
  • 举报
回复
每次write前后打印时戳(每次write的时间开销)、同时打印write的count参数和返回值,看看能不能解释3倍的现象。 一般write的返回值小于count,buffer满的可能性非常大。 这时候可以把timeout减半,或减少为1/10,降低下一次write的等待时间。
quickSort 2015-08-20
  • 打赏
  • 举报
回复
引用 7 楼 nswcfd 的回复:
楼主自己的write实现是个循环,不止一次sys_write系统调用, 第一次调用【超时】返回写满buffer的内容,循环并不结束, 继续调用sys_write,【超时】返回-1,导致外层循环退出。 所以是x2的关系。 第一次虽然只写了一部分,但由于非0,不认为是错误,返回值是正值; 第二次一个字节也没写进去(因为第一次就写满了),返回值就用来表示错误了。
你的分析挺有道理,应该是离真相越来越近。 还有一个问题,我写的数据量比较大,一次write肯定不够,所以我要做循环写,直到写完或者出错再返回。 这样的话是不是没法避免这个至少2倍的超时?
quickSort 2015-08-20
  • 打赏
  • 举报
回复
引用 7 楼 nswcfd 的回复:
楼主自己的write实现是个循环,不止一次sys_write系统调用, 第一次调用【超时】返回写满buffer的内容,循环并不结束, 继续调用sys_write,【超时】返回-1,导致外层循环退出。 所以是x2的关系。 第一次虽然只写了一部分,但由于非0,不认为是错误,返回值是正值; 第二次一个字节也没写进去(因为第一次就写满了),返回值就用来表示错误了。
偶尔还会出现的3倍, 这个似乎不好解释
nswcfd 2015-08-19
  • 打赏
  • 举报
回复
楼主自己的write实现是个循环,不止一次sys_write系统调用, 第一次调用【超时】返回写满buffer的内容,循环并不结束, 继续调用sys_write,【超时】返回-1,导致外层循环退出。 所以是x2的关系。 第一次虽然只写了一部分,但由于非0,不认为是错误,返回值是正值; 第二次一个字节也没写进去(因为第一次就写满了),返回值就用来表示错误了。
nswcfd 2015-08-19
  • 打赏
  • 举报
回复
/* [~]# cat test.c */
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <time.h>

char buf[1024 * 1024 * 10];

int main()
{
	int fd = socket(AF_INET, SOCK_STREAM, 0); 
	struct sockaddr_in addr = {
		.sin_family = AF_INET,
		.sin_addr.s_addr = htonl(0x7f000001),
		.sin_port = htons(10000),
	};
	struct timeval tv = {
		.tv_sec = 5,
	};
	int n;
	n = setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
	printf("SO_SNDTIMEO=%d\n", n);
	n = connect(fd, (void *)&addr, sizeof(addr));
	printf("connect=%d\n", n);
	if (n < 0)
		return 1;
	printf("wait 5s...\n");
	sleep(5);
	printf("begin write..\n");
	time_t t1, t2;
	t1 = time(NULL);
	n = write(fd, buf, sizeof buf);
	t2 = time(NULL);
	printf("write=%d/%d in %ds\n", n, sizeof buf, t2 - t1);
	return 0;
}

/*
[~]# nc -ldv 127.0.0.1 10000 &
[1] 7031
[~]# ./test &
[2] 7032
[~]# SO_SNDTIMEO=0
connect=0
wait 5s...
Connection from 127.0.0.1 port 10000 [tcp/ndmp] accepted
[~]# iptables -A OUTPUT -p tcp --dport 10000 -j DROP <---------- 用DROP丢包模拟拔断网线
[~]# begin write..
write=49152/10485760 in 5s <----- 没有出现两倍的超时现象,但write确实只写了一部分。
*/
图灵转世 2015-08-18
  • 打赏
  • 举报
回复
我也很关注这个,也想学学。
quickSort 2015-08-18
  • 打赏
  • 举报
回复
引用 2 楼 nswcfd 的回复:
数据量小的时候,write的内容都能写到tcp buffer里,所以SNDTIMEOUT不会其作用。 SNDTIMEOUT主要用在connect完成和等待可用的send buffer。 而且这个等待是系统内部schedule(等待事件)的时间,不是从调用者返回的时间。 当然,这些解释不了为什么是double关系。 楼主通常是在client的哪一步开始拔线? 在client write MAX的过程中,还是等待server response的过程中?timeout表现一样吗?
这个等待时间,难道不是阻塞的最大时间吗? (Specify the receiving or sending timeouts until reporting an error. The argument is a struct timeval. If an input or output function blocks for this period of time, and data has been sent or received, the return value of that function will be the amount of data transferred; if no data has been transferred and the timeout has been reached then -1 is returned with errno set to EAGAIN or EWOULDBLOCK, or EINPROGRESS (for connect(2)) just as if the socket was specified to be nonblocking. If the timeout is set to zero (the default) then the operation will never timeout. Timeouts only have effect for system calls that perform socket I/O (e.g., read(2), recvmsg(2), send(2), sendmsg(2)); timeouts have no effect for select(2), poll(2), epoll_wait(2), and so on. (来源: http://linux.die.net/man/7/socket)) 通常我在写数据前,就是先拔掉网线,然后回车,程序开始写socket。 rcvtimo也就是接收超时是没有问题的,总能在设定的超时时间到了就返回错误。
nswcfd 2015-08-18
  • 打赏
  • 举报
回复
个人理解,未经验证,一次write传入很大的buffer,在tcp_sendmsg内部,根据MSS分解成多次小块,依次向socket send buffer提交。 前面的小块可以直接填充,后面buffer变满,就进入wait状态(等待ack消化掉前面的数据),这时候timeout参数开始起作用,超时后返回错误。
nswcfd 2015-08-17
  • 打赏
  • 举报
回复
数据量小的时候,write的内容都能写到tcp buffer里,所以SNDTIMEOUT不会其作用。 SNDTIMEOUT主要用在connect完成和等待可用的send buffer。 而且这个等待是系统内部schedule(等待事件)的时间,不是从调用者返回的时间。 当然,这些解释不了为什么是double关系。 楼主通常是在client的哪一步开始拔线? 在client write MAX的过程中,还是等待server response的过程中?timeout表现一样吗?
quickSort 2015-08-16
  • 打赏
  • 举报
回复
在数据量较小的情况下,比如MAX只有几十个字节,拔掉网线也根本不返回错误,只在读的时候才返回错误。 在大于1M数据量后,就出现2倍设定的超时

23,125

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 应用程序开发区
社区管理员
  • 应用程序开发区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧