基于 Linux 平台

shutdown() 函数介绍

§ shutdown()优雅断开连接 - 图1

当我们使用 socket 来传输文件的时候,怎么检测到文件传输完成了呢?

这个时候服务器端应该最后向客户端传递 EOF 表示文件传输结束,客户端通过函数返回值接收 EOF,这样可以避免与被传输的文件内容产生冲突。所以现在的问题就变成了,服务器如何传递 EOF?

这个时候就用到了优雅的函数 shutdown,服务器关闭输出流,同时服务器发送 EOF 表示文件传输完成,但是这个时候因为保留了输入流,所以可以接收客户端的数据。

§ shutdown()优雅断开连接 - 图2

§ shutdown()优雅断开连接 - 图3 函数原型为:

  1. #include <sys/socket.h>
  2. int shutdown(int sock, int howto);

§ shutdown()优雅断开连接 - 图4 参数:

  • sock:需要断开的套接字文件描述符
  • howto:传递断开方式信息,其可能值如下
    • SHUT_RD:断开输入流
    • SHUT_WR:断开输出流
    • SHUT_RDWR:断开输入和输出流

§ shutdown()优雅断开连接 - 图5 返回值:成功时返回 0,失败时返回 -1.

§ shutdown()优雅断开连接 - 图6 例子:

  1. // 这个例子就是把下面代码中的句子
  2. int clnt_sd;
  3. socklen_t clnt_adr_sz;
  4. clnt_sd = accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
  5. shutdown(clnt_sd, SHUT_WR);

服务器代码

看一下针对这种情况,服务器的代码是什么样的

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #define BUF_SIZE 30
  8. void error_handling(char *message);
  9. int main(int argc, char *argv[]) {
  10. int serv_sd, clnt_sd;
  11. FILE *fp;
  12. char buf[BUF_SIZE];
  13. int read_cnt;
  14. struct sockaddr_in serv_adr, clnt_adr;
  15. socklen_t clnt_adr_sz;
  16. if (argc != 2) {
  17. printf("Usage: %s <port>\n", argv[0]);
  18. exit(1);
  19. }
  20. fp = fopen("file_server.c", "rb");
  21. serv_sd = socket(PF_INET, SOCK_STREAM, 0); // 服务器套接字
  22. memset(&serv_adr, 0, sizeof(serv_adr)); // 结构体初始化
  23. serv_adr.sin_family = AF_INET; // IPV4 地址
  24. serv_adr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示任意 IP
  25. // 注意:INADDR_ANY 相当于 0.0.0.0
  26. serv_adr.sin_port = htons(atoi(argv[1]));
  27. bind(serv_sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
  28. listen(serv_sd, 5); // TCP 监听
  29. clnt_adr_sz = sizeof(clnt_adr);
  30. clnt_sd = accept(serv_sd, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
  31. while(1) {
  32. read_cnt = fread((void*)buf, 1, BUF_SIZE, fp); // 接收客户端数据放到 buf
  33. if (read_cnt < BUF_SIZE) {
  34. write(clnt_sd, buf, read_cnt);
  35. break;
  36. }
  37. write(clnt_sd, buf, BUF_SIZE);
  38. }
  39. shutdown(clnt_sd, SHUT_WR); // shutdown 对输出流半关闭
  40. read(clnt_sd, buf, BUF_SIZE); // 半关闭后仍然可以接收消息
  41. printf("Message from client:%s\n", buf);
  42. fclose(fp);
  43. close(clnt_sd);
  44. close(serv_sd);
  45. return 0;
  46. }
  47. void error_handling(char *message) {
  48. fputs(message, stderr);
  49. fputc('\n', stderr);
  50. exit(1);
  51. }

客户端代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #define BUF_SIZE 30
  8. void error_handling(char *message);
  9. int main(int argc, char *argv[]) {
  10. int sd;
  11. FILE *sp;
  12. char buf[BUF_SIZE];
  13. int read_cnt;
  14. struct sockaddr_in serv_adr;
  15. if (argc != 3) {
  16. printf("Usage: %s <IP> <port>\n", argv[0]);
  17. exit(1);
  18. }
  19. fp = fopen("receive.dat", "wb");
  20. sd = socket(PF_INET, SOCK_STREAM, 0);
  21. memset(&serv_adr, 0, sizeof(serv_adr));
  22. serv_adr.sin_family = AF_INET;
  23. serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
  24. serv_adr.sin_port = htons(atoi(argv[2]));
  25. connect(sd, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
  26. while((read_cnt=read(sd, buf, BUF_SIZE))!=0) {
  27. // 接收数据并且保存到文件中,直到最后接收到 EOF
  28. fwrite((void*)buf, 1, read_cnt, fp);
  29. }
  30. puts("Received file data");
  31. write(sd, "Thank you", 10);
  32. fclose(fp);
  33. close(sd);
  34. return 0;
  35. }
  36. void error_handling(char *message) {
  37. fputs(message, stderr);
  38. fputc('\n', stderr);
  39. exit(1);
  40. }

基于 Windows 平台

Windows 平台同样通过调用 shutdown() 函数完成半关闭,只是向其传递的参数名略有不同,需要确认。

§ shutdown()优雅断开连接 - 图7 函数原型

  1. #include <winsock2.h>
  2. int shudown(SOCKET sock, int howto);

§ shutdown()优雅断开连接 - 图8 函数参数

  • sock:要断开的套接字句柄
  • howto:断开方式的信息,该参数的可能值和其含义可整理如下:
    • SD_RECEIVE:断开输入流
    • SD_SEND:断开输出流
    • SD_BOTH:同时断开输入输出流

§ shutdown()优雅断开连接 - 图9 函数返回值:函数成功时返回 0,失败时返回 SOCKET_ERROR。

服务器端代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <winsock2.h>
  5. #define BUF_SIZE 30
  6. void ErrorHandling(char *message);
  7. int main(int argc, char *argv[]) {
  8. WSADATA wsaData;
  9. SOCKET hServSock, hClntSock;
  10. FILE *fp;
  11. char buf[BUF_SIZE];
  12. int readCnt;
  13. SOCKADDR_IN servAdr, clntAdr;
  14. int clntAdrSz;
  15. if (argc != 2) {
  16. printf("Usage: %s <port>\n", argv[0]);
  17. exit(1);
  18. }
  19. if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
  20. ErrorHandling("WSAStartup() error!");
  21. }
  22. fp = fopen("file_server_win.c", "rb");
  23. hServSock = socket(PF_INET, SOCK_STREAM, 0); // 服务器套接字
  24. memset(&servAdr, 0, sizeof(servAdr)); // 结构体初始化
  25. servAdr.sin_family = AF_INET; // IPV4 地址
  26. servAdr.sin_addr.s_addr = htonl(INADDR_ANY); // INADDR_ANY 表示任意 IP
  27. // 注意:INADDR_ANY 相当于 0.0.0.0
  28. servAdr.sin_port = htons(atoi(argv[1]));
  29. bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
  30. listen(hServSock, 5); // TCP 监听
  31. clntAdrSz = sizeof(clntAdr);
  32. hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrSz);
  33. while(1) {
  34. readCnt = fread((void*)buf, 1, BUF_SIZE, fp); // 接收客户端数据放到 buf
  35. if (readCnt < BUF_SIZE) {
  36. send(hClntSock, (char*)&buf, readCnt, 0);
  37. break;
  38. }
  39. write(hClntSock, (char*)&buf, BUF_SIZE, 0);
  40. }
  41. shutdown(hClntSock, SD_SEND); // shutdown 对输出流半关闭
  42. recv(hClntSock, (char*)buf, BUF_SIZE, 0); // 半关闭后仍然可以接收消息
  43. printf("Message from client:%s\n", buf);
  44. fclose(fp);
  45. closesocket(hClntSock);
  46. closesocket(hServSock);
  47. WSACleanup();
  48. return 0;
  49. }
  50. void ErrorHandling(char *message) {
  51. fputs(message, stderr);
  52. fputc('\n', stderr);
  53. exit(1);
  54. }

客户端代码

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <winsock2.h>
  5. #define BUF_SIZE 30
  6. void ErrorHandling(char *message);
  7. int main(int argc, char *argv[]) {
  8. WSADATA wsaData;
  9. SOCKET hSocket;
  10. FILE *fp;
  11. char buf[BUF_SIZE];
  12. int readCnt;
  13. sockaddr_In servAdr;
  14. if (argc != 3) {
  15. printf("Usage: %s <IP> <port>\n", argv[0]);
  16. exit(1);
  17. }
  18. if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
  19. ErrorHandling("WSAStartup() error!");
  20. }
  21. fp = fopen("receive.dat", "wb");
  22. hSocket = socket(PF_INET, SOCK_STREAM, 0);
  23. memset(&servAdr, 0, sizeof(servAdr));
  24. servAdr.sin_family = AF_INET;
  25. servAdr.sin_addr.s_addr = inet_addr(argv[1]);
  26. servAdr.sin_port = htons(atoi(argv[2]));
  27. connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr));
  28. while((readCnt=recv(hSocket, buf, BUF_SIZE, 0))!=0) {
  29. // 接收数据并且保存到文件中,直到最后接收到 EOF
  30. fwrite((void*)buf, 1, readCnt, fp);
  31. }
  32. puts("Received file data");
  33. send(hSocket, "Thank you", 10, 0);
  34. fclose(fp);
  35. closesocket(hSocket);
  36. WSACleanup();
  37. return 0;
  38. }
  39. void ErrorHandling(char *message) {
  40. fputs(message, stderr);
  41. fputc('\n', stderr);
  42. exit(1);
  43. }