Linux网络编程(3)

1、主动发起关闭连接

一开始是关闭状态,主动发起连接段发送SYN信号,进入SYN_SENT(主动打开状态),发送ACK和SYN信号,接收发送端返回的ACK信号,此时三次握手建立完毕,进入ESTABLISHED(数据传输状态)
    
    一端发送FIN标志,进入FIN_WAIT_1状态,接收对端发送过来的ACK后进入FIN_WAIT2状态,(半关闭状态),主动发起端接收对端发来的FIN信号,然后发出回应ACK进入TIME_WAIT状态,对端接收ACK进入CLOSING。
    进入TIME_WAIT后,会有2MSL等待时间,主要目的确保最后一个发送的ACK能够准确到达
    主动关闭一端发出最后的一个ACK
    ubuntu中的MSL时间大约是30秒,最大生成时间

2、被动发起,关闭连接
    直接打开监听状态,接收客户端发送来的SYN,向客户端发送SYN和ACK,进入到SYN_RCVD,客户端接收ACK后进入ESTABLISHED状态,三次握手建立完毕
    服务器端接收FIN发送ACK进入CLOSE_WAIT状态,发送FIN后进入LAST_ACK,接收完ACK后 关闭

可以通过netstat -apn | grep 端口号查看当前端口处于哪个状态

3、半关闭

        是指,客户端断开连接,而服务器端仍然处于连接状态,此时,客户端无法发送数据,但是可以接收数据

RST是一个标志位,ACK应答发布过去的时候,就会发一个RST标志,三次握手建立失败,由对端的操作系统发送
    半关闭:程序中如何实现半关闭:
    当有多个文件描述符指向socket的时候,用close只能关闭一个,管的不彻底,所以就用到了shutdown
从程序的角度,可以使用API来控制实现半连接状态。
#include <sys/socket.h>
int shutdown(int sockfd, int how);
sockfd: 需要关闭的socket的描述符
how:    允许为shutdown操作选择以下几种方式:
    SHUT_RD(0):    关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。
                    该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。
    SHUT_WR(1):        关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。
    SHUT_RDWR(2):    关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。
使用close中止一个连接,但它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为0时才关闭连接。
shutdown不考虑描述符的引用计数,直接关闭描述符。也可选择中止一个方向的连接,只中止读或只中止写。
注意:
1.如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。 
2.在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后,其它的进程将无法进行通信。但,如果一个进程close(sfd)将不会影响到其它进程。

4、端口复用    

     会出现一种错误,当我们把服务器和客户端都打开的时候,先关闭服务器在关闭客户端,再次开启的时候就会出错,解决这个问题就可以使用端口复用。

     端口复用
在server的TCP连接没有完全断开之前不允许重新监听是不合理的。因为,TCP连接没有完全断开指的是connfd(127.0.0.1:6666)没有完全断开,而我们重新监听的是lis-tenfd(0.0.0.0:6666),虽然是占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。
在server代码的socket()和bind()调用之间插入如下代码:
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    状态不会变,依然是CLOSE_WAIT,只不过改的是端口可以复用

出现错误的原因:

lient终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。
MSL在RFC 1122中规定为两分钟,但是各操作系统的实现不同

5、多路IO转换

    多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。
    用内核来帮忙处理,可以省略连接时的阻塞,完成建立连接,读数据都不会有阻塞
6、多路IO转换的几种方法:

        select函数,pool和epool函数

7、select函数原型分析    
    int select(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);
            
    第一个参数:所监听的文件描述符中,最大的文件描述符+1,
        fd_t是一个位图
    第2/3/4参数:所监听的文件描述符"可读"事件
                 所监听的文件描述符"可写"事件
                 所监听的文件描述符"异常 "事件
    第五个参数:设置一个参数 定时阻塞监控时间,3种情况
                1.NULL,永远等下去
                2.设置timeval,等待固定时间
                3.设置timeval里时间均为0,检查描述字后立即返回,轮询
    struct timeval {
        long tv_sec; /* seconds */
        long tv_usec; /* microseconds */
    };
    都是传入传出参数, 传进去的时候所有加进集合的所有文件描述符,传出的是满足条件的文件描述符
    
    返回值:成功返回所监听的所有的监听集合中满足条件的总数。
             
    void FD_CLR(int fd, fd_set *set);     //fd从set中清除出去
    int FD_ISSET(int fd, fd_set *set);     //fd是否在集合中
    void FD_SET(int fd, fd_set *set);     //把fd设置到set集合中去
    void FD_ZERO(fd_set *set);             //把文件描述符集合里所有位清0
    
    使用:
        首先fd_set readfds;
        FD_ZERO(&readfds)
        FD_SET(fd1,&readfds)
        FD_SET(fd2,&readfds)
        FD_SET(fd3,&readfds)
        select();返回一个总数
        for(i<返回的总数)
            FD_ISSET(放到,&readfds);判断在不在这个集合里面
    
弊端:
1、文件描述符1024个,slect监听最大的上限就是1024个    
2、 当我们用的是3和1023时,查找是哪个满足条件时,会for从3到1024去查,我们自己定义一个数据结构,数组,把我们的放入数组中然后遍历这个数组
3、监听集合,满足监听条件的集合,是一个集合,需要将原有集合保存

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666

int main(int argc, char *argv[])
{
	int i, maxi, maxfd, listenfd, connfd, sockfd;
	int nready, client[FD_SETSIZE]; 	/* FD_SETSIZE 默认为 1024 */
	ssize_t n;
	fd_set rset, allset; allset保存上一次的集合是什么样
	char buf[MAXLINE];
	char str[INET_ADDRSTRLEN]; 			/* #define INET_ADDRSTRLEN 16 */
	socklen_t cliaddr_len;
	struct sockaddr_in cliaddr, servaddr;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	Listen(listenfd, 20); 		/* 默认最大128 */

	maxfd = listenfd; 			/* 初始化 */
	maxi = -1;					/* client[]的下标 */

	for (i = 0; i < FD_SETSIZE; i++)
		client[i] = -1; 		/* 用-1初始化client[] */

	FD_ZERO(&allset);
	FD_SET(listenfd, &allset); /* 构造select监控文件描述符集 */

for ( ; ; ) {
	rset = allset; 			/* 每次循环时都从新设置select监控信号集 */
	nready = select(maxfd+1, &rset, NULL, NULL, NULL);

	if (nready < 0)
		perr_exit("select error");
	if (FD_ISSET(listenfd, &rset)) { /* new client connection */有新的连接请求,链接的那个发了数据过来,
		cliaddr_len = sizeof(cliaddr);
		connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
		printf("received from %s at PORT %d\n",
				inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
				ntohs(cliaddr.sin_port));
		for (i = 0; i < FD_SETSIZE; i++) {
			if (client[i] < 0) {  //一开始把这个数组的值全部变为-1了,找一个任意地让把connfd放进去
				client[i] = connfd; /* 保存accept返回的文件描述符到client[]里 */
				break;
			}
		}
		/* 达到select能监控的文件个数上限 1024 */
		if (i == FD_SETSIZE) {
			fputs("too many clients\n", stderr);
			exit(1);
		}

		FD_SET(connfd, &allset); 	/* 添加一个新的文件描述符到监控信号集里 */
		if (connfd > maxfd)
			maxfd = connfd; 		/* select第一个参数需要 */
		if (i > maxi)
			maxi = i; 				/* 更新client[]最大下标值 */

		if (--nready == 0)
			continue; 				/* 如果没有更多的就绪文件描述符继续回到上面select阻塞监听,
										负责处理未处理完的就绪文件描述符 */
		}//判断是否有新的链接,并且把新的文件描述符加到集合里

		for (i = 0; i <= maxi; i++) { 	/* 检测哪个clients 有数据就绪 */
			if ( (sockfd = client[i]) < 0) 				continue;
			if (FD_ISSET(sockfd, &rset)) {//判断已连接的有没有读事件发生,没有的话回到死循环处接着监听,这里的Sockfd是fd1 已经被踢出去了,如果有数据发送,就不会踢出去,接着向下执行
				if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
					Close(sockfd);		/* 当client关闭链接时,服务器端也关闭对应链接 */
					FD_CLR(sockfd, &allset); /* 解除select监控此文件描述符 */
					client[i] = -1;
				} else {
					int j;
					for (j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
					Write(sockfd, buf, n);
				}
				if (--nready == 0)
					break; 
			}
		}
	}
	close(listenfd);
	return 0;
}

 

select实现
    文件描述符分两大类
        一、建立链接完成 : 读,写,异常
        二、建立连接请求:listenfd
   
一开始,rest有两个值listenfd fd1,当resect返回的时候,他返回对应时间发生的的,listen事件有事件发生    sd1没有进行数据传输所以会把fd1踢出去    
    
在rset = allset;             /* 每次循环时都从新设置select监控信号集 */
    nready = select(maxfd+1, &rset, NULL, NULL, NULL);
    之后,如果你在rest里面的几个文件有处理的就去处理,没有的就踢掉

8

poll函数    
    #include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    struct pollfd {
        int fd; /* 文件描述符 */
        short events; /* 监控的事件 */
        short revents; /* 监控事件中满足条件返回的事件 */
    };
     nfds 监听的个数
        timeout         毫秒级等待
        -1:阻塞等,#define INFTIM -1                 Linux中没有定义此宏
        0:立即返回,不阻塞进程
        >0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值

    
fds:数组的首地址,结构体数组
数组里面元素的个数    
优点:    突破1024限制,通过修改配置文件

        监听集合返回集合 分离
        搜索范围小,定义一个数组,满足条件的放到一个数组里面
        epoll可以实现,监听100个文件,有三个满足的,直接告诉你是哪三个
        
    突破1024限制,通过修改配置文件    
            可以使用cat命令查看一个进程可以打开的socket描述符上限。
            cat /proc/sys/fs/file-max
        如有需要,可以通过修改配置文件的方式修改该上限值。
            sudo vi /etc/security/limits.conf
            在文件尾部写入以下配置,soft软限制,hard硬限制。如下图所示。
            * soft nofile 65536       下限
            * hard nofile 100000  上限
    
监听集合返回集合 分离    
    fds[0].fd = lkistenfd;
    fds[0].events = POOLIN /POLLOUT/POLLERR
    fd[0].revents 系统给填

struct pollfd fds[50000];
fds[1].fd = d1
fd[1].events = POILLIN    
pool(fds,5,.-1)

/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024

int main(int argc, char *argv[])
{
	int i, j, maxi, listenfd, connfd, sockfd;
	int nready;
	ssize_t n;
	char buf[MAXLINE], str[INET_ADDRSTRLEN];
	socklen_t clilen;
	struct pollfd client[OPEN_MAX];
	struct sockaddr_in cliaddr, servaddr;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	Listen(listenfd, 20);

	client[0].fd = listenfd;
	client[0].events = POLLRDNORM; 					/* listenfd监听普通读事件 */

	for (i = 1; i < OPEN_MAX; i++)
		client[i].fd = -1; 							/* 用-1初始化client[]里剩下元素 */
	maxi = 0; 										/* client[]数组有效元素中最大元素下标 */

	for ( ; ; ) {
		nready = poll(client, maxi+1, -1); 			/* 阻塞 */
		if (client[0].revents & POLLRDNORM) { 		/* 有客户端链接请求 */
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
			printf("received from %s at PORT %d\n",
					inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
					ntohs(cliaddr.sin_port));
			for (i = 1; i < OPEN_MAX; i++) {
				if (client[i].fd < 0) {
					client[i].fd = connfd; 	/* 找到client[]中空闲的位置,存放accept返回的connfd */
					break;
				}
			}

			if (i == OPEN_MAX)
				perr_exit("too many clients");

			client[i].events = POLLRDNORM; 		/* 设置刚刚返回的connfd,监控读事件 */
			if (i > maxi)
				maxi = i; 						/* 更新client[]中最大元素下标 */
			if (--nready <= 0)
				continue; 						/* 没有更多就绪事件时,继续回到poll阻塞 */
		}
		for (i = 1; i <= maxi; i++) { 			/* 检测client[] */
			if ((sockfd = client[i].fd) < 0)
				continue;
			if (client[i].revents & (POLLRDNORM | POLLERR)) {
				if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
					if (errno == ECONNRESET) { /* 当收到 RST标志时 */
						/* connection reset by client */
						printf("client[%d] aborted connection\n", i);
						Close(sockfd);
						client[i].fd = -1;
					} else {
						perr_exit("read error");
					}
				} else if (n == 0) {
					/* connection closed by client */
					printf("client[%d] closed connection\n", i);
					Close(sockfd);
					client[i].fd = -1;
				} else {
					for (j = 0; j < n; j++)
						buf[j] = toupper(buf[j]);
						Writen(sockfd, buf, n);
				}
				if (--nready <= 0)
					break; 				/* no more readable descriptors */
			}
		}
	}
	return 0;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值