插口,也就是socket,从事IT的地球人都知道,既然谈到了socket,也来简单介绍一下socket的由来。
80年代初,美国国防部高级研究计划署ARPA让California大学在UNIX操作系统下实现TCP/IP协议,Berkley提出了为UNIX操作系统开发的网络通信接口Socket,它是建立在传输层协议(主要是TCP和IP)上的一种套接字规范,因此人们也将Socket接口称为BerkeleySocket。Socket概念最早出现于1983年的4.2BSD版本中,它的主要目的是提供一个统一的访问网络和进程间通信协议的接口。
Socket接口定义了许多函数例程,可以用它们来开发TCP/IP网络上的应用程序。它定义了两台计算机间的通信规范。如果两台计算机是利用一个“通道”进行通信,那么这个“通道”的两端就是套接字。Socket屏蔽了底层通信软件和具体操作系统的差异,使得任何两台安装了TCP协议软件和实现了Socket规范的计算机之间的通信成为可能。
插口代表一条通信链路的一端,存储或指向与链路有关的所有信息。这些信息包括:使用的协议、协议的状态信息(包括源和目的地址)、到达的连接队列、数据缓存和可选标志。
80年代初,美国国防部高级研究计划署ARPA让California大学在UNIX操作系统下实现TCP/IP协议,Berkley提出了为UNIX操作系统开发的网络通信接口Socket,它是建立在传输层协议(主要是TCP和IP)上的一种套接字规范,因此人们也将Socket接口称为BerkeleySocket。Socket概念最早出现于1983年的4.2BSD版本中,它的主要目的是提供一个统一的访问网络和进程间通信协议的接口。
Socket接口定义了许多函数例程,可以用它们来开发TCP/IP网络上的应用程序。它定义了两台计算机间的通信规范。如果两台计算机是利用一个“通道”进行通信,那么这个“通道”的两端就是套接字。Socket屏蔽了底层通信软件和具体操作系统的差异,使得任何两台安装了TCP协议软件和实现了Socket规范的计算机之间的通信成为可能。
插口代表一条通信链路的一端,存储或指向与链路有关的所有信息。这些信息包括:使用的协议、协议的状态信息(包括源和目的地址)、到达的连接队列、数据缓存和可选标志。
struct socket {
short so_type; /* generic type, see socket.h*/
short so_options; /* from socket call, see socket.h*/
short so_linger; /* time to linger while closing*/
short so_state; /* internal state flags SS_*, below*/
caddr_t so_pcb; /* protocol control block*/
struct protosw *so_proto; /* protocol handle*/
/** Variables for connection queueing.
* Socket where accepts occur is so_head in all subsidiary sockets.
* If so_head is 0, socket is not related to an accept.
* For head socket so_q0 queues partially completed connections,
* while so_q is a queue of connections ready to be accepted.
* If a connection is aborted and it has so_head set, then
* it has to be pulled out of either so_q0 or so_q.
* We allow connections to queue up based on current queue lengths
* and limit on number of queued connections for this socket.
*/
struct socket *so_head; /* back pointer to accept socket*/
struct socket *so_q0; /* queue of partial connections*/
struct socket *so_q; /* queue of incoming connections*/
short so_q0len; /* partials on so_q0*/
short so_qlen; /* number of connections on so_q*/
short so_qlimit; /* max number queued connections*/
short so_timeo; /* connection timeout*/
u_short so_error; /* error affecting connection*/
pid_t so_pgid; /* pgid for signals*/
u_long so_oobmark; /* chars to oob mark*/
/*
* Variables for socket buffering.
*/
struct sockbuf {
u_long sb_cc; /* actual chars in buffer*/
u_long sb_hiwat; /* max actual char count*/
u_long sb_mbcnt; /* chars of mbufs used*/
u_long sb_mbmax; /* max chars of mbufs to use*/
long sb_lowat; /* low water mark*/
struct mbuf *sb_mb; /* the mbuf chain*/
struct selinfo sb_sel; /* process selecting read/write*/
short sb_flags; /* flags, see below*/
short sb_timeo; /* timeout for read/write*/
} so_rcv, so_snd;
caddr_t so_tpcb; /* Wisc. protocol control block XXX*/
void (*so_upcall) __P((struct socket *so, caddr_t arg, int waitf));
caddr_t so_upcallarg; /* Arg for above*/
};
short so_type; /* generic type, see socket.h*/
short so_options; /* from socket call, see socket.h*/
short so_linger; /* time to linger while closing*/
short so_state; /* internal state flags SS_*, below*/
caddr_t so_pcb; /* protocol control block*/
struct protosw *so_proto; /* protocol handle*/
/** Variables for connection queueing.
* Socket where accepts occur is so_head in all subsidiary sockets.
* If so_head is 0, socket is not related to an accept.
* For head socket so_q0 queues partially completed connections,
* while so_q is a queue of connections ready to be accepted.
* If a connection is aborted and it has so_head set, then
* it has to be pulled out of either so_q0 or so_q.
* We allow connections to queue up based on current queue lengths
* and limit on number of queued connections for this socket.
*/
struct socket *so_head; /* back pointer to accept socket*/
struct socket *so_q0; /* queue of partial connections*/
struct socket *so_q; /* queue of incoming connections*/
short so_q0len; /* partials on so_q0*/
short so_qlen; /* number of connections on so_q*/
short so_qlimit; /* max number queued connections*/
short so_timeo; /* connection timeout*/
u_short so_error; /* error affecting connection*/
pid_t so_pgid; /* pgid for signals*/
u_long so_oobmark; /* chars to oob mark*/
/*
* Variables for socket buffering.
*/
struct sockbuf {
u_long sb_cc; /* actual chars in buffer*/
u_long sb_hiwat; /* max actual char count*/
u_long sb_mbcnt; /* chars of mbufs used*/
u_long sb_mbmax; /* max chars of mbufs to use*/
long sb_lowat; /* low water mark*/
struct mbuf *sb_mb; /* the mbuf chain*/
struct selinfo sb_sel; /* process selecting read/write*/
short sb_flags; /* flags, see below*/
short sb_timeo; /* timeout for read/write*/
} so_rcv, so_snd;
caddr_t so_tpcb; /* Wisc. protocol control block XXX*/
void (*so_upcall) __P((struct socket *so, caddr_t arg, int waitf));
caddr_t so_upcallarg; /* Arg for above*/
};
so_type由产生插口的进程来指定,它指明插口和相关协议支持的通信语义。对于UDP,so_type等于SOCK_DGRAM,而对于TCP,so_type则等于SOCK_STREAM。
so_options是一组改变插口行为的标志。通过getsockopt和setsockopt系统调用进程能修改除SO_ACCEPTCONN外所有的插口选项。当在插口上发送listen系统调用时,SO_ACCEPTCONN被内核设置。 so_linger等于当关闭一条连接时插口继续发送数据的时间间隔。
so_state表示插口的内部状态和一些其他的特点。有这样两个标志需要重点注意一下:SS_NBIO和SS_ASYNC标志。在默认情况下,进程在发出I/O请求后会等待资源。例如,对一个插口发read系统调用,如果当前没有网络上来的数据,则read系统调用就会被阻塞。同样,当一个进程调用write系统调用时,如果内核中没有缓存来存储发送的数据,则内核将阻塞进程。如果设置了SS_NBIO,在对插口执行I/O操作且请求的资源不能得到时,内核并不阻塞进程,而是返回EWOULDBLOCK。这就是我们在socket编程中通常要遇到的阻塞操作和非阻塞操作。
so_pcb指向协议控制块,协议控制块包含与协议有关的状态信息和插口参数。每一种协议都定义了自己的控制块结构,所以so_pcb被定义成一个通用的指针。
so_proto指向进程在socket系统调用中选择的协议的protosw结构。
so_q0队列中放着还没有完全建立的连接(如TCP的三次握手还没完成),队列的长度为so_q0len。
so_q队列中放着已经建立的连接或将被接受的连接(如TCP的三次握手已完成),队列的长度为so_qlen。
每一个socket都有自己的so_head,在每一个被排队的插口中,so_head指向设置了SO_ACCEPTCONN的源插口。
插口上可排队的连接数通过so_qlimit来控制,进程可以通过listen系统调用来设置so_qlimit。
每一个插口包括两个数据缓存,so_rcv和so_snd,分别用来缓存接收或发送的数据。
so_options是一组改变插口行为的标志。通过getsockopt和setsockopt系统调用进程能修改除SO_ACCEPTCONN外所有的插口选项。当在插口上发送listen系统调用时,SO_ACCEPTCONN被内核设置。 so_linger等于当关闭一条连接时插口继续发送数据的时间间隔。
so_state表示插口的内部状态和一些其他的特点。有这样两个标志需要重点注意一下:SS_NBIO和SS_ASYNC标志。在默认情况下,进程在发出I/O请求后会等待资源。例如,对一个插口发read系统调用,如果当前没有网络上来的数据,则read系统调用就会被阻塞。同样,当一个进程调用write系统调用时,如果内核中没有缓存来存储发送的数据,则内核将阻塞进程。如果设置了SS_NBIO,在对插口执行I/O操作且请求的资源不能得到时,内核并不阻塞进程,而是返回EWOULDBLOCK。这就是我们在socket编程中通常要遇到的阻塞操作和非阻塞操作。
so_pcb指向协议控制块,协议控制块包含与协议有关的状态信息和插口参数。每一种协议都定义了自己的控制块结构,所以so_pcb被定义成一个通用的指针。
so_proto指向进程在socket系统调用中选择的协议的protosw结构。
so_q0队列中放着还没有完全建立的连接(如TCP的三次握手还没完成),队列的长度为so_q0len。
so_q队列中放着已经建立的连接或将被接受的连接(如TCP的三次握手已完成),队列的长度为so_qlen。
每一个socket都有自己的so_head,在每一个被排队的插口中,so_head指向设置了SO_ACCEPTCONN的源插口。
插口上可排队的连接数通过so_qlimit来控制,进程可以通过listen系统调用来设置so_qlimit。
每一个插口包括两个数据缓存,so_rcv和so_snd,分别用来缓存接收或发送的数据。
sb_mb指向mbuf链的第一个mbuf。
sb_cc的值等于存储在mbuf链中的数据字节数。
sb_hiwat和sb_lowat用来调整插口的流控算法。
sb_mbcnt等于分配给缓存中的所有mbuf的存储器数量。
sb_sel是一个用来实现select系统调用的selinfo结构。
sb_flags表示sockbuf的属性。
sb_timeo用来限制一个进程在读写调用中被阻塞的时间,默认值为0,表示进程无限期的等待。
sb_cc的值等于存储在mbuf链中的数据字节数。
sb_hiwat和sb_lowat用来调整插口的流控算法。
sb_mbcnt等于分配给缓存中的所有mbuf的存储器数量。
sb_sel是一个用来实现select系统调用的selinfo结构。
sb_flags表示sockbuf的属性。
sb_timeo用来限制一个进程在读写调用中被阻塞的时间,默认值为0,表示进程无限期的等待。
socket系统调用
socket系统调用产生一个新的插口,并将插口同进程在参数domain、type和protocol中指定的协议联系起来。该函数(如图15-14所示)分配一个新的描述符,用来在后续的系统调用中标识插口,并将描述符返回给进程。
bind系统调用将一个本地的网络运输层地址和插口联系起来。一般来说,作为客户的进程并不关心它的本地地址是什么。在这种情况下,进程在进行通信之前没有必要调用bind;内核会自动为其选择一个本地地址。服务器进程则总是需要绑定到一个已知的地址上。所以,进程在接受连接(TCP)或接收数据报(UDP)之前必须调用bind,因为客户进程需要同已知的地址建立连接或发送数据报到已知的地址。
listen系统调用的功能是通知协议进程准备接收插口上的连接请求。它同时也指定插口上可以排队等待的连接数的门限值。超过门限值时,插口层将拒绝进入的连接请求排队等待。当这种情况出现时,TCP将忽略进入的连接请求。
调用listen后,进程调用accept等待连接请求。accept返回一个新的描述符,指向一个连接到客户的新的插口。原来的插口仍然是未连接的,并准备接收下一个连接。如果name指向一个正确的缓存,accept就会返回对方的地址。
服务器进程调用listen和accept系统调用等待远程进程初始化连接。如果进程想自己初始化一条连接(即客户端),则调用connect。对于面向连接的协议如TCP,connect建立一条与指定的外部地址的连接。如果进程没有调用bind来绑定地址,则内核选择并且隐式地绑定一个地址到插口。对于无连接协议如UDP或ICMP,connect记录外部地址,以便发送数据报时使用。
socket系统调用产生一个新的插口,并将插口同进程在参数domain、type和protocol中指定的协议联系起来。该函数(如图15-14所示)分配一个新的描述符,用来在后续的系统调用中标识插口,并将描述符返回给进程。
bind系统调用将一个本地的网络运输层地址和插口联系起来。一般来说,作为客户的进程并不关心它的本地地址是什么。在这种情况下,进程在进行通信之前没有必要调用bind;内核会自动为其选择一个本地地址。服务器进程则总是需要绑定到一个已知的地址上。所以,进程在接受连接(TCP)或接收数据报(UDP)之前必须调用bind,因为客户进程需要同已知的地址建立连接或发送数据报到已知的地址。
listen系统调用的功能是通知协议进程准备接收插口上的连接请求。它同时也指定插口上可以排队等待的连接数的门限值。超过门限值时,插口层将拒绝进入的连接请求排队等待。当这种情况出现时,TCP将忽略进入的连接请求。
调用listen后,进程调用accept等待连接请求。accept返回一个新的描述符,指向一个连接到客户的新的插口。原来的插口仍然是未连接的,并准备接收下一个连接。如果name指向一个正确的缓存,accept就会返回对方的地址。
服务器进程调用listen和accept系统调用等待远程进程初始化连接。如果进程想自己初始化一条连接(即客户端),则调用connect。对于面向连接的协议如TCP,connect建立一条与指定的外部地址的连接。如果进程没有调用bind来绑定地址,则内核选择并且隐式地绑定一个地址到插口。对于无连接协议如UDP或ICMP,connect记录外部地址,以便发送数据报时使用。