套接字连接
套接字是一种通信机子。凭借这样的机制。客户/server系统的开发工作既能够在本地单机上进行。也能够夸网络进行。
套接字的创建和使用与管道是有差别的。由于套接字明白地将客户和server区分开来。
套接字连接:
首先,server应用程序用系统调用socket来创建一个套接字,它是系统分配给该server进程的类似文件描写叙述符的资源,它不能与其它进程共享。
接下来。server进程会给套接字起个名字。本地套接字的名字是Linux文件系统中的文件名称,对于网络套接字它的名字是与客户连接的特定网络有关的服务标识符。这个标识符同意Linux将进入的针对特定port号的连接转到正确的server进程。
我们用bind来给套接字命名。然后server进程就開始等待客户连接到这个命名的套接字。
系统调用listen的作用是。创建一个队列并将其用于存放来自客户的进入连接。server通过系统调用accept来接受客户的连接。
server调用accept时。它会创建一个与原有的命名套接字不同的新套接字。这个套接字仅仅用于与这个特定的客户进行通信,而命名套接字则被保留下来继续处理来自客户的连接。
基于套接字系统的client更简单。
客户首先调用socket创建一个未命名套接字,然后将server的命名套接字作为一个地址来调用connect与server建立连接。
套接字属性
套接字的特性由三个属性确定:域、类型和协议。套接字还用地址作为它的名字。地址的格式随域的不同而不同。每一个协议又能够使用一个或多个地址来定义格式。
套接字的域
域指定套接字通信中使用的网络介质。
最常见的套接字域是AF_INET,它指的是互联网络。其底层协议——网际协议仅仅有一个地址族。它使用一种特定的方式来指定网络中的计算机,即人们常说的IP地址。
server计算机上可能同一时候有多个服务正在执行。
客户能够通过IPport来指定一台联网机器上的某个特定服务。在系统内部,port通过分配一个唯一16位的整数来标识,在系统外部,则须要通过IP地址和port号的组合来确定。
套接字作为通信的终点。必须在開始通信之前绑定一个port。
套接字域还能够是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地址和port号来全然确定。
命名套接字
要想让通过socket调用创建的套接字能够被其它进程使用,server就必须给该套接字命名。这样,AF_UNIX套接字就会关联到一个文件系统的路径名。AF_INET套接字结汇关联到一个IPport。
#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。
创建套接字队列
为了可以在套接字上接受进入的连接,server程序必须创建一个队列来保存未处理的请求。使用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);
一旦server程序创建并命名了套接字后,它就能够通过accept系统调用来等待客户建立对该套接字的连接。
accept系统调用仅仅有当客户试图连接到由sockfd參数指定的套接字上时才返回。
这里的客户指,在套接字队列中排在第一个的未处理连接。accept函数将创建一个新套接字来和该客户进行通信,而且返回新套接字的描写叙述符。
新套接字的类型和server监听套接字类型一样。
套接字必须事先由bind调用命名,而且有listen调用给他分配一个连接队列。
连接客户的地址将被放入address參数指向的sockaddr结构中。
參数address_len指定客户结构的长度。假设客户地址的长度超过这个值,它将被截段。所以在调用accept之前,address_len必须被设置为预期的地址长度。
当这个调用返回时,address_len将被设置为连接客户地址结构的实际长度。
假设套接字队列中没有未处理的联结,accept将被堵塞直到有客户建立连接为止。能够通过对套接字文件描写叙述符设置O_NOBLOCK标志来改变这一行为,使用函数fcntl。
请求连接
客户通过一个在未命名套接字和server监听套接字之间建立连接的方法来连接到server。
#include <sys/types.h>
#include <sys/socket.h>
int connect(intsockfd, const struct sockaddr *addr, socklen_t addrlen);
參数sockfd指定的套接字将连接到參数addr指定的server套接字,addr指向的结构的长度由參数addrlen指定。
关闭套接字
能够通过close函数来终止server和客户上套接字的连接。
主机字节序和网络字节序
为了使不同计算机能够通过网络传输的多字节整数的值达成一致,须要一个网络字节序。
客户和server必须在传输之前,将它们的内部整数表示方式转换为网络字节序。
能够通过下面函数完毕这一工作。
#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;};
从一个服务名映射到一个port号,服务名
#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);
能够用以上函数来设定和获取套接字选项。