在上一版本的回射程序中,若服務器子進程被殺死,則客戶端檢測不到這一事件的發生。原因在於,子進程被殺死時,雖然發送了FIN給客戶端套接字,但此時客戶端進程是阻塞於等待標準輸入上的,因此檢測不到套接字的輸入。解決辦法就是使用I/O多路複用。如下圖所示:
另一個問題:若輸入是批量輸入,則輸入結束後,客戶端檢測到EOF,則客戶端會關閉連接,而網絡中還有其他請求和應答,則會發現輸出比輸入少的現象,如下圖所示
當全部的9個請求發出後,則會關閉連接,而此時只有應答1被接收到,其他的會丟失。
解決辦法就是請求全部發送後,只關閉套接字寫的那端,不關閉讀的那端,也即半關閉狀態
爲此,使用了select來進行I/O多路複用。程序如下:
客戶端:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#define err_exit(m)\
{\
perror(m);\
exit(EXIT_FAILURE);\
}
#define SERV_PORT 9877
#define BUFSIZE 4096
int max(int a, int b)
{
if (a > b)
return a;
else
return b;
}
//客戶端具體操作函數
void str_cli(FILE *fp, int sockfd);
int main(int argc, char **argv)
{
if (argc != 2)
{
printf("argument error\n");
exit(0);
}
int sockfd;
struct sockaddr_in servaddr;
int status;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);//將點分十進制IP地址轉化爲網絡字節序的二進制地址
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
err_exit("socket");
status = connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));//連接服務器
if (status == -1)
err_exit("connect");
str_cli(stdin, sockfd);
exit(0);
}
void str_cli(FILE *fp, int sockfd)
{
int stdineof = 0;
int maxfdp1;
fd_set set;
int n;
char buf[BUFSIZE];
FD_ZERO(&set);
for (;;)
{
if (stdineof == 0)
FD_SET(fileno(fp), &set);//若stdineof = 1, 代表輸入已完成,不再監聽該描述符
FD_SET(sockfd, &set);
maxfdp1 = max(fileno(fp), sockfd) + 1;
select(maxfdp1, &set, NULL, NULL, NULL);//使用select的I/O多路複用
if (FD_ISSET(sockfd, &set))//套接字可讀
{
if ((n = read(sockfd, buf, BUFSIZE)) == 0)
{
if (stdineof == 1)
return ; //正常退出
else
{
printf("server terminated prematurely");
exit(1);
}
}
write(fileno(stdout), buf, n);
}
if (FD_ISSET(fileno(fp), &set))//標準輸入可讀
{
if ((n = read(fileno(fp), buf, BUFSIZE)) == 0) //爲0,則表示輸入結束
{
stdineof = 1;
shutdown(sockfd, SHUT_WR);
FD_CLR(fileno(fp), &set);
continue;
}
write(sockfd, buf, n);
}
}
}