一台机器上的进程可以使用套接字和另外一台机器上的进程通信,这样就可以支持分布在网络中的客户/服务器系统。同一台机器上的进程之间也可以使用套接字进行通信。
1.套接字(socket)是一种通信机制,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以跨网络进行。Linux所提供的功能(如打印服务、连接数据库和提供Web页面)和网络工具(如用于远程登录的rlogin和用于文件传输的ftp)通常都是通过套接字来进行通信的。
2.套接字的域:域(协议族)指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET,它指的是Internet网络,许多Linux局域网使用的都是该网络,当然,因特网自身用的也是它。其底层的协议——网际协议(IP)只有一个地址族,它使用一种特定的方式来指定网络中的计算机,即人们常说的IP地址。
3.套接字作为通信的终点,它必须在开始通信之前绑定一个端口。
4.一般情况下,小于1024的端口号都是为系统服务保留的,并且所服务的进程必须具有超级用户权限。
5.因为标准服务都对应标准的端口号,所以计算机之间可以轻松地互连,而不需要首先协商一个正确的端口号。本地服务可以使用非标准的端口地址。
6.流套接字(在某些方面类似于标准的输入/输出流)提供的是一个有序、可靠、双向字节流的连接。因此,发送的数据可以确保不会丢失、复制或乱序到达,并且在这一过程中发生的错误也不会显示出来。流套接字由类型SOCK_STREAM指定,它们是在AF_INET域中通过TCP/IP连接实现的。它们也是 AF_UNIX域中常用的套接字类型。
7.数据报套接字:与流套接字相反,由类型SOCK_DGRAM指定的数据报套接字不建立和维持一个连接。它对可以发送的数据报的长度有限制。数据报作为一个单独的网络消息被传输,它可能会丢失、复制或乱序到达。数据报套接字是在AF_INET域中通过UDP/IP连接实现的,它提供的是一种无序的不可靠服务 (UDP代表的是用户数据报协议)。
8.创建套接字:
int socket(int domain,int type, int protocol);
创建的套接字是一条通信线路的一个端点。domain参数指定协议族,type参数指定这个套接字的通信类型,protocol参数指定使用的协议。
最常用的套接字域(协议族)是AF_UNIX(用于linux文件系统的本地套接字)和AF_INET(网络套接字)
type:SOCK_STREAM(流套接字tcp/ip)和 SOCK_DGRAM(数据报套接字udp)。
protocol:协议一般由前两个参数决定自动选择,设0表示使用默认协议。
9.套接字地址
套接字地址结构都以一个指定地址类型(套接字域)的成员(如sun_family)开始。
AF_UNIX协议族地址结构:
struct sockaddr_un{
sa_family_t sun_family; /*AF_UNIX*/
char sun_path[];/*套接字文件名*/
};
AF_INET协议族地址结构:
struct sockaddr_in{
short int sin_family;/*AF_INET*/
unsigned short int sin_port /*端口号*/
struct in_addr sin_addr;/*IP*/
};
struct in_addr{
unsigned long int s_addr;
};
一个AF_INET套接字由它的域、IP地址和端口号来完全确定。
10.命名套接字(将套接字文件与地址绑定)
int bind(int socket, const struct sockaddr *address, size_t address_len);
bind系统调用把参数address中的地址分配给与文件描述符socket关联的未命名套接字。地址结构的长度由参数address_len传递。
11.创建套接字队列
为了能够在套接字上接受进入的连接,服务器程序必须创建一个队列来保存未处理的请求。它用listen系统调用来完成这一工作。
int listen (int socket, int backlog);
backlog:队列长度,常用值5
12.接受连接
int accept (int socket, struct sockaddr *address, size_t *address_len);
accept系统调用只有当有客户程序试图连接到由socket参数指定的套接字上时才返回。
accept函数将创建一个新套接字来与该客户进行通信,并且返回新套接字的描述符。新套接字的类型和服务器监听套接字类型是一样的。
连接客户的地址将被放入address参数指向的sockaddr结构中。如果我们不关心客户的地址,也可以将address参数指定为空指针。
如果套接字队列中没有未处理的连接,accept将阻塞(程序将暂停)直到有客户建立连接为止。我们可以通过对套接字文件描述符设置O_NONBLOCK标志来改变这一行为,使用fcntl函数
13.请求连接
int connect (int socket, const struct sockaddr *address, size_t address_len);
参数socket指定的套接字将连接到参数address指定的服务器套接字,address指向的结构的长度由参数address_len指定。
如果连接不能立刻建立,connect调用将阻塞一段不确定的超时时间。一旦这个超时时间到达,连接将被放弃,connect调用失败。
14.可以通过调用close函数来终止服务器和客户上的套接字连接,就如同对底层文件描述符进行关闭一样。应该总是在连接的两端都关闭套接字。
15.不同的计算机处理器使用不同的字节序来表示整数,为了使不同类型的计算机可以就通过网络传输的多字节整数的值达成一致,你需要定义一个网络字节序。客户和服务器程序必须在传输之前,将它们的内部整数表示方式转换为网络字节序。它们通过定义在头文件netinet/in.h中的函数来完成这一工作。
#include<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned short int netshort);
将一个网络字节序地址转换为一个点分十进制的ip字符串。
#include <arpa/inet.h>
//将一个网络字节序地址转换为一个点分十进制的ip字符串
char *inet_ntoa(struct in_addr in);
//将一个点分十进制的IP转换成一个长整数型数
in_addr_t inet_addr(const char* strptr);
16.UNIX系统通常以超级服务器的方式来提供多项网络服务。超级服务器程序(因特网守护进程xinetd或inetd)同时监听许多端口地址上的连接。当有客户连接到某项服务时,守护程序就运行相应的服务器。这使得针对各项网络服务的服务器不需要一直运行着,它们可以在需要时启动。
17.select系统调用
经常会遇到需要检查好几个输入的状态才能确定下一步行动的情况,需要不停地扫描输入设备看是否有数据,如果有数据到达就读取它。但这种做法很消耗CPU的时间。select系统调用允许程序同时在多个底层文件描述符上等待输入的到达(或输出的完成)。服务器也可以通过同时在多个打开的套接字上等待请求到来的方法来处理多个客户。
select函数对数据结构fd_set进行操作,它是由打开的文件描述符构成的集合。有一组定义好的宏可以用来控制这些集合:
#inlcude <sys/types.h>
#include <sys/time.h>
void FD_ZERO(fd_set *fdset);//将fdset初始化为空
void FD_CLR(int fd, fd_set *fdset);//在集合中删除fd
void FD_SET(int fd, fd_set *fdset);//在集合中添加fd
void FD_ISSET(int fd, fd_set *fdset);//判断fd是否在fdset中,在,返回非0
int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
struct timeval {
time_t tv_sec;// seconds
long tv_usec;// microseconds
}
select调用用于测试文件描述符集合中,是否有一个文件描述符已处于可读状态或可写状态或错误状态,它将阻塞以等待某个文件描述符进入上述这些状态。
参数nfds指定需要测试的文件描述符数目,测试的描述符范围从0到nfds-1(fd_set结构中可以容纳的文件描述符的最大数目由常量FD_SETSIZE指定。)。3个描述符集合都可以被设为空指针,这表示不执行相应的测试。
三个集合中任一一个有活动(可读可写或有错误条件)函数将返回。若没有活动,函数将等待timeout指定的时间后返回。如果timeout参数是一个空指针并且套接字上也没有任何活动,这个调用将一直阻塞下去。
当select返回时,我们可以用FD_ISSET对描述符进行测试。如果select是因为超时而返回的话,所有描述符集合都将被清空。select调用返回状态发生变化的描述符总数。
18.数据报
当客户需要发送一个短小的查询请求给服务器,并且期望接收到一个短小的响应时,我们一般就使用由UDP提供的服务。如果服务器处理客户请求的时间足够短,服务器就可以通过一次处理一个客户请求的方式来提供服务,从而允许操作系统将客户进入的请求放入队列。这简化了服务器程序的编写。
为了访问由UDP提供的服务,你需要像以前一样使用套接字和close系统调用,但需要用两个数据报专用的系统调用sendto和recvfrom来代替原来使用在套接字上的read和write调用。
int sendto (int sockfd, void *buffer, size_t len, int flag,
struct sockaddr *to, socklen_t tolen);
int recvfrom (int sockfd, void *buffer, size_t len, int flag,
struct sockaddr *from, socklen_t fromlen);
sendto系统调用从buffer缓存区中给使用指定套接字地址的目标服务器发送一个数据报。
recvfrom系统调用在套接字上等待从特定地址到来的数据报,并将它放入buffer缓存区。