发送一行文本给服务器,然后等待应答,这就形成了一个简单的回射服务器。如果把客户和服务器之间的网络作为全双工管道考虑,请求从客户向服务器发送,应答从服务器回发给客户。下图展示了客户与服务器之间的数据分组,忽略网络中的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