TCP/IP網路程式設計 學習筆記_8 --優雅地斷開套接字連線
阿新 • • 發佈:2019-02-19
基於TCP的半關閉
TCP中的斷開連線過程比建立連線過程更重要,因為建立連線過程一般不會出現什麼大的變數,但斷開過程就有可能發生預想不到的情況,因此要準確的掌控。
單方面斷開連線帶來的問題
Linux的close函式和Windows的closesocket函式是完全斷開連線。完全斷開是指無法傳輸資料也不能接收資料。因此,一方這樣直接斷開連線就顯得不太優雅了。如:主機A傳送完最後的資料後,呼叫close函式單方斷開了連線,那麼最終,由主機B傳輸的,主機A必須接收的確認資料也銷燬了(四次握手)。
為了解決這類問題,我們一般採用半關閉的方法,這是指可以傳輸資料但無法接收,或可以接收資料但無法傳輸。就是隻關閉流的一半。套接字和流(Stream)
兩臺主機通過套接字建立連線後進入可交換資料的狀態,我們把這種狀態看作一種流。如流水一樣,水朝一個方向流動,同樣,在套接字的流中,資料也只能向一個方向移動。示例圖如下:
一旦兩臺主機建立了套接字連線,每個主機就會擁有單獨的輸入流和輸出流。如圖,其中一個主機的輸入流與另一主機的輸出流相連,而輸出流則與另一主機的輸入流相連。我們這章講的優雅斷開連線其實就是斷開其中1個流,而非同時斷開兩個流。針對優雅斷開的shutdown函式
int shutdown(int sock, int howto);
sock:需要斷開的套接字檔案描述符
howto:斷開連線的方式,有三種:SHUT_RD:斷開輸入流,SHUT_WR:斷開輸出流,SHUT_RDWR:同時斷開
基於半關閉的檔案傳輸程式
//
// main.cpp
// hello_server
//
// Created by app05 on 15-8-10.
// Copyright (c) 2015年 app05. All rights reserved.
//
#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, const 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("/Users/app05/Desktop/server-clint/hello_server/hello_server/main.cpp", "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);
}
//
// main.cpp
// hello_client
//
// Created by app05 on 15-8-10.
// Copyright (c) 2015年 app05. All rights reserved.
//
#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, const 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("/Users/app05/Desktop/server-clint/hello_client/hello_client/receive.txt", "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));
/*read成功時返回接收的位元組數(但遇到檔案結尾則返回0),失敗時返回-1*/
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);
}