发送一行文本给服务器,然后等待应答,这就形成了一个简单的回射服务器。如果把客户和服务器之间的网络作为全双工管道考虑,请求从客户向服务器发送,应答从服务器回发给客户。下图展示了客户与服务器之间的数据分组,忽略网络中的TCP确认(ACK)。
下面是一个批量发送一行数据的示例程序,将标准输入重定向到一个拥有600多行字符的文件,将服务器发送回来的输出重定向到一个用来接收的文件。对于回射服务器而言,它们理应相等。
头文件
#ifndef MY_NET_H #define MY_NET_H #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> #include <unistd.h> #include <time.h> #include <string.h> #include <sys/select.h> #include <sys/time.h> #define MAXLINE 4096 #define SA struct sockaddr #define LISTENEQ 10 /*err_quit*/ void err_quit(const char* err_string) { printf("%s\n", err_string); exit(-1); } /*err_sys*/ void err_sys(const char* err_string) { perror(err_string); exit(-1); } /*Socket*/ int Socket(int domain, int type, int protocol) { int sockfd = socket(domain, type, protocol); if (sockfd == -1) err_sys("socket error"); return sockfd; } /*Inet_pton*/ int Inet_pton(int af, const char *src, void *dst) { int r; if ((r = inet_pton(af, src, dst)) <= 0) err_sys("inet_pton error"); return r; } /*Connect*/ int Connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { int r; if ((r = connect(sockfd, addr, addrlen)) == -1) err_sys("connect error"); return r; } /*Bind*/ int Bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { int r; r = bind(sockfd, addr, addrlen); if (r == -1) err_sys("bind error"); return r; } /*Listen*/ int Listen(int sockfd, int backlog) { int r; r = listen(sockfd, backlog); if (r == -1) err_sys("listen error"); return r; } /*Accept*/ int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { int r; r = accept(sockfd, addr, addrlen); if (r == -1) err_sys("accept error"); return r; } /*Close*/ int Close(int fd) { int r = close(fd); if (r == -1) err_sys("close error"); return r; } /*Read*/ int Read(int fd, void *buf, size_t count) { int r; r = read(fd, buf, count); if (r == -1) err_sys("read error"); return r; } /*Write*/ int Write(int fd, const void *buf, size_t count) { int r; r = write(fd, buf, count); if (r == -1) err_sys("write error"); return r; } /*Writen*/ //返回剩余的字符 int Writen(int fd, const void *buf, int len) { int r; const char* ptr; int nleft = len; if(len <= 0) return -1; ptr = buf; while (1) { r = Write(fd, ptr, len); nleft -= r; ptr += r; len -= r; if (nleft == 0) return 0; } } /*Readn*/ int Readn(int fd, void *buf, int len) { int r; char* ptr; ptr = buf; while (1) { r = Read(fd, ptr, len); if (r == 0) return 0; if (len-r > 0) { len -= r; ptr += r; } else if (len-r == 0) return len; } } #endif
客户程序
#include "net.h" main() { int sockfd, r, len; char recvline[MAXLINE + 1]; struct sockaddr_in servaddr; fd_set rset; int maxfd, nfds; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(22222); Inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); Connect(sockfd, (SA*)&servaddr, sizeof(servaddr)); maxfd = sockfd; nfds = maxfd + 1; FD_ZERO(&rset); while (1) { FD_SET(fileno(stdin), &rset); FD_SET(sockfd, &rset); r = select(nfds, &rset, NULL, NULL, NULL); if (r == -1) { //perror("select error"); exit(-1); } //标注输入可读 if (FD_ISSET(fileno(stdin), &rset)) { if(gets(recvline) != NULL) { len = strlen(recvline); r = Writen(sockfd, recvline, len); } else { Close(sockfd); //读到文件末尾关闭套接字,此时可能仍有数据在管道中 } } //套接字描述符可读 if (FD_ISSET(sockfd, &rset)) { r = Readn(sockfd, recvline, len); if (r == 0) { break; } recvline[r] = 0; printf("%s\n", recvline); } } exit(0); }
服务器程序
#include "net.h" main(int argc, char **argv) { int listenfd, connfd, r; struct sockaddr_in servaddr; char buf[MAXLINE]; time_t ticks; listenfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(22222); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); Bind(listenfd, (SA*)&servaddr, sizeof(servaddr)); Listen(listenfd, LISTENEQ); connfd = accept(listenfd, (SA*)NULL, NULL); while(1) { r = read(connfd, buf, MAXLINE); if (r == 0) { printf("客户端断开连接\n"); break; } buf[r] = 0; Writen(connfd, buf, r); } exit(0); }
重定向文件
./c <cdx_input.txt >cdx_output.txt
执行程序后查看用来接收服务器发送回数据的文件
如图所示,运行几次后,却发现接收文件总是小于发送文件。这是为什么呢?
问题在于gets读到文件末尾会跳出循环调用close,而此时虽然数据数据发送完毕,但管道中仍有可能还有去往服务器的数据,或从服务器返回客户的数据,此时close会终止读和写两个方向的数据传送,导致接收到的数据小于发送的数据。如下图所示。
1.close终止读、写两个方向的数据发送.
2.close把描述符引用计数-1,仅在计数变为0时才关闭套接字。而shutdown可以不管引用计数就激发终止序列(FIN)。
原型:
#include <sys/socket.h> int shutdown(int sockfd, int how); 返回值:成功返回0,出错返回-1
参数:
how有三个值
SHUT_RD:关闭读,套接字中不再接收数据,接收缓冲区中现有数据被丢弃。进程不能对该套接字再调用任何读函数,由该套接字接收的来自对端的数据被确认,然后被丢弃。
SHUT_WR:关闭写,对于TCP套接字称为半关闭,当先缓冲区数据被发送掉,后跟TCP正常终止序列,进程不能对该套接字再调用任何写函数。
SHUT_RDWR:相当于先调用两次shutdown,第一次指定SHUT_RD,第二次指定SHUT_WR。
修改后的客户程序,同时通知服务器发送数据完毕,而且可以继续接收数据。
#include "net.h" main() { int sockfd, r, len; char recvline[MAXLINE + 1]; struct sockaddr_in servaddr; fd_set rset; int maxfd, nfds; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(22222); Inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); Connect(sockfd, (SA*)&servaddr, sizeof(servaddr)); //printf("连接成功\n"); maxfd = sockfd; nfds = maxfd + 1; FD_ZERO(&rset); while (1) { FD_SET(fileno(stdin), &rset); FD_SET(sockfd, &rset); r = select(nfds, &rset, NULL, NULL, NULL); if (r == -1) { //perror("select error"); exit(-1); } //标注输入可读 if (FD_ISSET(fileno(stdin), &rset)) { if(gets(recvline) != NULL) { len = strlen(recvline); r = Writen(sockfd, recvline, len); } else { shutdown(sockfd, SHUT_WR); } } //套接字描述符可读 if (FD_ISSET(sockfd, &rset)) { r = Readn(sockfd, recvline, len); if (r == 0) { break; } recvline[r] = 0; printf("%s\n", recvline); } } exit(0); }
执行结果
接收文件
原文:http://blog.csdn.net/aspnet_lyc/article/details/24045867