网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。Socket本质是编程接口(API),通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同计算机之间的通信。
应用程序通常通过"套接字"向网络发出请求或者应答网络请求,Socket在建立网络连接时使用。连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面。
1、网络进程通信
如何标识网络中的一个进程呢?在本地可以通过进程PID来标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的IP地址可以唯一标识网络中的主机,而传输层的协议和端口可以唯一标识主机中的应用程序(进程)。这样利用三元组(IP地址,协议,端口)就可以标识网络中的进程了。网络中进程之间的通信就利用socket来完成。
2、通信过程
(1)服务器端初始化socket()
(2)绑定端口bind()
(3)监听端口listen()
(4)调用accept()阻塞,等待客户端连接
(5)客户端初始化一个socket()
(6)连接服务器connect()
(7)客户端/服务器端I/O读写
(8)关闭连接close()
3、socket接口函数
3.1 socket()函数。用于创建一个socket套接字描述符,唯一标识一个socket。
int socket(int domain, int type, int protocol)
- 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传输协议。
3.2 bind()函数。把一个地址族中的特定地址赋给socket。例如对应AF_INET就是把一个IPV4地址和端口号组合赋给socket。
int bind(SOCKET socket, const struct sockaddr* address, socklen_t address_len)
函数的三个参数分别为:
- socket:套接字描述符,它是通过socket()函数创建,唯一标识一个socket。bind()函数就是给这个套接字描述符绑定一个名字。
- address:是一个sockaddr结构指针,包含了要结合的IP地址和端口号。
- address_len:address缓冲区的长度。
如果函数执行成功,返回值为0,否则为SOCKET_ERROR。
3.3 listen()、connect()函数
如果是服务器,在调用socket()、bind()之后就会调用listen()来监听这个socket,如果是客户端调用connect()发出连接请求。
int listen(int sockfd, int backlog)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
listen()函数的第一个参数是要监听的socket套接字描述符,第二个参数是socket可以排队的最大连接数。socket()函数创建的socket默认是一个主动类型的,listen()函数将socket变为被动类型的,等待客户的连接请求。
connect()函数的第一个参数是客户端的socket套接字描述符,第二参数是服务器端的socket地址,第三个参数是socket地址的长度。
3.4 accept()函数
TCP客户端调用connect()函数,就会向TCP服务器端发送一个连接请求。TCP服务器端监听到这个请求后,就会调用accept()函数接收请求。
int accept(int sockfd, struct sockaddr* addr, socklen_t addrlen)
accept()函数的第一个参数是服务器端的socket套接字描述符,第二个参数是指向struct sockaddr *的指针,用于返回客户端的协议地址,第三个参数是协议地址的长度。
如果accpet成功,那么返回值是由内核自动生成的一个新的socket套接字描述符,服务器端把这个新的套接字描述符发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
注意:accept()函数的第一个参数是服务器开始调用socket()函数生成的套接字描述符,称为监听socket描述符;而accept()函数返回的是已连接的socket描述符。一个服务器通常仅仅创建一个监听socket描述符,它在该服务器的生命周期内一直存在。内核为服务器进程接收的客户端连接分别创建一个已连接socket描述符,当服务器完成了对某个客户端的服务,相应的已连接socket描述符就被关闭。
3.5 read()、write()等函数
调用网络I/O进行读写操作,有下面几组:
- read()/write()
- recv()/send()
- readv()/writev()
- recvmsg()/sendmsg()
- recvfrom()/sendto()
3.6 close()函数
在服务器端与客户端建立连接之后,会进行一些读写操作,完成了读写操作就要关闭相应的socket描述符。