socket编程进行的是端到端的通信,设置参数也只能是端到端协议之上网络层和传输层的。在网络层,需要指定是IPV4还是IPV6,指定是TCP还是UDP
基于TCP协议的Socket程序函数调用过程。
- TCP要先监听一个端口,一般是先调用bind函数,给这个socket赋予一个IP和端口。
- 然后调用listen函数进行监听。在TCP的状态图里面有一个这个状态,当调用了这个函数之后,服务端就进入了这个状态,这时候客户端就可以发起连接了
- 在内核中为每个socket维护两个队列。一个是已经建立连接的队列,这时候三次握手已经完毕,处于established状态;一个是还没有完全建立连接的队列,这时候还没有完成三次握手,处于syn_rcvd状态。
- 服务端调用accept函数,拿出一个已经完成的链接处理
- 在服务端等待时,客户端可以connect函数发起连接。先在参数中指明要连接的IP和PORT,然后发起三次握手。内核会给客户端分配一个临时端口,一旦握手成功,服务端的accept就会返回另一个socket。
监听的socket和真正用来传数据的socket是两个,一个作为监听socket,一个叫做已连接socket。建立连接之后,双方通过read和write 函数来读写数据, 就像在一个文件流里面写东西。
基于UDP协议的socket函数调用过程
UDP是没有连接的,不需要listen和connect,但是它仍然需要bind函数。UDP是没有维护连接状态的,因而不需要每对连接建立一组socket,而是只要有一个socket 就能够和多个客户端通信,每次通信的时候,都调用sendto 和recvfrom,都可以传入IP地址和端口。
如何让服务端为更多的客户端服务:
-
多进程方式
会将资源都复制一份。 -
多线程方式
在 Linux 下,通过 pthread_create 创建一个线程,也是调用 do_fork。不同的是,虽然新的线程在task 列表会新创建一项,但是很多资源,例如文件描述符列表、进程空间,还是共享的,只不过多了个引用而已。 -
IO复用
- select
由于 Socket 是文件描述符,因而某个线程盯的所有的 Socket,都放在一个文件描述符集合 fd_set 中,这就是项目进度墙,然后调用 select 函数来监听文件描述符集合是否有变化。一旦有变化,就会依次查看每个文件描述符。那些发生变化的文件描述符在 fd_set 对应的位都设为 1,表示 Socket 可读或者可写,从而可以进行读写操作,然后再调用 select,接着盯着下一轮的变化。
问题: 每次文件描述符中有socket发生变化的时候,都需要用轮询的方式查看进度;监视的项目数量又FD_SETSIZE限制。 - epoll
它在内核中的实现不是通过轮询的方式,而是通过注册 callback 函数的方式,当某个文件描述符发送变化的时候,就会主动通知。能完成这件事情的函数叫 epoll,它在内核中的实现不是通过轮询的方式,而是通过注册 callback 函数的方式,当某个文件描述符发送变化的时候,就会主动通知。
- select