TCP是一个基于字节流的传输服务,"流"意味着TCP所传输的数据是没有边界的。这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的。TCP的发送方无法保证对等方每次接收到的是一个完整的数据包。主机A向主机B发送两个数据包,主机B的接收情况可能是
(1)应用层调用write方法,将应用层的缓冲区中的数据拷贝到套接字的发送缓冲区。而发送缓冲区有一个SO_SNDBUF的限制,如果应用层的缓冲区数据大小大于套接字发送缓冲区的大小,则数据需要进行多次的发送。
(2)TCP所传输的报文段有MSS的限制,如果套接字缓冲区的大小大于MSS,也会导致消息的分割发送。
(3)由于链路层最大发送单元MTU,在IP层会进行数据的分片。
(1)发送定长包。如果每个消息的大小都是一样的,那么在接收对等方只要累计接收数据,直到数据等于一个定长的数值就将它作为一个消息。
(2)包尾加上\r\n标记。FTP协议正是这么做的。但问题在于如果数据正文中也含有\r\n,则会误判为消息的边界。
(3)包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对等方先接收包体长度,依据包体长度来接收包体。
(4)使用更加复杂的应用层协议。
echoserver.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
/*
param1:fd, param2:buf, param3:count
return:读取成功字节数
ssize_t:有符号
size_t:无符号
*/
struct packet {
int len;
char buf[1024];
};
ssize_t readn(int fd, void* buf, size_t count) {
size_t nleft = count;
ssize_t nread; //已经读了
char* bufp = (char*)buf;
while(nleft > 0) {
if((nread = read(fd, bufp, nleft)) < 0) {
if(errno == EINTR)
continue;
return -1;
} else if (0 == nread) {
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
/*
param1:fd, param2:buf, param3:count
return:已经发送了多少
*/
ssize_t writen(int fd, void* buf, size_t count) {
size_t nleft = count;
ssize_t nwrite;
char* bufp = (char*)buf;
while(nleft > 0) {
if((nwrite = write(fd, bufp, nleft)) < 0) {
if(errno == EINTR)
continue;
return -1;
} else if(0 == nwrite) {
continue;
}
bufp += nwrite;
nleft -= nwrite;
}
return count;
}
void do_service(int conn) {
//char recvbuf[1024];
struct packet recvbuf;
int n;
while(1) {
memset(&recvbuf, 0, sizeof(recvbuf));
int ret = readn(conn, &recvbuf.len, 4);
if(-1 == ret) {
ERR_EXIT("read");
} else if(4 > ret) {
printf("client close\n");
break;
}
n = ntohl(recvbuf.len);
ret = readn(conn, recvbuf.buf, n);
if(ret < n) { //server端知道client关闭
printf("client close\n");
break;
} else if(-1 == ret) {
ERR_EXIT("read");
}
fputs(recvbuf.buf, stdout);
writen(conn, &recvbuf, 4+n);
}
}
int main () {
int listenfd;
if(( listenfd= socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
//if((listenfd= socket(PF_INET, SOCK_STREAM, 0)) <0)
ERR_EXIT("socket");
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
//servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//inet_aton("127.0.0.1", &servaddr.sin_addr);
int on = 1;
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
ERR_EXIT("setsockopt");
if(bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))<0)
ERR_EXIT("bind");
if(listen(listenfd, SOMAXCONN) < 0)
ERR_EXIT("listen");
struct sockaddr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
int conn;
pid_t pid;
while(1) {
if((conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen)) < 0)
ERR_EXIT("accept");
printf("ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));
pid = fork();
if(-1 == pid) {
ERR_EXIT("fork");
}
if(pid == 0) { //child
close(listenfd);
do_service(conn);
exit(EXIT_SUCCESS);
} else { //parent
close(conn);
}
}
close(listenfd);
close(conn);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
struct packet {
int len;
char buf[1024];
};
ssize_t readn(int fd, void* buf, size_t count) {
size_t nleft = count;
ssize_t nread; //已经读了
char* bufp = (char*)buf;
while(nleft > 0) {
if((nread = read(fd, bufp, nleft)) < 0) {
if(errno == EINTR)
continue;
return -1;
} else if (0 == nread) {
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd, void* buf, size_t count) {
size_t nleft = count;
ssize_t nwrite;
char* bufp = (char*)buf;
while(nleft > 0) {
if((nwrite = write(fd, bufp, nleft)) < 0) {
if(errno == EINTR)
continue;
return -1;
} else if(0 == nwrite) {
continue;
}
bufp += nwrite;
nleft -= nwrite;
}
return count;
}
int main () {
int sock;
if(( sock= socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
ERR_EXIT("socket");
sock = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(5188);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
if(connect(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0)
ERR_EXIT("sockect");
struct packet sendbuf;
struct packet recvbuf;
memset(&sendbuf, 0, sizeof(sendbuf));
memset(&recvbuf, 0, sizeof(recvbuf));
//char sendbuf[1024] = {0};
//char recvbuf[1024] = {0};
int n;
while(fgets(sendbuf.buf, sizeof(sendbuf.buf), stdin) != NULL) {
n = strlen(sendbuf.buf);
sendbuf.len = htonl(n); //注意主机字节序-->网络字节序
writen(sock, &sendbuf, 4+n); //定制协议
//writen(sock, sendbuf, sizeof(sendbuf)); //发送定长包
//writen(sock, sendbuf, strlen(sendbuf));
int ret = readn(sock, &recvbuf.len, 4);
if(-1 == ret) {
ERR_EXIT("read");
} else if(4 > ret) {
printf("client close\n");
break;
}
n = ntohl(recvbuf.len);
ret = readn(sock, recvbuf.buf, n);
if(ret < n) { //server端知道client关闭
printf("client close\n");
break;
} else if(-1 == ret) {
ERR_EXIT("read");
}
//readn(sock, recvbuf, sizeof(recvbuf));
fputs(recvbuf.buf, stdout);
memset(&sendbuf, 0, sizeof(sendbuf));
memset(&recvbuf, 0, sizeof(recvbuf));
}
close(sock);
return 0;
}