参考书籍:《TCP/IP》网络编程,作者【韩】尹圣雨
测试环境:Ubuntu 10.10
GCC版本:4.4.5
一、单方面断开连接带来的问题
问题:如果主机A发送完数据后,调用close函数断开连接,之后主机A将无法接收主机B传输的数据。
解决办法:只关闭一部分数据交换中使用的流(半关闭)。
二、为何需要半关闭?
考虑如下情况实现过程:客户端连接到服务器端,服务器端将约定的文件传给客户端,客户端接收到后发送字符串“Thank You”给服务器端。
问题1:服务器端连续向客户端传输文件,而客户端则无法知道需要接收数据到何时。客户端也无法无休止地调用输入函数,因为这样有可能会导致程序阻塞(调用的函数未返回)。
尝试办法:服务器端和客户端约定一个代表文件尾的字符。
问题2:约定的文件尾字符不能出现在文件中。
解决办法:服务器端应最后向客户端传递EOF表示文件传输结束。
问题3:服务器如何传递EOF?
解决办法:断开输出流时向对方主机传输EOF。
问题4:服务器端发送完毕后调用close函数会产生EOF,但是客户端接收到EOF后,再向服务端发送数据时,服务端已断开连接,无法接收“Thank You”!
解决办法:服务器发送完文件后,调用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 errorHandling(const char* message);
int main(int argc, char* argv[])
{
int servSock, clientSock;
FILE* file;
char buf[BUF_SIZE] = {0};
int readCount;
struct sockaddr_in servAddr, clientAddr;
socklen_t clientAddrSize;
if(2 != argc)
{
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
file = fopen("main.c", "rb");
servSock = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == servSock)
errorHandling("socket() error");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
servAddr.sin_port = htons(atoi(argv[1]));
if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
errorHandling("bind() error");
if(-1 == listen(servSock, 5))
errorHandling("listen() error");
clientAddrSize = sizeof(clientAddr);
clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);
if(-1 == clientSock)
errorHandling("accept() error");
while(1)
{
readCount = fread((void*)buf, 1, BUF_SIZE, file);
if(readCount < BUF_SIZE)
{
write(clientSock, buf, readCount);
break;
}
write(clientSock, buf, BUF_SIZE);
}
shutdown(clientSock, SHUT_WR);
read(clientSock, buf, BUF_SIZE);
printf("Message from client: %s\n", buf);
fclose(file);
close(clientSock);
close(servSock);
return 0;
}
void errorHandling(const 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 errorHandling(const char* message);
int main(int argc, char* argv[])
{
int sock;
FILE* file;
char buf[BUF_SIZE] = {0};
int readCount = 0;
struct sockaddr_in servAddr;
if(3 != argc)
{
printf("Usage: %s <IP> <port>\n", argv[0]);
exit(1);
}
file = fopen("receive.dat", "wb");
sock = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == sock)
errorHandling("socket() error");
memset(&servAddr, 0, sizeof(servAddr));
servAddr.sin_family = AF_INET;
servAddr.sin_addr.s_addr = inet_addr(argv[1]);
servAddr.sin_port = htons(atoi(argv[2]));
if(-1 == connect(sock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
errorHandling("connect() error");
while((readCount = read(sock, buf, BUF_SIZE)) != 0)
fwrite((void*)buf, 1, readCount, file);
puts("Received file data");
write(sock, "Thank you", 10);
fclose(file);
close(sock);
return 0;
}
void errorHandling(const char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
操作:
1)服务器端:gcc main.c -o fileServer.out,运行:./fileServer.out 9190
Message from client: Thank you
2)客户端:gcc main.c -o fileClient.out,运行:./fileClient.out 127.0.0.1 9190
Received file data
分析:
当服务器读取到的文件小于30个字节时,说明读取到文件尾,使用半关闭函数shutdown关闭了服务器端输出流。服务器端接收到“Thank You”字符,证明了服务器半关闭后,输入流还能正常接收数据。