接着上篇的问题,tcp在收包时是怎样的?关于tcp的发包,根据常识可以判断,必然是按用户的消息包“原子”拼接的,即: UserDataPackage1:UserDataPackage2:UserDataPackage3
的顺序进行拼接的,在实际发送时,可以产生UserDataPackage1:UserDataPackage2-partial
与UserDataPackage2-partial:UserDataPackage3
两个传输包。所以,发送层面用户关注内容不是特别多,而接收处理层面则比较复杂。
示例
服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include "unistd.h"
#include "netinet/in.h" // sockaddr_in
#include "arpa/inet.h" // inet_pton
#define MAXLINE 4096
#define LISTENQ 1024
#define LOG(format, ...) \
printf("file:%s, line:%d ", __FILE__, __LINE__); printf(format, ##__VA_ARGS__); printf("\n")
int main(int argc, char **argv) {
LOG("==== server start ====");
int listenfd, connfd;
struct sockaddr_in servaddr;
int *buff = (int *)malloc(1024 * 1024 * 1024); // 1024M * 1024KB * 1024Byte
time_t ticks;
buff[0] = 4 * 1024 * 1024 / sizeof(int);
for (int i = 0; i < (1024 * 1024 * 1024 / sizeof(int) - 1); ++i) {
buff[i + 1] = i;
}
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(1024);
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == 0) {
LOG("bind port success.");
} else {
LOG("bind port fail.");
exit(-1);
}
listen(listenfd, LISTENQ);
LOG("wait recv connect request.");
for(;;) {
LOG("block net i/o.");
connfd = accept(listenfd, (struct sockaddr *)NULL, NULL);
LOG("accept connect request.");
ticks = time(NULL);
// snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
write(connfd, buff, 4 * 1024 * 1024); // 发生4MB数据,必然分包传输
close(connfd);
break; // 如果不想结束服务,注掉这里
}
LOG("==== server end ====");
exit(0);
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "unistd.h"
#include "netinet/in.h" // sockaddr_in
#include "arpa/inet.h" // inet_pton
#define MAXLINE 4 * 1024 * 1024
#define LOG(format, ...) \
printf("file:%s, line:%d ", __FILE__, __LINE__); printf(format, ##__VA_ARGS__); printf("\n")
int main(int argc, char **argv) {
LOG("==== client start ====");
int sockfd, n;
char *recvline = (char *)malloc(1024 * 1024 * 1024);; //[MAXLINE + 1];
struct sockaddr_in servaddr;
if (argc != 2) {
LOG("usage: a.out <IPaddress>");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
LOG("socket error");
exit(1);
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(1024);
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0) {
LOG("inet_pon error for %s", argv[1]);
exit(1);
}
if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) { // sock.h connect
LOG("connect error");
exit(1);
}
while ((n = read(sockfd, recvline, MAXLINE)) > 0) {
LOG("read bytes: %d", n); // 将接收到的字节数打印出来
recvline[n] = 0;
LOG("recv content:");
LOG("%d", *(int*)recvline);
// if (fputs(recvline, stdout) == EOF) {
// LOG("fputs error.");
// exit(1);
// }
}
if (n < 0) {
LOG("read error");
exit(1);
}
LOG("==== client end ====");
exit(0);
}
执行过程
./server
file:timeserv.c, line:18 ==== server start ====
file:timeserv.c, line:37 bind port success.
file:timeserv.c, line:44 wait recv connect request.
file:timeserv.c, line:47 block net i/o.
file:timeserv.c, line:49 accept connect request.
file:timeserv.c, line:56 ==== server end ====
./client 127.0.0.1
file:time.c, line:15 ==== client start ====
file:time.c, line:44 read bytes: 65536
file:time.c, line:46 recv content:
file:time.c, line:47 1048576
file:time.c, line:44 read bytes: 65536
file:time.c, line:46 recv content:
file:time.c, line:47 16383
file:time.c, line:44 read bytes: 2783743
file:time.c, line:46 recv content:
file:time.c, line:47 32767
file:time.c, line:44 read bytes: 1279489
file:time.c, line:46 recv content:
file:time.c, line:47 186547968
file:time.c, line:59 ==== client end ====
从客户端的读取来看,缓冲区的数据是循环使用的,也就是说在读取的过程中,如果有新的数据接收到,而此时还在读取过程中的话,内核会将新收到的数据仍纳入到本次用户数据请求处理中,直到读取完毕或达到用户空间缓冲区大小结束(??感觉是,但不知道是不是,需要看看内核源码,但似乎又不是??)。
这就是tcp无界的表现,它不管发送方是如何组织的消息分段,在tcp这一层,它就是一串字节流,且流是连续的,可以“无限读”。
基于这样的特征,应用层可以设计特定分界符或分段消息头来进行处理,完成分界。
衍生出一个问题
如果接受缓冲区被打满了,会发生什么?
网友评论