TCP和UDP的发送缓冲区和接收缓冲区内存问题

TCP协议是作用是用来进行端对端数据传送的,那么就会有发送端和接收端,在操作系统有两个空间即user space和kernal space。

每个Tcp socket连接在内核中都有一个发送缓冲区和接收缓冲区,TCP的全双工的工作模式以及TCP的流量(拥塞)控制便是依赖于这两个独立的buffer以及buffer的填充状态。

单工:只允许甲方向乙方传送信息,而乙方不能向甲方传送 ,如汽车单行道。

半双工:半双工就是指一个时间段内只有一个动作发生,甲方可以向乙方传送数据,乙方也可以向甲方传送数据,但不能同时进行,如一条窄马路同一时间只能允许一个车通行。

全双工:同时允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。

一个socket的两端,都会有send和recv两个方法,如client发送数据到server,那么就是客户端进程调用send发送数据,而send的作用是将数据拷贝进入socket的内核发送缓冲区之中,然后send便会在上层返回。

也就是说send()方法返回之时,数据不一定会

发送到对端即服务器上去(和write写文件有点类似),send()仅仅是把应用层buffer的数据拷贝进socket的内核发送buffer中,发送是TCP的事情,和send其实没有太大关系。

接收缓冲区把数据缓存入内核,等待recv()读取,recv()所做的工作,就是把内核缓冲区中的数据拷贝到应用层用户的buffer里面,并返回。若应用进程一直没有调用recv()进行读取的话,此数据会一直缓存在相应socket的接收缓冲区内。对于TCP,如果应用进程一直没有读取,接收缓冲区满了之后,发生的动作是:收端通知发端,接收窗口关闭(win=0)。这个便是滑动窗口的实现。保证TCP套接口接收缓冲区不会溢出,从而保证了TCP是可靠传输。因为对方不允许发出超过所通告窗口大小的数据。 这就是TCP的流量控制,如果对方无视窗口大小而发出了超过窗口大小的数据,则接收方TCP将丢弃它。

查看socket发送缓冲区大小,cat /proc/sys/net/ipv4/tcp_wmem

当接收端向发送端发送零窗口报文段后不久,接收端的接收缓存又有了一些存储空间,于是接收端向发送端发送了Windows size = 2的报文段,然而这个报文段在传输过程中丢失了。发送端一直等待收到接收端发送的非零窗口的通知,而接收端一直等待发送端发送数据,这样就死锁了。
1
(2)解决方法

TCP为每个连接设有一个持续计时器。只要TCP连接的一方收到对方的零窗口通知,就启动持续计时器,若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带1字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值。
1
TCP的Window是一个16bit位字段,它代表的是窗口的字节容量,也就是TCP标准窗口最大为2^16 -1 = 65535个字节(grpc默认值)。
另外在TCP的选项字段中还包括了一个TCP窗口扩大因子,option-kind 为 3 ,option-length为 3 个字节,option-data取值范围 0 -14.窗口扩大因子用来扩大TCP窗口,可把原来16bit的窗口,扩大为31bit,一般在带宽比较大的时候使用,比如光纤传输。
/********************************

一个UDP可读缓冲区不够导致丢包的现象

看到一篇写UDP 丢包场景的文章,其中提到如果UDP 缓冲区填满导致丢包的问题,写了个小程序验证了下,确实之前没有细究过,描述如下:

  • 数据报分片重组丢失:UDP 协议本身规定的大小是 64kb,但是在数据链路层有 MTU 的限制,大小大概在 5kb,所以当你发送一个很大的 UDP 包的时候,这个包会在 IP 层进行分片,然后重组。这个过程就有可能导致分片的包丢失。UDP 本身有 CRC 检测机制,会抛弃掉丢失的 UDP 包;

  • UDP 缓冲区填满:当 UDP 的缓冲区已经被填满的时候,接收方还没有处理这部分的 UDP 数据报,这个时候再过来的数据报就没有地方可以存了,自然就都被丢弃了。

    client发送两次UDP数据,第一次 500字节,第二次300字节,server端阻塞模式下接包,第一次recvfrom( 1000 ),收到是 1000,还是500,还是300,还是其他?

    由于UDP通信的有界性,接收到只能是500或300,又由于UDP的无序性和非可靠性,接收到可能是300,也可能是500,也可能一直阻塞在recvfrom调用上,直到超时返回(也就是什么也收不到)。

    第二种情况:在假定数据包是不丢失并且是按照发送顺序按序到达的情况下,server端阻塞模式下接包,先后三次调用:recvfrom( 200),recvfrom( 1000),recvfrom( 1000),接收情况如何呢?
    由于UDP通信的有界性,第一次recvfrom( 200)将接收第一个500字节的数据包,但是因为用户空间buf只有200字节,于是只会返回前面200字节,剩下300字节将丢弃。第二次recvfrom( 1000)将返回300字节,第三次recvfrom( 1000)将会阻塞。

如何解决:

以libevent测试程序为例,在接收到缓冲区有数据的事件后,首先通过如下的方法,或者libevent封装的方法,获取到系统缓冲区中可读数据的大小,然后申请到对应大小的buffer去调用recvfrom方法,否则会出现如上UDP可读缓冲区小余可读数据的情况,导致出现UDP数据读不全的问题!

1

int ret = ioctl(fd, FIONREAD, &totallen);

或者

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

static int

get_n_bytes_readable_on_socket(evutil_socket_t fd)

{

#if defined(FIONREAD) && defined(_WIN32)

        unsigned long lng = EVBUFFER_MAX_READ;

        if (ioctlsocket(fd, FIONREAD, &lng) < 0)

                return -1;

        /* Can overflow, but mostly harmlessly. XXXX */

        return (int)lng;

#elif defined(FIONREAD)

        int n = EVBUFFER_MAX_READ;

        if (ioctl(fd, FIONREAD, &n) < 0)

                return -1;

        return n;

#else

        return EVBUFFER_MAX_READ;

#endif

}

服务器收到包之后全部回发给客户端:

[root@localhost sample]# ./test_udp_server

bind() success – [0.0.0.0] [8888]

Read: len [2] – content [bc]

Read: len [4] – content [abcd]

Read: len [4] – content [abcd]

Read: len [17] – content [12345678901111111]

Read: len [13] – content [aaaaaaaaab123]

客户端发送字符给服务器,并输出服务器发回来的字符串:

[root@localhost sample]# ./test_udp_client

set sz:2097152, et recv_buff:0, len:0

set sz:2097152, get snd_buff:0, len:0

bind() success – [11.12.115.239] [8888]

input 0: exit!

input other: send to other msg!

abc

input message to others : 

sendToServer: len [2] – content [bc]

input message to others : Read:count:2 ,but read len [2] – content [bc]

recvfrom() no pending data.: Success

abcd

sendToServer: len [4] – content [abcd]

input message to others : Read:count:4 ,but read len [4] – content [abcd]

recvfrom() no pending data.: Success

abcd

sendToServer: len [4] – content [abcd]

input message to others : Read:count:4 ,but read len [4] – content [abcd]

recvfrom() no pending data.: Success

12345678901111111

sendToServer: len [17] – content [12345678901111111]

input message to others : Read:count:17 ,but read len [10] – content [1234567890

                                                                                 ­]

recvfrom() no pending data.: Success

aaaaaaaaab123

sendToServer: len [13] – content [aaaaaaaaab123]

input message to others : Read:count:13 ,but read len [10] – content [aaaaaaaaab

                                                                                 ­]

recvfrom() no pending data.: Success

服务器的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

//event test udp socket server

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <event.h>

#include <event2/listener.h>

#include <fcntl.h>

#include <pthread.h>

#include <signal.h>

#include <sys/types.h>

#include <sys/time.h>

#include <sys/stat.h>

#include <sys/resource.h>

/*

[root@localhost sample]# gcc -o test_udp_server test_udp_server.c -levent -lpthread

*/

#define SVR_IP "0.0.0.0"

#define SVR_PORT 8888

#define BUF_SIZE 1024

#define UR_CLIENT_SOCK_BUF_SIZE (65536)

#define UR_SERVER_SOCK_BUF_SIZE (UR_CLIENT_SOCK_BUF_SIZE * 32)

void read_cb(int fd, short event, void *arg) {

    char buf[BUF_SIZE];

    int len;

    int size = sizeof(struct sockaddr);

    struct sockaddr_in client_addr;

    memset(buf, 0, sizeof(buf));

    len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &size);

    if (len == -1) {

        perror("recvfrom()");

    else if (len == 0) {

        printf("Connection Closed\n");

    else {

        printf("Read: len [%d] – content [%s]\n", len, buf);

        /* Echo */

        sendto(fd, buf, len, 0, (struct sockaddr *)&client_addr, size);

    }

}

int set_sock_buf_size(int fd, int sz0)

{

    int sz;

    sz = sz0;

    while (sz > 0) {

        if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const void*) (&sz), (socklen_t) sizeof(sz)) < 0) {

            sz = sz / 2;

        else {

            break;

        }

    }

    if (sz < 1) {

        perror("Cannot set socket rcv size"); 

    }

    sz = sz0;

    while (sz > 0) {

        if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const void*) (&sz), (socklen_t) sizeof(sz)) < 0) {

            sz = sz / 2;

        else {

            break;

        }

    }

    if (sz < 1) {

        perror("Cannot set socket snd size"); 

    }

    return 0;

}

int bind_socket(struct event *ev) {

    int sock_fd;

    int flag = 1;

    struct sockaddr_in sin;

    /* Create endpoint */

    if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {

        perror("socket()");

        return -1;

    }

    /* Set socket option */

    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int)) < 0) {

        perror("setsockopt()");

        return 1;

    }

    /* Set IP, port */

    memset(&sin, 0, sizeof(sin));

    sin.sin_family = AF_INET;

    sin.sin_addr.s_addr = inet_addr(SVR_IP);

    sin.sin_port = htons(SVR_PORT);

#ifdef IP_RECVERR

        if (sin.sin_family != AF_INET6) {

            int on = 0;

#ifdef TURN_IP_RECVERR

            on = 1;

#endif

            if(setsockopt(sock_fd, IPPROTO_IP, IP_RECVERR, (void *)&on, sizeof(on))<0)

                perror("IP_RECVERR");

        }

#endif

#ifdef IPV6_RECVERR

        if (sin.sin_family == AF_INET6) {

            int on = 0;

#ifdef TURN_IP_RECVERR

            on = 1;

#endif

            if(setsockopt(sock_fd, IPPROTO_IPV6, IPV6_RECVERR, (void *)&on, sizeof(on))<0)

                perror("IPV6_RECVERR");

        }

#endif

    if (fcntl(sock_fd, F_SETFL, O_NONBLOCK) == -1) {

        perror("O_NONBLOCK");

        return -1;

    }

    set_sock_buf_size(sock_fd, UR_SERVER_SOCK_BUF_SIZE);

    /* Bind */

    if (bind(sock_fd, (struct sockaddr *)&sinsizeof(struct sockaddr)) < 0) {

        perror("bind()");

        return -1;

    else {

        printf("bind() success – [%s] [%u]\n", SVR_IP, SVR_PORT);

    }

    /* Init one event and add to active events */

    event_set(ev, sock_fd, EV_READ | EV_PERSIST, &read_cb, NULL);

    if (event_add(ev, NULL) == -1) {

        printf("event_add() failed\n");

    }

    return 0;

}

int

main(int argc, char **argv)

{

    struct event ev;

    /* Init. event */

    if (event_init() == NULL) {

        printf("event_init() failed\n");

        return -1;

    }

    /* Bind socket */

    if (bind_socket(&ev) != 0) {  

        printf("bind_socket() failed\n");

        return -1;

    }

    /* Enter event loop */

    event_dispatch();

    printf("done\n");

    return 0;

}

客户端的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

//event test udp socket server

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <event.h>

#include <event2/listener.h>

#include <fcntl.h>

#include <pthread.h> 

#include <signal.h>

#include <sys/ioctl.h>

#include <sys/types.h>

#include <sys/time.h>

#include <sys/stat.h>

#include <sys/resource.h>

/*

[root@localhost sample]# gcc -o test_udp_client test_udp_client.c -levent -lpthread

*/

#define LOCAL_IP "0.0.0.0"

#define LOCAL_PORT 19393

#define SVR_IP "11.12.115.239"

#define SVR_PORT  8888

#define BUF_SIZE 10 //1024

#define UR_CLIENT_SOCK_BUF_SIZE (65536)

#define UR_SERVER_SOCK_BUF_SIZE (UR_CLIENT_SOCK_BUF_SIZE * 32)

void read_cb(int fd, short event, void *arg) {

    char buf[BUF_SIZE];

    int len = 0;

    int totallen = 0;

    int size = sizeof(struct sockaddr);

    struct sockaddr_in client_addr;

    while(1){

        int ret = ioctl(fd, FIONREAD, &totallen);

        if(ret < 0 || !totallen) {

            perror("recvfrom() no pending data.");

            break;

        }

        memset(buf, 0, sizeof(buf));

        len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &size);

        if (len == -1) {

            perror("recvfrom() error");

            break;

        else if (len == 0) {

            printf("Connection Closed\n");

        else {

            printf("Read:count:%d ,but read len [%d] – content [%s]\n",totallen, len, buf);

            /* Echo */

            //sendto(fd, buf, len, 0, (struct sockaddr *)&client_addr, size);

        }

    }

}

  

int set_sock_buf_size(int fd, int sz0)

{

    int sz;

    sz = sz0;

    while (sz > 0) {

        if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (const void*) (&sz), (socklen_t) sizeof(sz)) < 0) {

            sz = sz / 2;

        else {

            break;

        }

    }

    if (sz < 1) {

        perror("Cannot set socket rcv size"); 

    }

        int val=0, len = 0;

        int ret = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void *)&val, &len);

    printf("set sz:%d, et recv_buff:%d, len:%d\r\n", sz, val, len);

    sz = sz0;

    while (sz > 0) {

        if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (const void*) (&sz), (socklen_t) sizeof(sz)) < 0) {

            sz = sz / 2;

        else {

            break;

        }

    }

         

    val=0, len = 0;

    ret = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void *)&val, &len);

    printf("set sz:%d, get snd_buff:%d, len:%d\r\n", sz, val, len);

    if (sz < 1) {

        perror("Cannot set socket snd size"); 

    }

    return 0;

}

void sendToServer(int fd, char* msg, int len, struct sockaddr_in *server){

    printf("sendToServer: len [%d] – content [%s]\n", len, msg);

    sendto(fd, msg, len, 0, (struct sockaddr *)server, sizeof(struct sockaddr_in));

}

typedef struct AAA{

    struct sockaddr_in *server;

    int fd;

}ServerInfo;

//recv user input value

static void * recv_input_thread(void *arg)  {

    if (arg == NULL){

        return NULL;

    }

    ServerInfo *serverInfo = (ServerInfo *) arg;

    char  input;

    char msg[1024];

    printf("input 0: exit!\n"); 

    printf("input other: send to other msg!\n");

    scanf("%c", &input);

    fflush(stdin);

    if (input == '0'){

        return NULL;

    }

    do{

        printf("input message to others : "); 

        memset(msg, 0x00, 1024);

        //scanf("%s", &msg); 

        gets(msg);

        msg[1023] = '\0';

        printf("\n"); 

        sendToServer(serverInfo->fd, msg, strlen(msg), serverInfo->server);

  

    }while(input != '0'); 

    printf("Done!\n"); 

    free(serverInfo);

    return NULL;

}

int bind_socket(struct event *ev, int sock_fd, int local_port) { 

    int flag = 1;

    struct sockaddr_in sin;

   

    /* Set IP, port */

    memset(&sin, 0, sizeof(sin));

    sin.sin_family = AF_INET;

    sin.sin_addr.s_addr = inet_addr(LOCAL_IP);

    sin.sin_port = local_port;

#ifdef IP_RECVERR

        if (sin.sin_family != AF_INET6) {

            int on = 0;

#ifdef TURN_IP_RECVERR

            on = 1;

#endif

            if(setsockopt(sock_fd, IPPROTO_IP, IP_RECVERR, (void *)&on, sizeof(on))<0)

                perror("IP_RECVERR");

        }

#endif

#ifdef IPV6_RECVERR

        if (sin.sin_family == AF_INET6) {

            int on = 0;

#ifdef TURN_IP_RECVERR

            on = 1;

#endif

            if(setsockopt(sock_fd, IPPROTO_IPV6, IPV6_RECVERR, (void *)&on, sizeof(on))<0)

                perror("IPV6_RECVERR");

        }

#endif

    if (fcntl(sock_fd, F_SETFL, O_NONBLOCK) == -1) {

        perror("O_NONBLOCK");

        return -1;

    }

    set_sock_buf_size(sock_fd, UR_SERVER_SOCK_BUF_SIZE);

    /* Bind */

    if (bind(sock_fd, (struct sockaddr *)&sinsizeof(struct sockaddr)) < 0) {

        perror("bind()");

        return -1;

    else {

        printf("bind() success – [%s] [%u]\n", SVR_IP, SVR_PORT);

    }

    /* Init one event and add to active events */

    event_set(ev, sock_fd, EV_READ | EV_PERSIST, &read_cb, NULL);

    if (event_add(ev, NULL) == -1) {

        printf("event_add() failed\n");

    }

    return 0;

}

int

main(int argc, char **argv)

{

    struct event udp_event;

    /* Init. event */

    if (event_init() == NULL) {

        printf("event_init() failed\n");

        return -1;

    

  

    int sock_fd;

    int flag = 1;

    struct sockaddr_in server;

    /* Create endpoint */

    if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {

        perror("socket()");

        return -1;

    }

    /* Set socket option */

    if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int)) < 0) {

        perror("setsockopt()");

        return 1;

    }

    /* Bind socket */

    if (bind_socket(&udp_event, sock_fd, LOCAL_PORT) != 0) {  

        printf("bind_socket() failed\n");

        return -1;

    }

    /* Set IP, port */

    memset(&server, 0, sizeof(server));

    server.sin_family = AF_INET;

    server.sin_addr.s_addr = inet_addr(SVR_IP);

    server.sin_port = htons(SVR_PORT);

    ServerInfo *serverInfo = (ServerInfo *)malloc(sizeof(ServerInfo));

    serverInfo->server = &server;

    serverInfo->fd = sock_fd;

    pthread_t tidp;

    if ((pthread_create(&tidp, NULL, recv_input_thread, (void*)serverInfo)) == -1){

        printf("create error!\n");

        return 1;

    }

  

    /* Enter the event loop; does not return. */

    event_dispatch();

    close(sock_fd);

    printf("done\n");

    return 0;

}

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值