您的位置:首页 > 理论基础 > 计算机网络

网络编程之I_O Multiplexing总结(四)

2018-03-15 16:35 363 查看

1. 概念澄清

1.1

A socket is ready for reading if any of the following four conditions is true:

The read half of the connection is closed (i.e., a TCP connection that has received a FIN). A read operation on the socket will not block and will return 0 (i.e., EOF)

A socket is ready for writing if any of the following four conditions is true:

The write half of the connection is closed. A write operation on the socket will generate SIGPIPE (Section 5.12).

  StackOverflow上关于这个问题给出了答案,其实一开始看到这个问题,难以理解的就是
shutdown(xxxx,SHUT_RD)
,一端调用该函数代表其不再接收数据,但是如果假设
active close
的一端首先调用该函数,显然是无法理解的,因为一般都是发送数据方作为
active close
端,而作为
passive close
端调用该函数,又多此一举,在百科中说该函数调用为空操作不权威,这个问题先按照常见的一种情形处理:

  


1.2

  TCP的状态变化图回顾:



  从理论上分析首先client处于
FIN_WAITE_1
,之后接收到server的
ACK
后,变化到
FIN_WAITE_2
,等待server的
FIN
;server此时接收到client的
FIN
后处于
CLOSE_WAIT
状态,之后其调用
close
向client发送
FIN
,自己进入
LAST_ACK
,等待client的
ACK


  腾讯的面经会有提问
TIME_WAIT
的作用,现在回顾如下:

To implement TCP’s full-duplex connection termination reliably

TIME_WAIT
状态用来重复发送
ACK
,假设第一次
FIN_WAIT_2
回复的
ACK
丢失的话,该
TIME_WAITE
状态持续
2MSL


To allow old duplicate segments to expire in the network

相同地址和端口号建立的连接不希望接收到
lost duplicate
,也就是上次连接的数据,则使用该状态让当前连接的数据全部过期。

2. Select server和Client的改进代码

2.1 Server

//6_ser.c
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#define INET_ADDRSTRLEN 16
#define LISTENQ 10
#define SERV_PORT 9877
#define MAXLINE 20
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
void str_echo(int sockfd);
ssize_t writen(int fd, const void *vptr, size_t n);

int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE];
char strcliaddr[INET_ADDRSTRLEN];
ssize_t n;
fd_set rset, allset;
char buf[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
handle_error("socket");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
if(bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) == -1)
handle_error("bind");
if(listen(listenfd, LISTENQ) == -1)
handle_error("listen");
maxfd = listenfd; /* initialize */
maxi = -1; /* index into client[] array */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* -1 indicates available entry */
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
for ( ; ; ) {
rset = allset; /* structure assignment */
if((nready=select(maxfd+1, &rset, NULL, NULL, NULL)) == -1)
handle_error("select");
if (FD_ISSET(listenfd, &rset)) { /* new client connection */
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) == -1 )
handle_error("accept");
for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd; /* save descriptor */
break;
}
if (i == FD_SETSIZE)
errx(1,"too many clients");
if(inet_ntop(AF_INET,&cliaddr.sin_addr,strcliaddr,INET_ADDRSTRLEN) == NULL)
handle_error("inet_pton");
printf("connected client number : %d, ipaddress:%s\n",i,strcliaddr);
FD_SET(connfd, &allset); /* add new descriptor to set */
if (connfd > maxfd)
maxfd = connfd; /* for select */
if (i > maxi)
maxi = i; /* max index in client[] array */
if (--nready <= 0)
continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = read(sockfd, buf, MAXLINE)) < 0)
handle_error("read");
if(n == 0){
if(getpeername(sockfd,(struct sockaddr *) &cliaddr, &clilen)== -1)
handle_error("getpeername");
if(inet_ntop(AF_INET,&cliaddr.sin_addr,strcliaddr,INET_ADDRSTRLEN) == NULL)
handle_error("inet_pton");
printf("closed client number : %d, ipaddress:%s\n",i,strcliaddr);
if(close(sockfd) == -1)
handle_error("close");
if(i ==  maxi)
maxi-=1;

FD_CLR(sockfd, &allset);
client[i] = -1;
} else
if(writen(sockfd, buf, n) == -1)
handle_error("writen");
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
}
void str_echo(int sockfd)
{
ssize_t n;
char buf[MAXLINE];
again:
while ( (n = read(sockfd, buf, MAXLINE)) > 0)
if(writen(sockfd, buf, n) == -1)
handle_error("writen");
if (n < 0 && errno == EINTR)
goto again;
else if (n < 0)
errx(1,"str_echo: read error");
}

ssize_t writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0)
{
if ( (nwritten = write(fd, ptr, nleft)) <= 0)
{
if (nwritten < 0 && errno == EINTR)
nwritten = 0; /* and call write() again */
else
return (-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return (n);
}


2.2 Client

//6_cli.c
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#define SERV_PORT 9877
#define MAXLINE 20
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
ssize_t writen(int fd, const void *vptr, size_t n);
void str_cli(FILE *fp, int sockfd);
int max(int,int);

int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;

if (argc != 2)
errx(1,"usage: tcpcli <IPaddress>");

if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
handle_error("socket");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) == -1)
handle_error("inet_pton");

if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) == -1)
handle_error("connect");
str_cli(stdin, sockfd);
exit(0);
}
int max(int a,int b)
{
return a < b? b:a;
}
void str_cli(FILE *fp, int sockfd)
{
int maxfdp1, stdineof;
fd_set rset;
char buf[MAXLINE];
int n;
stdineof = 0;
FD_ZERO(&rset);
for ( ; ; ) {
if (stdineof == 0)
FD_SET(fileno(fp), &rset);
FD_SET(sockfd, &rset);
maxfdp1 = max(fileno(fp), sockfd) + 1;
if(select(maxfdp1, &rset, NULL, NULL, NULL) == -1)
handle_error("select");
if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if ( (n = read(sockfd, buf, MAXLINE)) < 0)
handle_error("read");
if( n == 0){
if (stdineof == 1)
return; /* normal termination */
else
errx(1,"str_cli: server terminated prematurely");
}
if( write(fileno(stdout), buf, n) == -1 )
handle_error("write stdio");
}
if (FD_ISSET(fileno(fp), &rset)) { /* input is readable */
if ( (n = read(fileno(fp), buf, MAXLINE)) < 0)
handle_error("read");
if(n == 0){
stdineof = 1;
if(shutdown(sockfd, SHUT_WR)== -1)
handle_error("shutdown");/* send FIN */
FD_CLR(fileno(fp), &rset);
continue;
}
if(writen(sockfd, buf, n) == -1)
handle_error("writen");
}
}
}
ssize_t writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0)
{
if ( (nwritten = write(fd, ptr, nleft)) <= 0)
{
if (nwritten < 0 && errno == EINTR)
nwritten = 0; /* and call write() again */
else
return (-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return (n);
}


3. 结果验证

//server端
[root@localhost ~]# ./6_ser
connected client number : 0, ipaddress:127.0.0.1
connected client number : 1, ipaddress:192.168.182.130
closed client number : 0, ipaddress:127.0.0.1
closed client number : 1, ipaddress:192.168.182.130

//client0
[root@localhost ~]# ./6_cli 127.0.0.1
this is the first client.
this is the first client.
//CTRL+D
[root@localhost ~]#

//client1
[root@localhost ~]# ./6_cli 192.168.182.130
this is the second client.
this is the second client.
//CTRL+D
[root@localhost ~]#


  以上两个client同时连接到server,但是该server是single loop,意味着对于client数据量比较大的时候,阻塞其他client接受server的服务。然后看看状态转化,以一个client为例:

//server继续等待,此时上面两个连接已经结束,9877为server的端口。
[root@localhost ~]# netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:9877            0.0.0.0:*               LISTEN
//笔者是同一台电脑多个终端,所以这里看到两个建立连接的socket,59750是clinet的端口
[root@localhost ~]# ./6_cli 192.168.182.130
//等待输入
[root@localhost ~]# ./6_ser
connected client number : 0, ipaddress:127.0.0.1
connected client number : 1, ipaddress:192.168.182.130
closed client number : 0, ipaddress:127.0.0.1
closed client number : 1, ipaddress:192.168.182.130
connected client number : 0, ipaddress:192.168.182.130 //上面新连接的client

//此时观察netstat
[root@localhost ~]# netstat -a | grep tcp
tcp        0      0 0.0.0.0:9877            0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:ssh             0.0.0.0:*               LISTEN
tcp        0      0 localhost.localdom:9877 localhost.localdo:59750 ESTABLISHED
tcp        0      0 localhost.localdo:59750 localhost.localdom:9877 ESTABLISHED
//在client键入EOF,迅速查看结果,client的TCP处于TIME_WAIT状态,server端的socket已经关闭
[root@localhost ~]# netstat -a | grep tcp
tcp        0      0 0.0.0.0:9877            0.0.0.0:*               LISTEN
tcp        0      0 localhost.localdo:59750 localhost.localdom:9877 TIME_WAIT


4.总结

  client和server都采用
select
处理,为了在server显示连接的client,及client结束时打印相应的信息,笔者用了
inet_pton
getpeername
,对于
maxi
的处理,笔者只是简单的让其减一。最后这两个代码本身的缺陷,在以前的章节有讨论过,在此不赘述。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: