基于 Linux 平台
shutdown() 函数介绍
当我们使用 socket 来传输文件的时候,怎么检测到文件传输完成了呢?
这个时候服务器端应该最后向客户端传递 EOF 表示文件传输结束,客户端通过函数返回值接收 EOF,这样可以避免与被传输的文件内容产生冲突。所以现在的问题就变成了,服务器如何传递 EOF?
这个时候就用到了优雅的函数 shutdown
,服务器关闭输出流,同时服务器发送 EOF 表示文件传输完成,但是这个时候因为保留了输入流,所以可以接收客户端的数据。
函数原型为:
#include <sys/socket.h>
int shutdown(int sock, int howto);
参数:
- sock:需要断开的套接字文件描述符
- howto:传递断开方式信息,其可能值如下
- SHUT_RD:断开输入流
- SHUT_WR:断开输出流
- SHUT_RDWR:断开输入和输出流
返回值:成功时返回 0,失败时返回 -1.
例子:
// 这个例子就是把下面代码中的句子
int clnt_sd;
socklen_t clnt_adr_sz;
clnt_sd = accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
shutdown(clnt_sd, SHUT_WR);
服务器代码
看一下针对这种情况,服务器的代码是什么样的
#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; // IPV4 地址
serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示任意 IP
// 注意:INADDR_ANY 相当于 0.0.0.0
serv_adr.sin_port = htons(atoi(argv[1]));
bind(serv_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
listen(serv_sd, 5); // TCP 监听
clnt_adr_sz = sizeof(clnt_adr);
clnt_sd = accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
while(1) {
read_cnt = fread((void*)buf, 1, BUF_SIZE, fp); // 接收客户端数据放到 buf
if (read_cnt < BUF_SIZE) {
write(clnt_sd, buf, read_cnt);
break;
}
write(clnt_sd, buf, BUF_SIZE);
}
shutdown(clnt_sd, SHUT_WR); // shutdown 对输出流半关闭
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 *sp;
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);
}
基于 Windows 平台
Windows 平台同样通过调用 shutdown() 函数完成半关闭,只是向其传递的参数名略有不同,需要确认。
函数原型
#include <winsock2.h>
int shudown(SOCKET sock, int howto);
函数参数
- sock:要断开的套接字句柄
- howto:断开方式的信息,该参数的可能值和其含义可整理如下:
- SD_RECEIVE:断开输入流
- SD_SEND:断开输出流
- SD_BOTH:同时断开输入输出流
函数返回值:函数成功时返回 0,失败时返回 SOCKET_ERROR。
服务器端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#define BUF_SIZE 30
void ErrorHandling(char *message);
int main(int argc, char *argv[]) {
WSADATA wsaData;
SOCKET hServSock, hClntSock;
FILE *fp;
char buf[BUF_SIZE];
int readCnt;
SOCKADDR_IN servAdr, clntAdr;
int clntAdrSz;
if (argc != 2) {
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
ErrorHandling("WSAStartup() error!");
}
fp = fopen("file_server_win.c", "rb");
hServSock = socket(PF_INET, SOCK_STREAM, 0); // 服务器套接字
memset(&servAdr, 0, sizeof(servAdr)); // 结构体初始化
servAdr.sin_family = AF_INET; // IPV4 地址
servAdr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示任意 IP
// 注意:INADDR_ANY 相当于 0.0.0.0
servAdr.sin_port = htons(atoi(argv[1]));
bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
listen(hServSock, 5); // TCP 监听
clntAdrSz = sizeof(clntAdr);
hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrSz);
while(1) {
readCnt = fread((void*)buf, 1, BUF_SIZE, fp); // 接收客户端数据放到 buf
if (readCnt < BUF_SIZE) {
send(hClntSock, (char*)&buf, readCnt, 0);
break;
}
write(hClntSock, (char*)&buf, BUF_SIZE, 0);
}
shutdown(hClntSock, SD_SEND); // shutdown 对输出流半关闭
recv(hClntSock, (char*)buf, BUF_SIZE, 0); // 半关闭后仍然可以接收消息
printf("Message from client:%s\n", buf);
fclose(fp);
closesocket(hClntSock);
closesocket(hServSock);
WSACleanup();
return 0;
}
void ErrorHandling(char *message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
客户端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#define BUF_SIZE 30
void ErrorHandling(char *message);
int main(int argc, char *argv[]) {
WSADATA wsaData;
SOCKET hSocket;
FILE *fp;
char buf[BUF_SIZE];
int readCnt;
sockaddr_In servAdr;
if (argc != 3) {
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
ErrorHandling("WSAStartup() error!");
}
fp = fopen("receive.dat", "wb");
hSocket = socket(PF_INET, SOCK_STREAM, 0);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = inet_addr(argv[1]);
servAdr.sin_port = htons(atoi(argv[2]));
connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr));
while((readCnt=recv(hSocket, buf, BUF_SIZE, 0))!=0) {
// 接收数据并且保存到文件中,直到最后接收到 EOF
fwrite((void*)buf, 1, readCnt, fp);
}
puts("Received file data");
send(hSocket, "Thank you", 10, 0);
fclose(fp);
closesocket(hSocket);
WSACleanup();
return 0;
}
void ErrorHandling(char *message) {
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}