事实上,write()函数调用后并非立即传输数据,read()函数调用后也并非马上接收数据,更准确的说write()函数调用瞬间,数据移至输出缓冲并在适当的时候(不论是一次还是多次传送)传向对方的输入缓冲;read()函数调用瞬间,从输入缓冲中读取数据。
I/O缓冲特性如下:
Linux和Windows的closesocket函数意味着完全断开连接,在某些情况下可能带来问题。若A、B相互通信,A发送完最后的数据后调用close()函数断开连接,之后A无法再发送数据给B,但是由主机B发送的,A必须接收的数据也销毁了。
两台主机通过套接字建立连接后可进入交换数据的状态,我们可以把它视作是一种流(像水流一样朝一个方向流动)。
我们使用shutdown函数可以用来关闭其中一个流。
#include
int shutdown(int sock,int howto);
howto的参数 | 含义 |
---|---|
SHUT_RD | 断开输入流(会抹去输入缓冲的数据) |
SHUT_WR | 断开输出流(会继续传递输出缓冲中的数据) |
SHUT_RDWR | 同时断开I/O流 |
有的朋友可能会有疑惑,究竟为什么需要半关闭,如果留出足够长的时间来保证完成数据传输不就行了吗?没错,保留足够长的时间确实不需要半关闭,但是需要考虑如下情况:假设服务器只用来传输文件数据,而客户端无法知道数据接收到何时,客户端也不能无休止的调用输入函数。为解决此问题,服务器端应传递EOF告诉客户端表示文件传输结束这样就可以了。那么服务器怎么传递呢?shutdown函数应运而生,它可以关闭服务器端的输出流并传递EOF,又保留了输入流,用来接收客户端的消息。
下面我们结合以学内容实现一个收发文件的服务器端与客户端。整体流程如下,使用服务器端发送一个.c文件给客户端,发送结束服务器端后半关闭输出流,最后验证服务器端输入流不会受到影响,接受客户端发送的thank you。
服务器端创建并发送file_server.c给客户端,传输完成后半关闭输出流再读取客户端回传的数据。
#define BUF_SIZE 30
char buf[BUF_SIZE];
FILE *fp;
fp = fopen("file_server.c", "rb");
//每次读取30个字节,不够30字节则全部读取
while (1)
{
read_cnt = fread((void *)buf, 1, BUF_SIZE, fp);
if (read_cnt < BUF_SIZE)
{
write(clnt_sd, buf, BUF_SIZE);
break;
}
write(clnt_sd, buf, BUF_SIZE);
}
shutdown(clnt_sd, SHUT_WR);//半关闭对客户端的输出
read(clnt_sd, buf, BUF_SIZE);//读取客户端发来的数据
printf("Message from client:%s\n", buf);
fclose(fp);
close(clnt_sd);
close(serv_sd);
其中fread()函数函数原型如下
省略掉错误处理的完整代码如下
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 30
int main(int argc, char *argv[])
{
int serv_sd, clnt_sd;
FILE *fp;
char buf[BUF_SIZE];
int read_cnt;
struct sockaddr_in serv_adr, clnt_adr;
socklen_t clnt_adr_sz;
if (argc != 2)
{
printf("Usage:%s\n" , argv[0]);
exit(1);
}
//传递file_server.c的内容
fp = fopen("file_server.c", "rb");
serv_sd = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_adr.sin_port = htons(atoi(argv[1]));
bind(serv_sd, (struct sockaddr *)&serv_adr, sizeof(serv_adr));
listen(serv_sd, 5);
clnt_adr_sz = sizeof(clnt_adr);
clnt_sd = accept(serv_sd, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);
while (1)
{
//每次读取30个字节,不够30字节则全部读取
read_cnt = fread((void *)buf, 1, BUF_SIZE, fp);
if (read_cnt < BUF_SIZE)
{
write(clnt_sd, buf, BUF_SIZE);
break;
}
write(clnt_sd, buf, BUF_SIZE);
}
shutdown(clnt_sd, SHUT_WR); //半关闭对客户端的输出
read(clnt_sd, buf, BUF_SIZE); //读取客户端发来的数据
printf("Message from client:%s\n", buf);
fclose(fp);
close(clnt_sd);
close(serv_sd);
return 0;
}
客户端创建并把收到数据写入receive.dat中,最后回传Thank you
#define BUF_SIZE 30
FILE *fp;
fp = fopen("receive.dat", "wb");
while ((read_cnt = read(sd, buf, BUF_SIZE)) != 0)
fwrite((void *)buf, 1, read_cnt, fp);
puts("Received file data");
write(sd, "Thank you", 10);
fclose(fp);
完整代码如下
#include
#include
#include
#include
#include
#include
#define BUF_SIZE 30
int main(int argc, char *argv[])
{
int sd;
FILE *fp;
char buf[BUF_SIZE];
int read_cnt;
struct sockaddr_in serv_adr;
if (argc != 3)
{
printf("Usage:%s\n" , argv[0]);
exit(1);
}
fp = fopen("receive.dat", "wb");
sd = socket(PF_INET, SOCK_STREAM, 0);
memset(&serv_adr, 0, sizeof(serv_adr));
serv_adr.sin_family = AF_INET;
serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
serv_adr.sin_port = htons(atoi(argv[2]));
connect(sd, (struct sockaddr *)&serv_adr, sizeof(serv_adr));
while ((read_cnt = read(sd, buf, BUF_SIZE)) != 0)
fwrite((void *)buf, 1, read_cnt, fp);
puts("Received file data");
write(sd, "Thank you", 10);
fclose(fp);
close(sd);
return 0;
}