网络编程知识

TCP/IP 网络连接的几种状态

TCP/IP 协议中,TCP(传输控制协议)连接在通信过程中会经历不同的状态。以下是TCP连接的主要状态及其说明:
LISTEN - 在服务器端监听来自客户端的连接请求。服务器进程监听某个端口,等待客户端发起连接。
SYN-SENT - 客户端发送一个SYN(同步序列编号)报文以开始一个TCP连接,并进入SYN-SENT状态,等待服务器确认。
SYN-RECEIVED - 服务器收到客户端的SYN请求后,会发送自己的SYN请求作为应答,并附上对客户端SYN请求的确认。服务器进入SYN-RECEIVED状态。
ESTABLISHED - 当客户端收到服务器的SYN+ACK(确认)响应后,会发送一个ACK确认,此时连接建立成功,双方都进入ESTABLISHED状态。在这个状态下,双方可以开始数据传输。
FIN-WAIT-1 - 当连接的一方(可以是客户端或服务器)完成数据发送任务后,它会发送一个FIN(结束)报文,希望关闭连接,并进入FIN-WAIT-1状态。
FIN-WAIT-2 - 当发送方收到对方的ACK确认后,进入FIN-WAIT-2状态,等待对方的FIN报文。
CLOSE-WAIT - 在接收到对方的FIN报文后,另一方发送ACK确认,并进入CLOSE-WAIT状态。在这个状态下,等待本地用户关闭连接。
CLOSING - 在同时关闭的情况下,当一方在发送FIN后收到对方的FIN报文,它会发送ACK确认,并进入CLOSING状态,等待对方的ACK确认。
LAST-ACK - 当处于CLOSE-WAIT状态的一方发送FIN后,它会进入LAST-ACK状态,等待对方的最终ACK确认。
TIME-WAIT - 当一个TCP连接的一方接收到最终的ACK确认后,它会进入TIME-WAIT状态。这个状态通常会持续一段时间(2倍的MSL,最大报文生存时间),以确保对方收到最终的ACK确认。
CLOSED - 最终,当连接彻底关闭后,双方都会进入CLOSED状态。
在TCP连接的生命周期中,这些状态确保了连接的可靠性和正确的关闭。CLOSE-WAIT状态特别指出了接收到FIN请求的一方已经确认关闭请求,但是还没有关闭自己的连接,可能是因为还有数据需要发送或者等待应用程序执行关闭操作。如果一个连接长时间处于CLOSE-WAIT状态,可能表明应用程序没有正确地关闭socket连接,这可能会导致资源泄露。

TCP/IP协议在哪里处理大小端的

在TCP/IP协议中,大小端处理通常是在传输层协议中完成的。

TCP协议是面向连接的、可靠的、基于字节流的协议,它的数据传输是通过TCP段(TCP Segment)进行的。在TCP段中,数据部分可以被看作是一个二进制流,这个流的字节序可能是不同的,因此TCP需要保证在传输数据时,无论对端的字节序如何,接收方都能够正确解析数据。

为了解决大小端问题,TCP协议通过在传输层中定义了一个叫做网络字节序(Network Byte Order)的统一字节序。网络字节序采用大端序的方式,即最高位字节存储在最低的地址上,最低位字节存储在最高的地址上。在TCP传输数据时,发送方会将数据转换为网络字节序,接收方则需要将数据从网络字节序转换为本地字节序。这个过程通常是由操作系统的网络库完成的。

在C/C++中,可以使用头文件<arpa/inet.h>中提供的一些函数来进行字节序转换。其中,htons()和ntohs()函数用于将16位的整数转换为网络字节序和本地字节序;htonl()和ntohl()函数用于将32位的整数转换为网络字节序和本地字节序。这些函数通常在网络编程中使用,以保证数据在不同的机器之间正确传输。

##面试问题
以下为腾讯后台面试题目:
1、A B两主机之间网络通信的全过程(分组转发算法)、什么时候广播
ARP协议的过程
2、从socket读数据时,socket缓存里的数据,可能超过用户缓存的长度,如何处理? 例如,socket缓存有8kB的数据,而你的缓存只有2kB空间。
3、有哪些SOCKET
流socket SOCK_STREAM TCP协议
数据报socket SOCK_DGRAM UDP协议
原始套接字 raw socket
4、TCP/UDP区别、5层网络模型、传输层有哪些协议
5、同步和异步 异步的返回后,如何得知该fd可用
6、内存共享mmap如何实现
7、进程间的通信
8、传输层有哪些协议
9、TCP三次握手、只握两次是什么结果,如果客服端发送大量的SYN包,服务器端会出现什么问题。

##ARP协议
【1】ARP cache:局域网中的每个主机都有一个ARP cache,保存着本局域网上的各个主机或者路由器的IP地址到MAC地址的映射。
【2】一开始如何获取到ARP cache表?
首先,需得到某个主机的MAC地址:
通过广播一个ARP请求分组,填入本主机的IP和MAC地址以及目的主机的IP地址D(本局域网上的所有主机的ARP进程都将收到此ARP请求),只有IP地址为D的主机才会响应该请求。然后得到信息后两方都将IP->MAC的信息填入各自的ARP cache中。

Q 为何不直接用MAC通信,而要用IP
因为,各式各样的网络使用不用的硬件地址。

##路由器分组转发算法
1、【获得目的主机的网络地址】从数据报的首部提取目的主机的IP地址D,计算出目的主机的网络地址N。
(将IP数据报中目的主机的IP地址和路由表上的子网掩码进行&运算,就可以得出网络地址N)
2、【直接交付】若N就是与此路由器直接相连的某个网络的网络地址。则直接进行交付,不需要经过其他路由器,而是直接将IP数据报交付给目的主机。
(注意,直接交付时,路由器需要将目的主机地址D转换为具体的硬件地址,把数据报封装在MAC帧,在发送此帧。)
若N不是与此路由器直接相连的网络,就进行间接交付。执行3或执行4
3、【特定路由】若路由表中有目的地址为D的特定主机路由,则把数据报传送给路由表中所指明的下一跳路由器;否则,执行4。
(这是特殊情况)
4、若路由表中有到达网络N的路由,则把数据报传送给路由表中所指明的下一跳路由器;否则,执行5。
5、【默认路由】如果3和4都没能将IP数据报转发出去,若路由表中有一个默认路由,则把数据报传送给路由表中所指明的默认
路由器;否则,执行6
6、报告转发分组出错。

##ICMP协议
1、【端口不可达】
当UDP/TCP发现从IP上来的报文中的目的端口号不正确时,丢弃该报文,并由ICMP发送“端口不可达”差错报文给发送方。
5种差错报告报文
【终点不可达】当路由器或主机不能交付数据报时,向源点发送
【源点抑制】由于拥塞而丢弃数据时
【时间超过】1、路由器收到生存时间为0的数据报 2、主机在预定时间未收到一个数据报的全部数据报片
【参数问题】
【改变路由】

##TCP*流量控制&拥塞控制
利用滑动窗口来实现流量控制
在TCP中采用滑动窗口来进行传输控制时,滑动窗口的大小意味着接收方的有多大的缓冲来接收数据。一般发送方确认滑动窗口应发送多大的数据,而接收方则确认接收窗口的大小。
一般接收方提供的窗口大小通常由接收进程控制,这将影响TCP的性能。一般操作系统默认设置发送和接收缓冲区的大小为4096字节。
当滑动窗口在未发送数据时,窗口的大小为0,正常是不能发送数据出去,但是有2种情况能发送出数据:
1、发送紧急数据。
2、发送方通知接收方希望接收下一字节的数据及发送方滑动窗口的大小。

利用拥塞算法来实现拥塞控制
两幅图来解释
无论是在慢开始阶段还是在拥塞避免阶段,只要发送方判断网络出现拥塞(其根据就是没有收到确认,虽然没有收到确认可能是其他原因的分组丢失,但是因为无法判定,所以都当做拥塞来处理),就把慢开始门限设置为出现拥塞时的发送窗口大小的一半。然后把拥塞窗口设置为1,执行慢开始算法。如下图
这里写图片描述

这里写图片描述

##tcp与udp的区别
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付(由应用程序去处理)
TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;
UDP是面向报文的,发送方对从应用程序交下来的报文,在添加首部后即交给IP层。
UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道

##udp调用connect有什么作用?
1.因为UDP可以是一对一,多对一,一对多,或者多对多的通信,所以每次调用sendto()/recvfrom()时都必须指定目标IP和端口号。通过调用connect()建立一个端到端的连接,就可以和TCP一样使用send()/recv()传递数据,而不需要每次都指定目标IP和端口号。但是它和TCP不同的是它没有三次握手的过程。
UDP发送接收数据的两种方法:
方法一:
socket----->sendto()或recvfrom()
方法二:
socket----->connect()----->send()或recv()
2.还可以通过在已建立连接的UDP套接字上,再次调用connect()实现以下功能:
a.指定
新的IP地址和端口号

b.断开连接。调用connect时把套接口地址结构的地址簇成员(sin_ family)设置为AF_UNSPEC
TCP套接字只能调用一次connect()函数。

参考:UDP编程中的connect

##tcp连接中时序图,状态图
Q1 为什么在TCP协议里,建立连接是三次握手,而关闭连接却是四次握手呢?
A1 因为当处于LISTEN 状态的服务器端SOCKET当收到SYN报文(客户端希望新建一个TCP连接)后,它可以把ACK(应答作用)和SYN(同步作用)放在同一个报文里来发送给客户端
但在关闭TCP连接时,当收到对方的FIN报文时,对方仅仅表示对方没有数据发送给你了,但未必你的所有数据都已经全部发送给了对方,所以你大可不必马上关闭SOCKET(发送一个FIN报文),等你发送完剩余的数据给对方之后,再发送FIN报文给对方来表示你同意现在关闭连接了,所以通常情况下,这里的ACK报文和FIN报文都是分开发送的。

Q2 为什么TIME_WAIT 状态还需要等2*MSL秒之后才能返回到CLOSED 状态呢?
A2 因为虽然双方都同意关闭连接了,而且握手的4个报文也都发送完毕,按理可以直接回到CLOSED 状态(就好比从SYN_ SENT 状态到ESTABLISH 状态那样)
但是我们必须假想网络是不可靠的,你无法保证你最后发送的ACK报文一定会被对方收到,就是说对方处于LAST_ ACK 状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT 状态的作用就是用来重发可能丢失的ACK报文。

Q3 关闭TCP连接一定需要4次挥手吗?
A3 不一定,4次挥手关闭TCP连接是最安全的做法。但在有些时候,我们不喜欢TIME_ WAIT 状态(如当MSL数值设置过大导致服务器端有太多TIME_ WAIT状态的TCP连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时我们可以通过设置SOCKET变量的SO_ LINGER标志避免SOCKET在close()之后进入TIME_ WAIT状态,这时将通过发送RST(reset重置))强制终止TCP连接(取代正常的TCP四次握手的终止方式)。但这并不是一个很好的主意,TIME_WAIT 对于我们来说往往是有利的。

##RAW socket
1、raw socket与流socket、数据报socket的区别
TCP/UDP类型的套接字只能够访问传输层以及传输层以上的数据,因为当IP层把数据传递给传输层时,下层的数据包头已经被丢掉了
原始套接字却可以访问传输层以下的数据,所以使用 raw套接字你可以实现上至应用层的数据操作,也可以实现下至链路层的数据操作.
参考:Raw Socket和Socket编程

##套接字同步异步
套接字最早是Unix的,window是借鉴过来的。TCP/IP协议族提供三种套接字:流式、数据报式、原始套接字。其中原始套接字允许对底层协议直接访问,一般用于检验
新协议或者新设备问题,很少使用。
套接字编程原理:延续文件作用思想,打开-读写-关闭的模式。
C/S编程模式如下:
Ø 服务器端:
打开通信通道,告诉本地机器,愿意在该通道上接受客户请求——监听,等待客户请求——接受请求,创建专用链接进行读写——处理完毕,关闭专用链接——关闭通信通道(当然其中监听到关闭专用链接可以重复循环)
Ø 客户端:打开通信通道,连接服务器——数据交互——关闭信道。
Socket通信方式
Ø 同步:客户端在发送请求之后必须等到服务器回应之后才可以发送下一条请求。串行运行
Ø 异步:客户端请求之后,不必等到服务器回应之后就可以发送下一条请求。并行运行
套接字模式
Ø 阻塞:执行此套接字调用时,所有调用函数只有在得到返回结果之后才会返回。在调用结果返回之前,当前进程会被挂起。即此套接字一直被阻塞在网络调用上。
Ø 非阻塞:执行此套接字调用时,调用函数即使得不到得到返回结果也会返回。
套接字工作步骤:
Ø 服务器监听:监听时服务器端套接字并不定位具体客户端套接字,而是处于等待链接的状态,实时监控网络状态
Ø 客户端链接:客户端发出链接请求,要连接的目标是服务器端的套接字。为此客户端套接字必须描述服务器端套接字的服务器地址与端口号。
Ø 链接确认:是指服务器端套接字监听到客户端套接字的链接请求时,它响应客户端链接请求,建立一个新的线程,把服务器端套接字的描述发送给客户端,一旦客户端确认此描述,则链接建立好。而服务器端的套接字继续处于监听状态,继续接受其他客户端套接字请求。
在TCP/IP网络中,IP网络交互分类两大类:面向连接的交互与面向无连接的交互。

##非阻塞connect详解
1、connect阻塞的原因
connect函数需要一直等到客户接收到对于自己的SYN的ACK为止才返回,这意味着每个connect函数总会阻塞其调用进程至少一个到服务器的RTT时间,而RTT波动范围很大,从局域网的几个毫秒到几百个毫秒甚至广域网上的几秒,这会导致比较长时间的阻塞。

2、设置非阻塞connect
设置非阻塞IO有两种方法:APUE P355
调用open时,指定O_NONBLOCK标志
调用fcntl来设置一个已打开的描述符
使用fcntl来设置非阻塞IO

int flags;  
if((flags = fcntl(fd, F_GETFL)) < 0) //获取当前的flags标志  
  err_sys(“F_GETFL error!”);
  
flags |= O_NONBLOCK; //修改非阻塞标志位
  
if(fcntl(fd, F_SETFL, flags) < 0)  //设置非阻塞标志  
  err_sys(“F_SETFL error!”);

3、connect函数
对于阻塞式套接字,调用connect函数将激发TCP的三次握手过程,而且仅在连接建立成功或者出错时才返回;
对于非阻塞式套接字,如果调用connect函数会之间返回-1(表示出错),且错误为EINPROGRESS,表示连接建立,建立启动但是尚未完成;如果返回0,则表示连接已经建立,这通常是在服务器和客户在同一台主机上时发生。
这里写图片描述

n = connect(fd, (struct sockaddr*)&sa, sizeof(sa));
if (n == -1)	//连接出错
{  
	if (errno == EINPROGRESS) 
	{  
		//连接正在建立...	调用select来检查这个链接是否建立成功	
	}
	else
	{
		return -1;
	}
}
else if(n == 0)  //连接已建立(服务器和客户端在同一台机器上时就有可能发生这种情况)
	goto done;

4、使用select来判断文件描述符何时可写
调用select查询,直到需要查询的描述符已准备好IO时,返回。另外可以设置查询等待的超时时间。

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval* timeout); 
  1. 利用select实现非阻塞式connect
    分以下几步:
    (1) 创建socket,并利用fcntl将其设置为非阻塞
    (2) 调用connect函数,如果返回0,则连接建立;如果返回-1,检查errno ,如果值为 EINPROGRESS,则连接正在建立。
    (3) 为了控制连接建立时间,将该socket描述符加入到select的可写集合中,采用select函数设定超时。
    (4) 如果规定时间内成功建立,则描述符变为可写;否则,采用getsockopt函数捕获错误信息
    (5) 恢复套接字的文件状态并返回(将socket设置为阻塞模式)。
    【代码】
	bool ret = false;
	if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
	{
	    tm.tv_set = TIME_OUT_TIME;
	    tm.tv_uset = 0;
	    FD_ZERO(&set);
	    FD_SET(sockfd, &set);

		//sockfd+1表示select搜索的范围(最大描述符+1)
	    if( select(sockfd+1, NULL, &set, NULL, &tm) > 0)
	    {
	        getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, (socklen_t *)&len);
	        if(error == 0) 
		        ret = true;
	        else 
		        ret = false;
	    } 
	    else 
		    ret = false;
	}
	else 
		ret = true;
		
	ul = 0;
	ioctl(sockfd, FIONBIO, &ul); //设置为阻塞模式 

http://blog.csdn.net/yuanzhangmei1/article/details/8269685
http://blog.chinaunix.net/uid-16792259-id-3064785.html
(必考必问,提示:设置非阻塞,返回之后用select检测状态)

6、解决connect调用失败
APUE P450
如果客户进程调用connect失败,则进程休眠一小段时间再尝试,循环每次增加一点休眠时间。

##socket服务端的实现
创建套接字->绑定套接字->监听->连接(此连接是又客服端发起的,此时会进行三次握手协议)->accept

	//创建(面向连接的)套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    //设置服务器端的地址结构
    memset(&addr_serv, 0, sizeof(addr_serv));
    addr_serv.sin_family = AF_INET;
    addr_serv.sin_port = htons(SOURCE_PORT);
    addr_serv.sin_addr.s_addr = htonl(INADDR_ANY);
    //套接字绑定到服务器地址结构上
	bind(sockfd, (struct sockaddr*)&addr_serv, sizeof(addr_serv));
    //监听
	listen(sockfd, SRV_LISTEN_LIST_LEN)
	//连接
	/*
	*   当一个客户端的连接请求到达服务器主机侦听的端口时
	*   此时客户端的连接会在队列中等待
	*   直到使用服务器处理接收请求
	*/
	conn_fd = accept(sockfd, (struct sockaddr *)&addr_client, &client_addr_len);

    //循环接收发送数据
    while(1)
    {
		printf("starting receive....\n");
		recv_num = recv(conn_fd, recv_buf, BUFFER_LEN, 0);
		
		recv_buf[recv_num] = '\0';
		printf("receive is :%s\n",recv_buf);
		
		snprintf(send_buf, BUFFER_LEN, "receive %d bytes data!\n",recv_num);
		send(conn_fd, send_buf, BUFFER_LEN, 0);
    }
	close(conn_fd);

##调用shutdown关闭套接字中的读、写端

int shutdown(int sockfd, int how);

how如果是:
SHUT_RD,则关闭sockfd的读端
SHUT_WR,则关闭写端
SHUT_RDWR,读写端都被关闭

##accept函数是在三次握手哪一个环节调用
第一次握手:客户端发送syn包(syn=j)到服务器。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个ASK包(ask=k)。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1)。
三次握手完成后,客户端和服务器就建立了tcp连接。这时可以调用accept函数获得此连接。
3次握手发生在客户端 connect 时 而服务器accept 只是从内核已完成握手的队列可以中取出一个

##select详解

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。
调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fd_set,来找到就绪的描述符。
select的一 个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但 是这样也会造成效率的降低。

##epoll详解
1、建立一个epoll对象

int epoll_create(int size);  
//size是内核保证能够正确处理的最大句柄数
//返回是epoll描述符,-1表示创建失败

创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭创建的fd,否则可能导致fd被耗尽。
2、操作epoll对象

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 

它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
epoll_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把 epoll正在监控的某个socket句柄移出epoll,不再监控它等等。
epfd: epoll_create()的返回值。
op: 第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
fd: 需要监听的fd。
event: 监听的事件,告诉内核需要监听什么事件,struct epoll_event结构如下:

	//感兴趣的事件和被触发的事件  
	struct epoll_event {  
	    __uint32_t events; /* Epoll events */  
	    epoll_data_t data; /* User data variable */  
	}; 
	typedef union epoll_data {  //联合体
	    void *ptr;  
	    int fd;  
	    __uint32_t u32;  
	    __uint64_t u64;  
	} epoll_data_t;   

events可以是以下几个宏的集合:
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT: 表示对应的文件描述符可以写
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR: 表示对应的文件描述符发生错误;
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

3、收集监控事件

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
//收集在epoll监控的事件中已经发送的事件
//在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。

epfd: epoll_create()的返回值。
events: 分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)
maxevents:告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size
timeout: 是超时时间(毫秒,0会立即返回,-1永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。

在 select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描。而epoll事先通过epoll_ ctl()来注册一 个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知

##使用select或epoll的原因
当需要读两个以上的I/O的时候,如果使用阻塞式的I/O,那么可能长时间的阻塞在一个描述符上面,另外的描述符虽然有数据但是不能读出来,这样实时性不能满足要求,大概的解决方案有以下几种:
1.使用多进程或者多线程,但是这种方法会造成程序的复杂,而且对与进程与线程的创建维护也需要很多的开销。(Apache服务器是用的子进程的方式,优点可以隔离用户)
2.用一个进程,但是使用非阻塞的I/O读取数据,当一个I/O不可读的时候立刻返回,检查下一个是否可读,这种形式的循环为轮询(polling),这种方法比较浪费CPU时间,因为大多数时间是不可读,但是仍花费时间不断反复执行read系统调用。
3.异步I/O(asynchronous I/O),当一个描述符准备好的时候用一个信号告诉进程,但是由于信号个数有限,多个描述符时不适用。
4.一种较好的方式为I/O多路转接(I/O multiplexing)先构造一张有关描述符的列表(epoll中为队列),然后调用一个函数,直到这些描述符中的一个准备好时才返回,返回时告诉进程哪些I/O就绪。select和epoll这两个机制都是多路I/O机制的解决方案,select为POSIX标准中的,而epoll为Linux所特有的。

##select和epoll的区别(必问)
1.select的句柄数目受限,在linux/posix_ types.h头文件有这样的声明:

#define  __FD_SETSIZE 1024

表示select最多同时监听1024个fd。而epoll没有,它的限制是最大的打开文件句柄数目。
2.epoll的最大好处是不会随着fd的数目增长而降低效率
selec中采用轮询处理,其中的数据结构类似一个数组的数据结构,epoll是通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数,通过调用epoll_wait()来获取状态。
epoll只会对"活跃"的socket进行操作—这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数(把这个句柄加入队列),其他idle状态句柄则不会,在这点上,epoll实现了一个"伪"AIO。但是如果绝大部分的I/O都是“活跃的”,每个I/O端口使用率很高的话,epoll效率不一定比select高(可能是要维护队列复杂)。
3.使用mmap加速内核与用户空间的消息传递。无论是select,poll还是epoll都需要内核把fd消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。

##epoll优于select的地方
【IO多路复用】
高并发编程方法当然就是把两个阶段分开处理。即,等待消息准备好的代码段,与处理消息的代码段是分离的。当然,这也要求套接字必须是非阻塞的,否则,处理消息的代码段很容易导致条件不满足时,所在线程又进入了睡眠等待阶段。那么问题来了,等待消息准备好这个阶段怎么实现?它毕竟还是等待,这意味着线程还是要睡眠的!解决办法就是,线程主动查询,或者让1个线程为所有连接而等待!
这就是IO多路复用了。多路复用就是处理等待消息准备好这件事的,但它可以同时处理多个连接!它也可能“等待”,所以它也会导致线程睡眠,然而这不要紧,因为它一对多、它可以监控所有连接。这样,当我们的线程被唤醒执行时,就一定是有一些连接准备好被我们的代码执行了,这是有效率的!没有那么多个线程都在争抢处理“等待消息准备好”阶段,整个世界终于清净了!
【简单的谈下epoll为何会替代select】
前面提到过,高并发的核心解决方案是1个线程处理所有连接的“等待消息准备好”,这一点上epoll和select是无争议的。但select预估错误了一件事,就像我们开篇所说,当数十万并发连接存在时,可能每一毫秒只有数百个活跃的连接,同时其余数十万连接在这一毫秒是非活跃的。select的使用方法是这样的:
返回的活跃连接 select(全部待监控的连接)
什么时候会调用select方法呢?在你认为需要找出有报文到达的活跃连接时,就应该调用。所以,调用select在高并发时是会被频繁调用的。这样,这个频繁调用的方法就很有必要看看它是否有效率,因为,它的轻微效率损失都会被“频繁”二字所放大。它有效率损失吗?显而易见,全部待监控连接是数以十万计的,返回的只是数百个活跃连接,这本身就是无效率的表现。被放大后就会发现,处理并发上万个连接时,select就完全力不从心了。
再来说说epoll是如何解决的。它很聪明的用了3个方法来实现select方法要做的事:
新建的epoll描述符
epoll_create()
epoll_ctrl(epoll描述符,添加或者删除所有待监控的连接)
返回的活跃连接 ==epoll_wait( epoll描述符 )
这么做的好处主要是:分清了频繁调用和不频繁调用的操作。
例如,epoll_ ctrl是不太频繁调用的,而epoll_ wait是非常频繁调用的。这时,epoll_wait却几乎没有入参,这比select的效率高出一大截,而且,它也不会随着并发连接的增加使得入参越发多起来,导致内核执行效率下降。
epoll是怎么实现的呢?其实很简单,从这3个方法就可以看出,它比select聪明的避免了每次频繁调用“哪些连接已经处在消息准备好阶段”的epoll_ wait时,是不需要把所有待监控连接传入的。这意味着,它在内核态维护了一个数据结构保存着所有待监控的连接。这个数据结构就是一棵红黑树,它的结点的增加、减少是通过epoll_ctrl来完成的。

参考:高性能网络编程5–IO复用与并发编程

##epoll哪些触发模式,有啥区别?
(必须非常详尽的解释水平触发和边缘触发的区别,以及边缘触发在编程中要做哪些更多的确认)
LT(level-triggered),水平触发,是缺省的工作方式,并且同时支持block(阻塞)和no-block(非阻塞) socket
在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的。所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

ET(edge-triggered),边缘触发,是高速工作方式,只支持no-block(非阻塞) socket
在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认(这句话不理解)。
简言之
水平触发 level –> 有事了,你不处理?不断骚扰你直到你处理….
边缘触发 edge –> 有事了,告诉你一次,你不处理?拉倒!
LT(水平触发)模式下,默认不可读,只有epoll通知你可读才是可读,否则不可读
ET(边沿触发)模式下,**默认可读。你可以随便读,直到发生EAGAIN。**可读时读和不读,怎么读都由你自己决定,中间epoll不管。 EAGAIN后不可读了,等到再次可读,epoll会再通知一次。

【何时使用epoll】
当有大量的idle -connection(空闲连接)或者dead-connection时,可用。

##linux下epoll实现高效处理百万句柄
http://blog.csdn.net/russell_tao/article/details/7160071

大规模连接上来,并发模型怎么设计
tcp结束连接怎么握手,time_wait状态是什么,为什么会有time_wait状态?哪一方会有time_wait状态,如何避免time_wait状态占用资源(必须回答的详细)
tcp头多少字节?哪些字段?(必问)
参考:IP头、TCP头、UDP头详解以及定义

面向连接的socket数据处理过程

对于面向连接的socket类型(SOCK_STREAM,SOCK_SEQPACKET)在读写数据之前必须建立连接。
首先服务器端socket必须在一个客户端知道的地址进行监听,也就是创建socket之后必须调用bind绑定到一个指定的地址,然后调用
int listen(int sockfd, int backlog);进行监听。
此时服务器socket允许客户端进行连接,backlog提示没被accept的客户连接请求队列的大小,系统决定实际的值,最大值定义为SOMAXCONN在头文件socket.h>里面。如果某种原因导致服务器端进程未及时accpet客户连接而导致此队列满了的话则新的客户端连接请求被拒绝(在工作中遇到过此情况,IONA ORBIX(CORBA中间件),由于没有配置超时时间结果在WIFI网络中传输数据出现异常情况一直阻塞而无机会调用accept接受新的客户请求,于是最终队列满导致新的客户连接被拒绝)。
  调用listen之后,当有客户端连接到达的时候调用

int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);
//返回用于连接数据传送的socket描述符

接受客户端连接建立起连接返回用于连接数据传送的socket描述符,进行监听的socket可以用于继续监听客户端的连接请求,返回的socket描述符跟监听的socket类型一致。如果addr不为NULL,则客户端发起连接请求的socket地址信息会通过addr进行返回。
  如果监听的socket描述符为阻塞模式,则accept一直会阻塞直到有客户发起连接请求
  如果监听的socket描述符为非阻塞模式,则如果当前没有可用的客户连接请求,则返回-1(errno设置为EAGAIN)。
  可以使用select函数对监听的socket描述符进行多路分离,如果有客户连接请求则select将监听的socket描述符设置为可读(注意,如果监听的socket为阻塞模式而使用select进行多路分离则可能造成select返回可读但是调用accept会被阻塞住的情况,原因是在调用accept之前客户端可能主动关闭连接或者发送RST异常关闭连接,因此select最好跟非阻塞socket搭配使用)。
  http://blog.chinaunix.net/uid-15014334-id-3341061.html

如果select返回可读,结果只读到0字节,什么情况?
keepalive 是什么东东?如何使用?
列举你所知道的tcp选项,并说明其作用。
socket什么情况下可读?

##两台主机A,B之间进行通信(不同网段)
一、主机A在应用层上的操作
1.首先我们应该知道:计算机区分各种不同的应用程序和服务就是依靠端口号进行的。而不论主机或服务器运行什么操作系统,只要其使用TCP/IP协议,各种服务端口号是一样的。同时为了让对方能给我们回信息,我们也需要有一个接口来接收信息。这个端口不能和已存在的系统服务端口冲突。
2.通过读取用户输入的url地址,通过dns转换为ip地址,即目标的ip地址。

二、主机A在传输层的操作
1、首先对上层发来的数据做分段。其分段的原因有三个:
a.一个大文件的数据流,如果被封装成一个巨大的数据包和数据帧,当在网络上传输的时候,其他网络应用就无法进行了。必须等到该数据帧传送完毕。
b.一个数据包在网络上传递,经常面临各种原因造成的错误,比如线路受到磁场干扰。如果此时数据包错误则还要重传。无法忍受。
c.各种网络传输介质及网络设备都有最大传输单元的限制,不允许在网络上出现巨大的包。综上所述要给数据流分段,即每个分段称为segment
2、在本例中,分段完后要添加控制信息。在传输层有两个数据传输协议,一个是TCP另一个是UDP,一般网上邻居传数据时,用的是TCP。此层为每一个SEGMENT加上一个TCP头。其中最主要的就是加上控制信息,源端口,目的端口和顺序号。其中源端口和目的端口表示数据是由主机A哪个协议或应用程序发出的,送到哪个应用程序及协议。顺序号是该数据段在整个数据流中的位置。
3、但是此层无法封装数据段为哪台主机发出送往哪台主机于是用到下一层。

三、 主机A在网络层的操作
IP地址做为逻辑地址,虽不能表示网络设备的实际位置,但它能逻辑的标明远程设备,从而解决物理地址无法表示远程设备的弊病。通过此层添加了IP头,源IP,目的IP,将数据封装成PACKET。这样主机B收到PACKET后就知道是哪台主机发来的了。

四、主机A在网络接口层的操作
他在上层IP包的基础上,加上源MAC地址、目的MAC地址和采用协议的信息。因为A、B不在同一网段,主机A不可能通过ARP解析到B的MAC地址。主机A必须依靠网络中的路由器来所数据包路由到目的网络。所以,APR将该数据包发往自己ARP缓存中的网关的MAC地址,此网关可以是路由器的接口也可以是代理服务器的某网卡的IP,总之谁能为主机访问其他网络提供可能,默认网关的IP就写谁。本地将查询ARP表中的网关的MAC地址进行封装并发送。
以上的数据最后变成数据帧被传到物理层的线缆上。变成电信号。交给交换机A进行处理。此时主机A的数据传递工作完毕。

在网络中,数据报到达路由器后的处理过程如下:
路由器通过mac头中的信息,可以看到L3层协议是ip协议,然后去掉mac头,检查目的ip地址是否是本地ip地址,如果不是则根据路由表,找到下一跳地址,然后重新打上mac头,发往下一跳.根据连接方式的不同,此时重新封装的mac头可能不同。在Internet网上,数据包有一定的生命期,如果在此期间不能找到主机B的话,数据包将丢弃。

一、主机B在网络接口层的操作
首先核实收到的数据帧的MAC是不是自己的,并了解该帧是来自于哪里。如果是,就拆卸该帧头从而得到数据包,向上层传送。
二、主机B在网络层的操作
核实数据包的源IP地址和目的IP地址,确认后并了解来自于哪个逻辑结点,拆掉包头,得到数据段。
三、主机B在传输层的操作
会对所收到的数据段进行确认,即向主机A发送确认信息。同时会拆掉TCP头,并按每个数据段的顺序号将分段组成数据流(如果顺序号不对,则要求对方重发)最后将数据流发送到TCP头中指定的目的端口。由那个端口的应用程序或协议来处理。
由于TCP报文段被封装成IP数据报来传输,而 IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
四、主机B在应用层的操作
按照应用层的相关协议读取数据。

本例中是通过TCP数据包传输的,TCP 是面向连接的运输层协议。每一条 TCP 连接只能有两个端点(endpoint),每一条 TCP 连接只能是点对点的(一对一)。
当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
TCP将保持首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的有无变化,若收到的段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段(希望发送端超时并重传)。
TCP报文段被封装成IP数据报来传输,而 IP数据报的到达可能会失序,因此TCP报文段的到达也可能会失序。如果必要,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
由于IP数据报会发生重复,TCP的接收端必须丢弃重复的数据。
运输连接的管理就是使运输连接的建立和释放都能正常地进行。运输连接就有三个阶段,即:连接建立、数据传送和连接释放。建立连接通过三次握手,而释放链接是通过四次握手。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值