Socket 起源于 Unix,而Unix基本哲学之一就是一切皆文件,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符
Socket三次握手及四次挥手过程:
三次握手过程:
(1)第一次握手:客户端向服务器发送SYN包,假设序列号为x,进入SYN_SEND状态。
(2)第二次握手:服务器收到SYN包,进行确认,发送ACK报文,序列号为x+1,同时自己也发送一个SYN包(假设序列号为y),此时服务器进入SYN_RECV状态。
(3)第三次握手:客户端收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(确认号为y+1),客户端和服务器进入ESTABLISHED状态,完成三次握手。
四次挥手过程:
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。
(3)然后服务器B也会发送一个FIN给客户端A。
(4)客户端A发回ACK报文确认,确认序号为收到序号加1。
主动关闭连接的一方收到FIN后会进入TIME_WAIT状态。这样设计主要有两个原因:
(1)可靠地实现TCP连接的终止。四次挥手过程中,如果客户端最终发送的ACK丢失,服务器会重发FIN,通过保持TIME_WAIT状态可以允许它重发ACK,保证TCP连接正常地终止。
(2)让旧连接的重复分组在网络中消失。因为如果复用旧连接的端口马上开启一个新的连接,原来旧连接中的数据包可能会被这个新连接收到。处于TIME_WAIT状态的连接就可以避免这种情况。它会持续2个最大分解生命期(MSL),也就是一个IP数据包能在网络中生存的最长时间,保证新连接建立的时候,旧连接的数据包已经在网络中消失了。
接口说明:
sockaddr_in
socket分为服务器端和客户端,服务器端监听端口发来的请求,收到后向客户端发送一个Hello World,客户机负责发送消息并打印收到的Hello World.
服务器步骤:建立socket,绑定socket和地址信息,开启监听,收到请求后发送数据。
客户端步骤:建立socket,连接服务器端,接收并打印服务器给的数据
服务器:
客户端:
sockaddr_un
进程间通信的另一种方式是使用UNIX套接字,人们在使用这种方式时往往用的不是网络套接字,而是一种称为本地套接字的方式。这样做可以避免为黑客留下后门。socket接口中的协议族参数决定了使用的地址类型,AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合,而AF_UNIX决定要用一个绝对路径名作为地址。
服务端:
客户端:
socket分为阻塞和非阻塞两种。阻塞式的socket的recv服从这样的规则:当缓冲区内有数据时,立即返回所有的数据;当缓冲区内无数据时,阻塞直到缓冲区中有数据。非阻塞式的socket的recv服从的规则则是:当缓冲区内有数据时,立即返回所有的数据;当缓冲区内无数据时,产生EAGAIN的错误并返回(在Python中会抛出一个异常)。两种情况都不会返回空字符串,返回空数据的结果是对方关闭了连接之后才会出现的。下面的示例中会添加set_nonblock(fcntl函数)实现非阻塞socket。
如何解决服务器多并发问题?
1、每个进程处理一个客户端
2、每个线程处理一个客户端
3、io复用模型
select、poll、epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。io复用模型只需要一个进程就够了,之所以能够同时处理多个客户端的请求,原因是可以查询哪个客户端准备好了,对于准备好的客户端(例如客户端已经发了信息过来,本服务器用read读取数据的时候不会阻塞;另外,客户端已经关闭了连接,那么本服务read的时候,返回0,也不会阻塞),则和它进行通信,而未准备好的,就暂时先不理会。
1,select 实现
select的几大缺点:
(1)每次调用select都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
2, poll 实现
poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。
3、epoll 实现
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生:
-
调用epoll_create建立一个epoll对象(在epoll文件系统中给这个句柄分配资源);
-
调用epoll_ctl向epoll对象中添加这所有连接的套接字;
-
调用epoll_wait收集发生事件的连接。
epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?
对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果)。
对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max 查看,一般来说这个数目和系统内存关系很大。
总结:epoll采用事件触发的方式,当某个fd准备好后会触发事件,这样减少了内核的轮询。同时,epoll返回的是那些准备好的fd,避免程序员进行全部的轮询。另外,select的fd数上限一般是1024,但是epoll没有上限。
epoll的水平触发和边沿触发?
水平触发:epoll的事件默认情况下是水平触发。例如,只要客户端的数据仍然未读完,那么事件就会一直发生。告诉服务器,请将数据读出来。
边沿触发:加上了events那里加了EPOLLET这个选项后,变成边沿触发。也就是数据可读,则只触发一次事件,服务器必须一直读,直到把数据读完。
服务端:
客户端:
ps:
O_NONBLOCK和O_NDELAY所产生的结果都是使I/O变成非阻塞模式(non-blocking),在读取不到数据或是写入缓冲区已满会马上return,而不会阻塞等待。
它们的差别在于:在读操作时,如果读不到数据,O_NDELAY会使I/O函数马上返回0,但这又衍生出一个问题,因为读取到文件末尾(EOF)时返回的也是0,这样无法区分是哪种情况。因此,O_NONBLOCK就产生出来,它在读取不到数据时会回传-1,并且设置errno为EAGAIN。
socket编程读写文件recv()、send()函数返回值分析
send,sendto以及sendmsg系统调用用于发送消息到另一个套接字。send函数在套接字处于连接状态时方可使用。而sendto和sendmsg在任何时候都可使用 。
下面的代码片段演示了send,sendto,sendmsg函数的用法: