在断开连接时,除了使用close或closesocket函数完全断开连接,还可以只关闭一部分数据交换中使用的流。断开一部分连接是指,可以传输数据但无法接收,或可以接收数据但无法传输。
套接字和流
建立套接字后可交换数据的状态看作一种流,在套接字的流中,数据只能向一个方向移动。为了进行双向通信,需要如图所示的两个流。
一旦两台主机间建立了套接字连接,每个主机就会拥有单独的输入流和输出流。其中一个主机的输入流与另一个主机的输出流相连,而输出流与另一个主机的输入流相连。
针对优雅断开的shutdown函数
shutdown函数用来关闭其中1个流。
SHUT_RD:断开输入流。套接字无法接收数据,即使输入缓冲区收到数据也会抹去,无法调用输入相关函数;SHUT_WR:断开输出流,套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递至目标主机;SHUT_RDWR:同时断开 I/O 流,相当于分两次调用 shutdown,其中一次以 SHUT_RD 为参数,另一次以 SHUT_WR 为参数。
半关闭的应用场景
一种可供参考的应用场景如下:服务器向客户端传输文件,当文件传输结束后需要客户端发送某些信息时,可以调用shutdown函数,只关闭服务器的输出流。这样既可以发送EOF,同时又保留了输入流,可以接收对方数据。
基于半关闭的文件传输程序
服务器端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
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 <port>\n", argv[0]);
exit(1);
}
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)
{
/*
size_t fread( void *buffer, size_t size, size_t count, FILE *stream )
buffer 是读取的数据存放的内存的指针
size 是每次读取的字节数
count 是读取次数
stream 是要读取的文件的指针
返回成功读取的对象个数,若出现错误或到达文件末尾,则可能小于count。若size或count为零,则fread返回零且不进行其他动作。
*/
read_cnt=fread((void*)buf, 1, BUF_SIZE, fp);
if(read_cnt<BUF_SIZE)
{
write(clnt_sd, buf, read_cnt);
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;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char *message);
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 <IP> <port>\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) //当收到EOF时客户端知道文件传输已完成,跳出循环
fwrite((void*)buf, 1, read_cnt, fp);
puts("Received file data");
write(sd, "Thank you", 10);
fclose(fp);
close(sd);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}