日常生活中我们无时无刻不在使用socket进行通信,如我们每天打开浏览器浏览网页时,浏览器的进程怎么与web服务器通信的?当你用QQ聊天时,QQ进程怎么与服务器或你好友所在的QQ进程通信?这些都得靠socket。

  • 网络中的进程之间如何通信?

本地可以通过PID来标识主机上的某个进程,但是在网络上这是行不通的,好在TCP/IP协议已经帮我们解决了这个问题。网络层的ip标识某台主机,“协议+端口”唯一标识主机中的应用程序。因此利用三元组(ip,协议,端口)表示网络中的进程,网络中的进程就可以利用这一标识来与其他进程进行通信。目前,基本上所有的网络进程都是采用socket实现通信。

  • socket基本操作

socket是“open-write/read-close”,以TCP为例,通信过程如下:

d000baa1cd11728b45647b06cafcc3cec3fd2c4c

http://c.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=17baf4c7d739b60059c307e588395e4f/d000baa1cd11728b45647b06cafcc3cec3fd2c4c.jpg

(1)socket()函数

int socket(int domain, int type, int protocol);

用于创建一个socket描述符,socket函数的三个参数分别为:

domain:即协议族。常见协议族有:AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

type:指定socket类型。常见的socket类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等

protocol:指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议


当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

(2)bind()函数

该函数把一个地址族中的特定地址赋给socket,例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

sockfd:socket描述字。

addr:指定要绑定给sockfd的协议地址,这个地址根据地址创建socket时的地址协议族的不同而不同。

addrlen:对应地址的长度。

通常服务器在启动时会绑定一个地址(IP地址+端口)提供服务,客户就可以通过它来连接服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。

(3)listen()、connect()、accept()函数

作为一个服务器,在调用socket(),bind()之后就会调用listen()监听客户端发来的请求,客户端这时调用connect()发送连接请求,服务器端调用accept()函数接收请求,这样就建好连接了。之后就是网络的IO读写操作,类同于普通文件的读写I/O操作。

int listen(int sockfd, int backlog);

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);


int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

(4)read()、write()等函数

万事具备只欠东风,至此服务器与客户已经建立好连接了。可以调用网络I/O进行读写操作了,即实现了网咯中不同进程之间的通信!网络I/O操作有下面几组:

read()/write()

recv()/send()

readv()/writev()

recvmsg()/sendmsg()

recvfrom()/sendto()

我推荐使用recvmsg()/sendmsg()函数,这两个函数是最通用的I/O函数,实际上可以把上面的其它函数都替换成这两个函数。

(5)close()函数

在服务器与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述字,好比操作完打开的文件要调用fclose关闭打开的文件。


  • socket中TCP的三次握手建立连接过程

    我们知道TCP建立连接要进行“三次握手”,这三次握手主要调用了socket中如下介个函数:

p_w_picpath

(1)当客户端调用connect请求连接时,向服务器发送SYN J包,这时connect进入阻塞态,

(2)服务器监听到这个连接请求后,调用accept接收这个请求,发送一个SYN K,ACK J+1包给客户端,然后进入阻塞态,

(3)客户端收到后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。


  • socket中TCP的四次握手释放连接过程

p_w_picpath

(1)客户端某个应用进程主动发起close,这时向服务器发送一个FIN M包;

(2)收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;

(3)一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N

(4)接收到这个FIN的源发送端TCP对它进行确认。