下面给出开发简单客户回射服务器程序迭代过程。
第一版
程序见上一篇博客末尾,由于服务器关闭后处于TIMEWAIT状态,因此存在一个问题:短时间内服务器程序不能重启并使用。
第二版(服务器端使用了REUSEADDR选项)
使用这个选项后,即使服务器处于TIMEWAIT状态,也可以重新启用。
下面给出实现代码。(默认makefile就是上篇博客末尾的makefile)
cli:同第一版的cli程序。
srv:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
int main(int argc,char* argv[])
{
int listenfd;
if((listenfd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 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;
if((conn = accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen)) < 0)
ERR_EXIT("accept");
printf("ip=%s portr=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
char recvbuf[1024];
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
int ret = read(conn,recvbuf,sizeof(recvbuf));
fputs(recvbuf,stdout);
write(conn,recvbuf,ret);
}
return 0;
}
第三版(增加并发)
父子进程做处理:父进程接受accept,子进程处理连接。
(cli和makefile同第二版)
srv:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
void do_service(int fd);
ssize_t readn(int fd,void* buf,size_t count);
int main(int argc,char* argv[])
{
int listenfd;
if((listenfd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 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 portr=%d\n",inet_ntoa(peeraddr.sin_addr),\
ntohs(peeraddr.sin_port));
pid = fork();
if(pid==-1)
ERR_EXIT("fork");
if(pid==0)
{
close(listenfd);
do_service(conn);
/*break out then close child process*/
exit(EXIT_SUCCESS);
}else
{
close(conn);/*parent and child share the fds*/
}
}
return 0;
}
void do_service(int fd)
{
char recvbuf[1024];
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
int ret = read(fd,recvbuf,sizeof(recvbuf));
if(ret == 0)/*client has closed*/
{
printf("client closed\n");
break;
}else if(ret == -1)
{
ERR_EXIT("read");
}
fputs(recvbuf,stdout);
write(fd,recvbuf,ret);
}
}
第四版 (增加对粘包的处理)
粘包来源
* TCP协议本身(传输层)不能传递消息边界,原因在于基于字节流的TCP是不能保证对等方一次读操作返回多少字节的,因而存在粘包问题。
处理粘包的常见解决办法:
* 定长包
* 包尾加\r\n(ftp)
* 包头加上包体长度
第一次尝试
先尝试定长包的方式来完善之前的第三版程序,思路是封装readn和writen函数。
代码
cli:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
ssize_t readn(int fd,void* buf,size_t count);
ssize_t writen(int fd,void* buf,size_t count);
int main(int argc,char* argv[])
{
int sock;
if((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 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("127.0.0.1");
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//inet_aton("127.0.0.1",&servaddr.sin_addr);
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
ERR_EXIT("connect");
char sendbuf[1024] = {0};
char recvbuf[1024] = {0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
writen(sock,sendbuf,sizeof(sendbuf));
readn(sock,recvbuf,sizeof(recvbuf));
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
return 0;
}
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft = count;
size_t nread = 0;
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nread = read(fd,bufp,nleft)) < 0)
{
if(errno == EINTR)
continue;
return -1;
}
else if(nread == 0)
{
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd,void* buf,size_t count)
{
size_t nleft = count;
size_t nwritten = 0;
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nwritten = write(fd,bufp,nleft)) < 0)
{
if(errno == EINTR)
continue;
return -1;
}
else if(nwritten == 0)
{
continue;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
srv:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
void do_service(int fd);
ssize_t readn(int fd,void* buf,size_t count);
ssize_t writen(int fd,void* buf,size_t count);
int main(int argc,char* argv[])
{
int listenfd;
if((listenfd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 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 portr=%d\n",inet_ntoa(peeraddr.sin_addr),\
ntohs(peeraddr.sin_port));
pid = fork();
if(pid==-1)
ERR_EXIT("fork");
if(pid==0)
{
close(listenfd);
do_service(conn);
/*break out then close child process*/
exit(EXIT_SUCCESS);
}else
{
close(conn);/*parent and child share the fds*/
}
}
return 0;
}
void do_service(int fd)
{
char recvbuf[1024];
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
int ret = readn(fd,recvbuf,sizeof(recvbuf));
if(ret == 0)/*client has closed*/
{
printf("client closed\n");
break;
}else if(ret == -1)
{
ERR_EXIT("read");
}
fputs(recvbuf,stdout);
writen(fd,recvbuf,ret);
}
}
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft = count;/*count:bytes which need to read*/
ssize_t nread;/*has readed*/
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nread = read(fd,bufp,nleft)) < 0)
{
/*if signal interrupt*/
if(errno == EINTR)
continue;
return -1;
}
else if(nread == 0)/*peer has been closed*/
{
// break;
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd,void* buf,size_t count)
{
size_t nleft = count;/*count:bytes which need to read*/
ssize_t nwritten;/*has readed*/
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nwritten = write(fd,bufp,nleft)) < 0)
{
/*if signal interrupt*/
if(errno == EINTR)
continue;
return -1;
}
else if(nwritten == 0)/*peer has been closed*/
{
continue;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
定长包虽然可以解决粘包问题,但是其每次发生都需要一个缓冲区,有效数据可能在其中所占比重非常小,举一个极端例子,每次只发送一个字符。每次发送数据时,大量无效数据充斥于网络,无形中增大了网络负担,因而还需要进一步改进。
第二次尝试
增加一个包头,包头存放每次传输的数据长度。
这样可舍弃定长包。
代码
cli:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
typedef struct _Package
{
int len;
char buf[1024];
}Package;
ssize_t readn(int fd,void* buf,size_t count);
ssize_t writen(int fd,void* buf,size_t count);
int main(int argc,char* argv[])
{
int sock;
if((sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 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("127.0.0.1");
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
//inet_aton("127.0.0.1",&servaddr.sin_addr);
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
ERR_EXIT("connect");
Package sendbuf;
Package recvbuf;
memset(&sendbuf,0,sizeof(sendbuf));
memset(&recvbuf,0,sizeof(recvbuf));
int n = 0;
while(fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin)!=NULL)
{
n = strlen(sendbuf.buf);
sendbuf.len = htonl(n);
writen(sock,&sendbuf,4 + n);
/*receive head len field*/
int ret = readn(sock,&recvbuf.len,4);
if(ret == -1)ERR_EXIT("read");
else if(ret < 4)
{
printf("srv close\n");
break;
}
n = ntohl(recvbuf.len);
ret = readn(sock,recvbuf.buf,n);
if(ret == -1)ERR_EXIT("read");
else if(ret < n)
{
printf("srv close\n");
break;
}
fputs(recvbuf.buf,stdout);
memset(&sendbuf,0,sizeof(sendbuf));
memset(&recvbuf,0,sizeof(recvbuf));
}
close(sock);
return 0;
}
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft = count;
size_t nread = 0;
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nread = read(fd,bufp,nleft)) < 0)
{
if(errno == EINTR)
continue;
return -1;
}
else if(nread == 0)
{
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd,void* buf,size_t count)
{
size_t nleft = count;
size_t nwritten = 0;
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nwritten = write(fd,bufp,nleft)) < 0)
{
if(errno == EINTR)
continue;
return -1;
}
else if(nwritten == 0)
{
continue;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
srv:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define ERR_EXIT(m)\
do\
{\
perror(m);\
exit(EXIT_FAILURE);\
}while(0)
typedef struct _Package
{
int len;
char buf[1024];
}Package;
void do_service(int fd);
ssize_t readn(int fd,void* buf,size_t count);
ssize_t writen(int fd,void* buf,size_t count);
int main(int argc,char* argv[])
{
int listenfd;
if((listenfd = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP)) < 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 portr=%d\n",inet_ntoa(peeraddr.sin_addr),\
ntohs(peeraddr.sin_port));
pid = fork();
if(pid==-1)
ERR_EXIT("fork");
if(pid==0)
{
close(listenfd);
do_service(conn);
/*break out then close child process*/
exit(EXIT_SUCCESS);
}else
{
close(conn);/*parent and child share the fds*/
}
}
return 0;
}
void do_service(int fd)
{
Package recvbuf;
//char recvbuf[1024];
memset(&recvbuf,0,sizeof(recvbuf));
int n = 0;
while(1)
{
memset(&recvbuf,0,sizeof(recvbuf));
int ret = readn(fd,&recvbuf.len,4);
if(ret == -1)ERR_EXIT("read");
else if(ret < 4)/*client has closed*/
{
printf("client closed\n");
break;
}
n = ntohl(recvbuf.len);
ret = readn(fd,recvbuf.buf,n);
if(ret == -1)ERR_EXIT("read");
else if(ret < n)/*client has closed*/
{
printf("client closed\n");
break;
}
fputs(recvbuf.buf,stdout);
writen(fd,&recvbuf,n + 4);
}
}
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft = count;/*count:bytes which need to read*/
ssize_t nread;/*has readed*/
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nread = read(fd,bufp,nleft)) < 0)
{
/*if signal interrupt*/
if(errno == EINTR)
continue;
return -1;
}
else if(nread == 0)/*peer has been closed*/
{
// break;
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd,void* buf,size_t count)
{
size_t nleft = count;/*count:bytes which need to read*/
ssize_t nwritten;/*has readed*/
char* bufp = (char*)buf;
while(nleft > 0)
{
if((nwritten = write(fd,bufp,nleft)) < 0)
{
/*if signal interrupt*/
if(errno == EINTR)
continue;
return -1;
}
else if(nwritten == 0)/*peer has been closed*/
{
continue;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}