网络编程——TCP的半关闭
程序员文章站
2022-06-30 18:21:10
...
参考
- 《TCP/IP网络编程》 尹圣雨
TCP的半关闭
一旦两台主机间建立了套接字连接,每个主机就会拥有单独的输入流和输出流。其中一个主机的输入流与另一主机的输出流相连,而输出流则与另一主机的输入流相连
Linux的close函数和Windows的closesocket函数会同时断开两个流。即主机无法传输数据,也无法接收数据。导致另一台主机正在发送的数据无法再接收了。因此,我们需要半关闭状态,只关闭其中的一个流
假设服务器端想半关闭自己的输出流,一方面需要让客户端知道数据已经传输完毕,另一方面要保留输入流。要实现第一点,服务器端应最后向客户端传递EOF表示文件传输结束;要实现第二点,就使用shutdown函数代替close或closesocket函数
shutdown函数
#include <sys/socket.h>
int shutdown(int sock, int howto);
成功时返回0,失败时返回-1。其中,sock为需要断开的套接字文件描述符;howto为传递断开方式信息
howto有三种可能值:
- SHUT_RD:断开输入流
- SHUT_WR:断开输出流
- SHUT_RDWR:同时断开I/O流
实现
服务器端
#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)
{
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)
{
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 shutdown(SOCKET sock, int howto);
成功时返回0,失败时返回SOCKET_ERROR
howto有三种可能值:
- SD_RECEIVE:断开输入流
- SD_SEND:断开输出流
- SD_BOTH:同时断开I/O流
服务器端
#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;
errno_t error;
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!");
}
error = fopen_s(&fp, "file_server_win.c", "rb");
hServSock = socket(PF_INET, SOCK_STREAM, 0);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
servAdr.sin_port = htons(atoi(argv[1]));
bind(hServSock, (SOCKADDR*)&servAdr, sizeof(servAdr));
listen(hServSock, 5);
clntAdrSz = sizeof(clntAdr);
hClntSock = accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrSz);
while (1)
{
readCnt = fread((void*)buf, 1, BUF_SIZE, fp);
if (readCnt < BUF_SIZE)
{
send(hClntSock, (char*)&buf, readCnt, 0);
break;
}
send(hClntSock, (char*)&buf, BUF_SIZE, 0);
}
shutdown(hClntSock, SD_SEND);
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>
#include <WS2tcpip.h>
#define BUF_SIZE 30
void ErrorHandling(char* message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hSocket;
FILE* fp;
errno_t error;
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!");
}
error = fopen_s(&fp, "receive.dat", "wb");
hSocket = socket(PF_INET, SOCK_STREAM, 0);
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &servAdr.sin_addr);
servAdr.sin_port = htons(atoi(argv[2]));
connect(hSocket, (SOCKADDR*)&servAdr, sizeof(servAdr));
while ((readCnt = recv(hSocket, buf, BUF_SIZE, 0)) != 0)
{
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);
}