网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。
socket地址
socket地址,表示一个IP地址和端口对(ip,port),它唯一地表示了使用TCP通信的一端。
分为通用socket地址和专用socket地址。
通用socket地址的结构体sockaddr,其定义如下:
#include<bits/socket.h>
struct sockadrr
{
sa_family_t sa_family;
char sa_data[14];
};
sa_family成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。
sa_data 成员用于存放socket地址值,但是,不同的协议族的地址值具有不同的含义和长度,14个字节的sa_data根本无法完全容纳多数据协议族的地址值。因此,Linux定义了下面这个新的通用socket地址结构体
#include<bits/socket.h>
struct sockadrr_storage
{
sa_family_t sa_family;
unsigned long int __ss_align;
char __ss_padding[128-sizeof(__ss_align)];
};
并采用
内存对齐(__ss_align);
专用socket地址结构体,避免通用socket地址需要执行繁琐的位操作。
UNIX本地域协议族使用如下专用socket地址结构体:
#include <sys/un.h>
struct sockaddr_un
{
sa_family_t sin_family; // 地址族:AF_UNX
char sun_path[108]; // 文件路径名
};
创建socket,socket是可读、可写、可控制、可关闭的文件描述符。
#include<sys/types.h>
#inlcude<sys/socket.h>
int socket(int domain, int type, int protocol);
domain 参数告诉系统使用哪个底层协议族,对TCP/IP协议族而言,该参数应该设置为PF_INET ( IPv4)。
type参数指定服务类型,服务类型主要有SOCK_STREAM服务(流服务)和SOCK_UGRAM(数据服务),对于TCP/IP协议族而言,其值取SOCK_STREAM表示传输层使用TCP协议,取SOCK_DGRAM表示传输层使用UDP协议。在Linux2.6.17版本之后,type参数处理服务类型值外,还可是SOCK_NONBLOCK和SOCK_CLOEXEC,分别表示将新建socket设为非阻塞的,以及用fork调用创建子进程时在子进程中关闭该socket。
protocol参数是在前两个参数构成的协议集合下,再选择一个具体的协议。不过这个值通常都是唯一的。几乎在所有情况下,我们都应该把它设置为0,表示默认协议。
调用成功时返回一个socket文件描述符,失败则返回-1 并设置errno。
命名socket
指定地址族中的socket地址。
#include <sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
bind 将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数指定该socket地址长度。
监听socket
需要创建一个存放待处理的客户连接监听列表,才能接受客户端连接
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd指定被监听的socket。
backlog提示内核监听队列的最大长度。一般监听队列中完整连接的上限通常比backlog值略大(backlog+1)
接受连接
系统调用从listen监听队列中接受一个连接
#include <sys/types.h>
#include<sys/socket.h>
int accept(int sockfd, struct sockadrr *addr, socklen_t *addrlen);
sockfd 指执行过listen系统调用的监听socket。
addr用来获取被接受连接的远端socket地址,该socket地址的长度由addrlen参数指出。
accept 成功时返回一个新的连接socket,该socket唯一标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通信。
发起连接
如果服务器通过listen调用来被动接受连接,那么客户端通过系统调用来主动与服务器建立连接
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr,socklen_t addrlen);
sockfd参数由socket系统调用返回一个socket,serv_addr参数是服务器监听的socket地址,addrlen是这个地址长度。
connect成功时返回0,一旦成功建立连接,sockfd就唯一地标识了这个连接,客户端就可以通过读写sockfd来与服务器通信。
关闭连接
关闭一个连接实际上就是关闭该连接对应的socket,这个可以通过如下关闭普通文件描述符的系统调用来完成
#include<unistd.h>
int close(int fd);
fd参数是待关闭的socket,不过,close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1。只有当fd的应用计数为0时才真正关闭连接。多进程程序中,一次fork系统调用默认将是父进程中打开的socket的引用计数加1,因此我们必须在父进程和子进程中都对该socket执行close调用才能将连接关闭。
如果要立即终止连接(而不是引用减1),可以使用如下shutdown系统调用。
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
sockfd参数是待关闭的socket,howto参数决定了shutdown的行为,可取下表中值。
由此可见,shutdown能够分别关闭socket上的读或写,或者都关闭,而close在关闭连接是只能讲socket上的读和写同时关闭。