int socket(int domain, int type, int protocol);
- 套接字是应用程序和底层网络协议之间的一个通信端口,这意味着想要读取来至网络的数据就需要读取套接字,想要写数据到网络就要写套接字,想要控制网络协议选项就要设置套接字。从程序员的角度来讲,套接字等价于网络,就像文件描述符是磁盘操作的一个端口一样。
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
/*@param
* doamin--指明通信域,决定通信使用的网络协议族,协议族不同,地址结构也不同
* -AF_UNIX:UNIX通信域,即同一台计算机内两个进程通过文件系统进行通信
* 要求文件系统的路径名作为套接字的地址
* -AF_INET:网络通信,使用IPv4;要求32位IPv4地址
* -AF_INET6:网络通信,使用IPv6;
*
* type--指明套接字的类型,套接字的类型指明通信的语义。
* -SOCK_STREAM:字节流套接字。(TCP)
* -SOCK_DGRAM:数据报套接字。(UDP)
* -SOCK_RAW:raw套接字,主要用于越过高层协议而直接访问低层协议,使程序员
* 可以直接使用IP协议或者是网络的物理层。
*
* protocol--domain参数确定套接字采用什么协议族,而此参数在给定的协议族内选
* 选择一种具体的协议。对于给定的通信域内的每一种套接字类型,一般只存在
* 一种协议,在这种情况之下,只要指明了通信域和通信类型,协议就是唯一的,
* 一般都将此参数置为0,即让系统选择默认的协议。
*/
该函数在通信域domain中创建一个类型为type、使用协议protocol的套接字,并返回一个套接字描述字。
int socketpair(int domain,int type,int protocol,int filedes[2]);
socket()创建的只是一个套接字,若通信的两个进程是由fork()派生的具有共同祖先的进程,则可以使用socketpair();
#include <sys/socket.h>
int socketpair(int domain,int type,int protocol,int filedes[2]);
/*若type指明是有链接的类型,得到的两个套接字是已连接的,若指明了一个
* 无连接的类型,得到的两个套接字是无连接的,但是由于他们都知道
* 对方的存在,因此可以互相发送数据包。该函数通常用于父子进程之间
* 通信,与无名管道非常的相似。
*/
该函数创建一对未命名的套接字,返回这对套接字描述符于filedes[0]和filedes[1]当中,套接字偶对是一个全双工的通信信道,其在两端均可执行读和写操作。fork之后须在一端关闭另一端使用的描述字。
close()和shutdown()
#include <unistd.h>
int close(int socket);
#include <sys/socket.h>
int shutdown(int socket,int how);
/*
*how--指明执行哪一种关闭动作可以为下列值:
* -SHUT_RD:停止从此套接字接收数据,若还有数据到达,则拒收。
* -SHUT_WR:停止从此套接字发送数据,忽略任何等待发送的数据,
* 停止查询已经发送的数据的有关消息,若已发送数据丢失则不在重新发送它。
* -SHUT_DOWN:停止从该套接字发送和接收数据。
*/
close()导致该套接字被销毁,当此套接字是面向连接的且还有数据等待传送时,close()会尝试完成传送。有时我们并不需要关闭这个套接字,而是断开其连接,则可以使用shutdown()来做。
套接字地址结构
在Internet通信域当中,套接字地址由主机的IP地址加上端口号组成。
程序中分别使用两种不同的数据类型来表示32位和128位的IP地址
#include <netinet/in.h>
typedef unit32_t in_addr_t;
struct in_addr {in_addr_t s_addr;};
struct in6_addr {uint8 s6_addr[16]; };
UNIX提供了如下的一组函数将点-数表示的IPv4地址或者是冒号表示的IPv6地址转换成按照网络字节序表示的二进制整数形式或者反之。
#include <apra/inet.h>
int inet_aton(const char *name,struct in_addr *addr);
/*将标准数-点表示的IPv4地址转换为二进制形式,并将结果存储在addr中*/
char *inet_ntoa(struct in_addr inaddr);
/*转换32位IPv4地址为标准的数-点表示字符串,存储该字符串在一个静态分配的缓存区当中,并返回指向该缓冲区的指针。后继调用将会覆盖同一个缓冲区,因此当需要保存其内容时应该将其复制到别处*/
int inet_pton(int family,const char *nameptr, void *addrptr);
const char *inet_ntop(int family, void *addrptr, char *nameptr, size_t len);
/*这两个函数是为了IPv6新加的即可转换v6也可转换v4,由family取值AF_INET还是AF_INET6来指定.*/
域名地址
UNIX内部使用一个主机地址数据库来记录主机名和IP地址之间的映射,这一数据库由文件/etc/hosts或者是域名服务系统提供.
gethostbyname()和gethostbyaddr()用来从该数据库中获得一台主机的完整地址信息,包括主机名和IP地址.
#include<netdb.h>
struct hostent *gethostbyname(const char *name)
/*返回主机name的地址信息,失败返回NULL,成功则保存这些信息在一个静态分配的hostent结构当中,并返回该结构的地址,若需保存,则复制出来.*/
struct hostent *gethostbyaddr(const void *addr,size_t length ,int type);
服务与端口号
Internet通信域当中的套接字地址由机器的IP地址加上端口号组成,IP地址是唯一标识网络中的一台计算机的,端口号则区别一台机器中的不同的服务进程.
在UNIX系统中存在一个专门记录标准服务的数据库,由文件/etc/services或者是域名服务器提供.定义在<netdb.h>中的servent结构当中,用于表示服务数据库的登记项信息.
struct servent{
char *s_name;
char *s_aliases;
int s_port;
char *s_proto;
}
#include <netdb.h>
struct servent *getservbyname(const char *name,const char *proto);
//返回使用协议proto且名为name的服务的有关信息.
struct servent *getserbyport(int port,const char *proto);
//返回使用协议proto且位于端口port的服务的有关信息.
套接字地址数据结构
现在我们已经知道了表示IP地址的数据结构in_addr和in6_addr,知道了如何从主机名获得字符串形式的IP地址,已经如何将字符串形式的IP地址转换为32位或者是128位的数值形式,同时也知道了如何获取端口号,由此便可以顺利的形成套接字的地址.
1.UNIX通信域的套接字地址结构
#include <sys/un.h>
struct sockaddr_un{
sa_family_t sun_family;
char sun_path[];//给出unix文件的路径名,不能与文件系统中的其他文件同.
}
每一种通信域套接字地址结构中的第一个成员都相同,他们指出套接字的地址族。对于sockaddr_un结构类型,该成员的值只能是AF_UNIX。由sun_path命名的文件是在调用bind的时候创建的.此时内核为他在文件系统中创建一个inode.所以应该在关闭一个命令套接字之后,还应该调用unlink()或者是remove()将其从文件系统中移除.
- IPv4和IPv6通信域套接字地址结构
IPv4套接字地址的类型是sockaddr_in结构,它至少由如下一些成员.表示IPv6的套接字地址的数据类型是sockaddr_in6结构,他至少有如下的成员:
#include <netinet/in.h>
struct sockaddr_in{
sa_family_t sin_family; //套接字地址族或格式,值应该为AF_INET
in_port_t sin_port; //16位端口号,网络字节序
struct in_addr sin_addr; //32位IP地址,网络字节序
unsigned char sin_zero[8]; //保留未用
}
struct sockaddr_in6{
sa_family_t sin6_family; //AF_INET6
in_port_t sin6_port; // 16位端口
uint32_t sin6_flowinfo; //IPv6流标签和优先级信号,网络字节序
struct in6_addr sin6_addr; //128位IPv6,网络字节序
}
- 通用套接字地址结构
许多的套接字函数都要求指明套接字的地址,为了既能够支持不同的协议族,又有统一的接口,这些函数使用指向结构类型为sockaddr的指针作为参数:
struct sockaddr{
sa_family_t sa_family;
char sa_data[];
}
例如:
struct sockaddr_un name;
name.sun_family = AF_UNIX;
strcpy(name.sun_path,filename);
bind(sockfd,(struct aockaddr *)&name, sizeof(name));
字节顺序
数据是存放在存储器当中的,存放数据的基本单元是字节通常4个字节或者是八个字节组成一个机器字.一般与计算机的寄存器的大小相同.
当将一个数存放在存储器当中时,有两种存放顺序,一种将一个数当中最有意义的字节(MSB)防在存储器编号较小的字节(称为little-endian顺序,因为数的最小意义位在后面.).另外一些计算机将最小意义字节(LSB)放在存储器编号较小的字节(称为big-endian顺序,因为数的最有意义位在后面 )
例如:
大端模式:数据的结尾部分位于地址的高位.
小端模式:数据的结尾部分位于地址的地位.
两台计算机通过套接字通信时,若各自使用不同的字节顺序将导致数据的解释出现问题,Internet协议采用统一的big-endian字节顺序,称之为网络字节序。
因此,当建立一个Internet套接字连接时,必须要保证在sockaddr_in中的sin_port和sin_addr域当中给出的数据是网络字节序的。如果封装一个整形数据在一个消息当中并通过套接字发送出去,则也应该将其转换为网络字节序使用getservbyname()和gethostbyname()或者是inet_aton()获得的端口号和机器地址已经是网络字节序的,可以直接使用。否则必须使用专门的函数显式的转换。
#include <arpa/inet.h>
unit16_t htos (unit16_t hostshort)//用于转换sin_port
unit32_t htol(unit32_t hostlong) // 转换sin_addr
unit16_t ntohs (uint16_t netshort) //用于转换sin_port
unit32_t ntohl (uint32_t netlong); //
命名套接字
用socket()创建的套接字只是系统中的一个没有名字的资源,其他的进程无法访问,也无法从他接收消息,仅仅当程序通过调用bind()给套接字指定了一个名字之后其他的进程才能找到他。只有对外提供服务的套接字才需要bind();
int bind(int socket,const struct sockaddr *address,socklet_t address_len);
套接字通信模式
支持两种通信模式:有连接的(流)和无连接的(数据报)。
-
当服务程序启动的时候,先调用socket()创建一个套接字,之后调用bind()给该套接字命名一个众所周知的名字,以便其他的程序能够与之进行通讯。命名之后,等待客户端的连接,在多个客户端同时连接时,调用listen()为进入的连接创建一个连接队列。通过调用accept()逐一的接收这些连接。
服务器每一次调用accept()都会创建一个新的套接字,它不同于之前的已经命名的套接字,这个新的套接字完全只用于和特定的客户端通信
和有链接的套接字通信方式不同的是,无连接的套接字通信以对等的方式交换数据。尽管通信的两个进程仍然可能分为客户和服务进程,客户和服务都必须要先创建套接字。
流套接字操作
在这种通信方式中,bind允许服务进程给予一个套接字地址,并建立连接的一方,客户进程通过调用connect(),而服务经常调用accept()来完成连接。我们称一对已经连接的套接字为对等套接字,其中请求服务的一方为主动套接字,等待连接请求的套接字为被动套接字。一个套接字在开始的时候是主动的,并且只有在调用了listen之后才会成为被动。只有主动套接字可以用于connect(),只有被动套接字可以用于accept();
- 请求连接
请求连接是客户端的动作,客户端的套接字是主动套接字,客户进程通过调用connect()建立主动套接字与有名的被动套接字之间的连接。
#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, socklen address_len);
connect()调用成功时,套接字socket被连接到address给出的地址,后者必须是已经存在的服务程序套接字。connect()等待直到服务程序回答了连接请求,或者是等待时间超过了某个时间限制之后才返回。若超时返回,connect()将会失败并且流产连接请求。如果connect()在阻塞期间由于收到信号而被中断,它将失败并置errno为EINTR,但是连接请求并不会流产,而是被异步的建立。可以对套接字socket设置非阻塞方式,从而使得connect不等待回答就直接返回。对套接字设置非阻塞方式的方法和文件描述字一样使用fcntl();若对套接字socket()设置了非阻塞标志O_NONBLOCK,当连接不能够被立即建立的时候,connect将失败并置errno为EINPROGRESS但是连接请求不是流产,而是被异步的建立。在连接建立之前,后继对同一个套接字调用connect()将会导致失败。
对于异步建立的连接,可以使用select()和poll()来指出套接字就绪