Linux/UNIX套接字连接
套接字连接
套接字是一种通信机子,凭借这种机制,客户/服务器系统的开发工作既可以在本地单机上进行,也可以夸网络进行。套接字的创建和使用与管道是有区别的,因为套接字明确地将客户和服务器区分开来。
套接字连接:
首先,服务器应用程序用系统调用socket来创建一个套接字,它是系统分配给该服务器进程的类似文件描述符的资源,它不能与其他进程共享。
接下来,服务器进程会给套接字起个名字。本地套接字的名字是Linux文件系统中的文件名,对于网络套接字它的名字是与客户连接的特定网络有关的服务标识符。这个标识符允许Linux将进入的针对特定端口号的连接转到正确的服务器进程。我们用bind来给套接字命名。然后服务器进程就开始等待客户连接到这个命名的套接字。系统调用listen的作用是,创建一个队列并将其用于存放来自客户的进入连接。服务器通过系统调用accept来接受客户的连接。
服务器调用accept时,它会创建一个与原有的命名套接字不同的新套接字。这个套接字只用于与这个特定的客户进行通信,而命名套接字则被保留下来继续处理来自客户的连接。
基于套接字系统的客户端更简单。客户首先调用socket创建一个未命名套接字,然后将服务器的命名套接字作为一个地址来调用connect与服务器建立连接。
套接字属性
套接字的特性由三个属性确定:域、类型和协议。套接字还用地址作为它的名字。地址的格式随域的不同而不同。每个协议又可以使用一个或多个地址来定义格式。
套接字的域
域指定套接字通信中使用的网络介质。最常见的套接字域是AF_INET,它指的是互联网络。其底层协议——网际协议只有一个地址族,它使用一种特定的方式来指定网络中的计算机,即人们常说的IP地址。
服务器计算机上可能同时有多个服务正在运行。客户可以通过IP端口来指定一台联网机器上的某个特定服务。在系统内部,端口通过分配一个唯一16位的整数来标识,在系统外部,则需要通过IP地址和端口号的组合来确定。套接字作为通信的终点,必须在开始通信之前绑定一个端口。
套接字域还可以是AF_UNIX,即使一台还未联网的计算机上的套接字也可以使用这个域。这个域的底层协议就是文件输入/输出,而他的地址就是绝对路径的文件名。我们的服务器套接字的地址是server_socket。
套接字类型
一个套接字域可能有多种不同的通信方式,而每种通信方式又有不同的特性。AF_UNIX域的套接字没有这样的问题,因为他们提供一个可靠的双向通信路径。
因特网协议提供两种不同的服务:流和数据报
流套接字:
流套接字提供一个有序、可靠、双向字节流的连接。因此,发送的数据可以确保不会丢失、复制或乱序到达,并且在这一过程中发生的错误也不会显示出来。流套接字由类型SOCK_STREAM指定,他们是在AF_INET域中通过TCP/IP连接实现。他们也是AF_UNIX域中最常见的套接字类型。
数据报套接字
由类型SOCK_DGRAM指定的数据报套接字不建立和维持一个连接。它对可以发送的数据报的长度有限制。数据报作为一个单独的网络消息被传输,可能会丢失、复制或乱序到达。
数据报套接字是在AF_INET域中通过UDP/IP连接实现的,它提供的是一种无须的不可靠服务。但从紫云的角度来看,它们的开销比较小,因为不需要维持网络连接。而且因为无需花费时间来建立,所以他们的速度也很快。UDP代表的是数据报协议。
套接字协议
只要底层的传输机制允许不止一个协议来提供要求的套接字类型,就可以为套接字选择一个特定的协议。
创建套接字
socket系统调用创建一个套接字并返回一个描述符,该描述符可以用来访问该套接字。
#include<sys/types.h>
#include<sys/socket.h>
int socket(intdomain, int type, int protocol);
创建的套接字是一条通信线路的一个端点。domain参数指定协议族,type参数指定这个套接字的通信类型,protocol参数指定使用的协议。
最常见的套接字是AF_UNIX和AF_INET。前者用于实现UNIX文件系统的本地套戒指,后者用于网络套接字。
type取值包括SOCK_STREAM和SOCK_DGRAM。
通信所用的协议一般有套接字类型和套接字域决定,通常不需要进行选择。将protocol参数设置为0表示使用默认协议。
socket系统调用返回一个描述符。
套接字地址
每个套接字域都有其自己的格式。
对于AF_UNIX域套接字来说,它的地址由结构sockaddr_un来描述,该结果定义在头文件sys/un.h中。
structsockrrd_un{
sa_family_t sun_family;
char sun_path[];
};
此处sun_family指定地址类型,sun_path为文件名来指定套接字地址。
在AF_INET域中,套接字地址由结构socketaddr_in来指定,该结构定义在文件netinet/in.h中,至少包含以下几个成员:
structsocket_in{
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
};
IP地址结构in_addr定义为
struct in_addr{
unigned long int s_addr;
};
IP地址中的四个字节组成一个32位的值,一个AF_INET套接字由它的域、IP地址和端口号来完全确定。
命名套接字
要想让通过socket调用创建的套接字可以被其他进程使用,服务器就必须给该套接字命名。这样,AF_UNIX套接字就会关联到一个文件系统的路径名。AF_INET套接字结汇关联到一个IP端口。
#include<sys/types.h> /* See NOTES*/
#include <sys/socket.h>
int bind(intsockfd, const struct sockaddr *addr, socklen_t addrlen);
bind系统调用把参数addr中的地址分配给与文件描述符socket关联的未命名套接字。地址结构的长度由参数address_len传递。
bind调用需要将一个特定的地址结构指针转化为指向通用地类型(struct sockaddr *)。
bind调用成功返回0,失败返回-1。
创建套接字队列
为了能够在套接字上接受进入的连接,服务器程序必须创建一个队列来保存未处理的请求。使用listen系统调用来实现这一工作。
#include<sys/types.h> /* See NOTES*/
#include <sys/socket.h>
int listen(int sockfd, int backlog);
在套接字队列中,等待处理的进入连接的个数不能超过backlog这个数字。再往后的连接将被拒绝。
接受连接
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr*addr, socklen_t *addrlen);
一旦服务器程序创建并命名了套接字后,它就可以通过accept系统调用来等待客户建立对该套接字的连接。
accept系统调用只有当客户试图连接到由sockfd参数指定的套接字上时才返回。这里的客户指,在套接字队列中排在第一个的未处理连接。accept函数将创建一个新套接字来和该客户进行通信,并且返回新套接字的描述符。新套接字的类型和服务器监听套接字类型一样。
套接字必须事先由bind调用命名,并且有listen调用给他分配一个连接队列。连接客户的地址将被放入address参数指向的sockaddr结构中。
参数address_len指定客户结构的长度。如果客户地址的长度超过这个值,它将被截段。所以在调用accept之前,address_len必须被设置为预期的地址长度。当这个调用返回时,address_len将被设置为连接客户地址结构的实际长度。
如果套接字队列中没有未处理的联结,accept将被阻塞直到有客户建立连接为止。可以通过对套接字文件描述符设置O_NOBLOCK标志来改变这一行为,使用函数fcntl。
请求连接
客户通过一个在未命名套接字和服务器监听套接字之间建立连接的方法来连接到服务器。
#include <sys/types.h>
#include <sys/socket.h>
int connect(intsockfd, const struct sockaddr *addr, socklen_t addrlen);
参数sockfd指定的套接字将连接到参数addr指定的服务器套接字,addr指向的结构的长度由参数addrlen指定。
关闭套接字
可以通过close函数来终止服务器和客户上套接字的连接。
主机字节序和网络字节序
为了使不同计算机可以通过网络传输的多字节整数的值达成一致,需要一个网络字节序。客户和服务器必须在传输之前,将它们的内部整数表示方式转换为网络字节序。可以通过以下函数完成这一工作。
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_thtons(uint16_t hostshort);
uint32_tntohl(uint32_t netlong);
uint16_tntohs(uint16_t netshort);
htonl表示”host tonetwork ,long”。
网络信息
通过调用gethostent,可以找到给定计算机主机信息
#include<netdb.h>
Struct hostent *gethostent (void);
Void sethostent (int stayopen);
Void endhostent (void);
Struct hostent{
Char *h_name;
Char **h_aliases;
Inth_addrtype;
Inth_length;
Char **h_addr_list;};
从接口获得网络名字和网络号:
#include<netdb.h.
Struct netent * getnetbyaddr (uint32_t net,int type);
Struct netent * getnetbyname (const char *name);
Struct netent * getnetent (void);
Void setnetent (int stayopen);
Void endnetent (void);
Struct netent{
Char *n_name;
Char **n_aliases;
Intn_addrtype;
Uint32_t n_net;};
将协议名字和协议号采用以下函数映射
#include <netdb.h>
Struct protoent * getprotobyname (const char* name);
Struct protoent * getprotobynumber (intproto);
Struct protoent * getprotoent (void);
Void setprotoent (int stayopen);
Void endprotoent (void);
Struct protoent{
Char *p_name;
Char **p_aliases;
Intp_proto;};
从一个服务名映射到一个端口号,服务名
#include<netdb.h>
Struct servent * getservbyname (const char *name, const char * proto);
Struct servent * getservbyport (int port,const char * proto);
Struct servent * getservent( (void);
Void setervent (int stayopen);
Void endservent (void);
Struct servent{
Char *s_name;
Char **s_aliases;
Ints_port;
Char *s_proto;};
从一个主机名字和服务名字映射到一个地址
#include <sys/socket.h>
#include <netdb.h>
Int getaddrinfo (const char * restrict host,const char * restrict service, const struct addrinfo * restrict hint, structaddrinfo ** restrict res);
Void freeaddrinfo (struct addrinfo * ai);
Struct addrinfo{
Intai_flags;
intai_family;
Intai_socktype;
Intai_protocol;
Socklen_t ai_addrlen;
Structsockaddr * ai_addr;
Char *ai_canonname;
Structaddrinfo * ai_next;};
gai_strerror将返回的错误码转换成错误消息
#include<netdb.h>
Const char * gai_strerror (int error);
将地址转换成主机或者服务名
#include<sys/socket.h>
#include <netdb.h>
Int getnameinfo (const struct sockaddr *restrict addr, socklen_t alen, char * restrict host,socklen_t hostlen, char *restrict service, socklen_t servlen, unsigned int flags);
数据传输
发送数据:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr,socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary databuffer len */
int msg_flags; /*flags on received message */
};
接收数据:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr,socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
套接字选项
#include<sys/types.h> /* See NOTES*/
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t*optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval,socklen_t optlen);
可以用以上函数来设定和获取套接字选项。