socket

socket介绍

  • socket是一种IPC机制,但是它不仅可以实现本机进程间的通信还可以实现不同网络中的进程间通信。一般的unix操作系统以及大多数操作系统都是使用BSD版本的套接字
  • 使用int socket(int domain, int type, int protocol);系统调用可以创建一个socket,该调用返回一个指向socket的文件描述符。第一个参数domain表示通信的范围,常用取值有AF_INET、AF_INET6、AF_UNIX,取值的含义见下表;第二个参数type表示该套接字的类型,常见的取值有流套接字和数据报套接字(SOCK_STREAM和SOCK_DGRAM);第三个参数一般指定为0,但如果要使用原始套接字需要指定为SOCK_RAW
domain 通信方式 地址格式 地址结构
AF_INET 通过ipv4进行通信 32位ip地址加上16位端口号 sockaddr_in
AF_INET6 通过ipv6进行通信 128为ip地址加上16位端口号 sockaddr_in6
AF_UNIX 通过本地主机通信 路径名 sockaddr_un
  • 在创建完socket以后,需要将一个地址绑定到socket上,这时候需要调用int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);其中sockfd是相关套接字的文件描述符;addr是指向地址结构的指针,该结构的类型根据domain的参数而确定;addrlen表示该结构的大小。该函数调用成功返回0,失败返回-1
  • 流socket通常分为主动和被动两种,在默认情况下,使用socket调用创建的socket是主动的。一个主动的socket可以通过int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen)调用来连接一个被动的sokcet(监听套接字),其中sockfd是对应的主动套接字,addr是远端被动套接字绑定的地址,addrlen是结构体长度,这称为主动打开一个连接。而一个被动的socket需要调用int listen(int sockfd,int backlog)函数来获得,相当于开启监听。sockfd是对应的套接字文件描述符,backlog是指定内核中未决套接字队列的长度。当服务端没法处理一个连接时(可能正在执行IO或者其他),内核会将accept产生的套接字放入未决队列中,accept调用会立即返回成功;如果超出了队列的长度限制,accept调用会阻塞直到队列中有空闲的位置,出现这种情况意味着有大量的socket同时连接服务器,在这个时候对于在对等端调用connect连接函数的socket来说,可能就会连接失败
  • int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen)中的sockfd是一个被动套接字,如果调用成功,addr会指向对等端的地址结构体,addrlen是结构体长度;并且该函数返回一个新的文件描述符,指向一个新的套接字,这个新的套接字就是后续用来与对等端通信的socket
  • 由于socket相关函数返回的都是文件描述符,所以可以使用read、write等IO函数进行数据收发;并且可以调用close关闭socket。同时,socket的IO方式和管道的IO方式很相似,如果某对等端关闭了套接字,那另一端想从中读取数据会返回EOF;往里面写数据会收到SIGPIPE信号,中断write调用并返回EPIPE错误
  • 使用数据报套接字收发数据需要使用专门的函数ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen)以及ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen)
  • 对于unix domain来说,即本机进程间的通信,想要连接一个流socket需要在该socket文件上拥有写权限;想要通过数据报socket发送数据需要对socket文件拥有写权限。此外,都需要对该socket所在路径的所有目录有执行权限
  • udp是不可靠的协议,因为udp在ip协议之上只提供了两个额外的特性,端口号和数据校验。所以在使用udp传输数据时需要在应用层保证其可靠性(如果必要的话)

Internet Domain SOCKET

  • 由于不同的硬件架构用不同的字节顺序来存储数据,所以为了方便在网络上传输,需要规定一个标准的存储顺序,称之为网络字节序。这是一种大端的字节序,即数据的最高有效位存储在地址的低字节。所以经常需要将数据在网络字节序和主机字节序之间相互转换,常常使用下面这组函数进行转换
#include 

uint16_t htons(uint16_t host_uint16);
uint32_t htonl(uint32_t host_uint32);
uint16_t ntohs(uint16_t net_uint16);
uint32_t ntohl(uint32_t net_uint32);
  • 对于Internet Domain类型的socket来说,地址类型分为ipv4和ipv6两种,这里只介绍ipv4。即对于domain为AF_INET的socket来说,使用的地址被存储于一个sockaddr_in的结构体中。其中sin_family需要指定为AF_INET;sin_port表示该地址绑定的端口号,端口号的类型为in_port_t(16位无符号整型),一般使用htons函数转换字节序;sin_addr是一个存放具体ipv4地址的结构体in_addr,该结构体中只有一个in_addr_t类型的成员变量,表示一个32位无符号整型;最后一个_pad字段是用来填充字节的,一般不设置。相关结构体如下
#include 

struct in_addr{
    in_addr_t s_addr;
};

struct sockaddr_in{
    sa_family_t sin_family;
    in_port_t sin_port;
    struct in_addr sin_addr;
    unsigned char _pad[X];
};
  • int inet_pton(int domain,const char* src_str,void* addrptr)函数允许将字符串表示的地址转换为对应的二进制格式的地址。其中domain指定AF_INET或者AF_INET6;src_str表示字符串表示的v4或者v6地址;转换得到的二进制地址会存放于addrptr中(该指针指向一个in_addr或者in_addr6结构)。相反的,const char* inet_ntop(int domain,const void* addrptr,char* dst_str,size_t len)函数将一个二进制表示的地址转换为相对应的字符串形式。其中domain参数的意义和上面一样;addrptr应该指向一个in_addr或者in_addr6结构体;dst_str指向存放字符串的缓冲区;len表示缓冲区长度
  • 如果只是单纯的想要转换ipv4地址,可以使用char* inet_ntoa(struct in_addr in)int inet_aton(const char* string,struct in_addr* addr)代替上面较复杂的函数
  • 函数int getaddrinfo(const char *node, const char *service,const struct addrinfo *hints,struct addrinfo **res)可以通过查询DNS服务器获得所指定域名的所有相关地址以及端口号(服务名)。该函数调用成功返回0,否则返回错误码,可以调用const char* gai_strerror(int errcode)函数来查看出错信息。其中node参数包含一个主机名或者字符串形式的地址;service参数包含一个十进制的端口号或者服务名;hints参数指向一个addrinfo结构体,用来规定res列表中返回的地址的标准,hints参数只能设置结构体中的ai_flags,ai_family,ai_socktype和ai_protocol这四个字段,其余字段应该设置为0,ai_family字段指定了返回的socket的地址结构(ipv4或者ipv6或者其他),ai_socktype指定了返回的socket类型,可以是SOCK_DGRAM或者SOCK_STREAM等;res参数返回一个addrinfo结构体列表,保存了所有与指定域名以及端口号相关的地址信息。addrinfo结构体如下
struct addrinfo {
     int ai_flags;
     int ai_family;
     int ai_socktype;
     int ai_protocol;
     socklen_t ai_addrlen;
     struct sockaddr* ai_addr;
     char* ai_canonname;
     struct addrinfo* ai_next;
 };
  • 由于getaddrinfo函数如果调用成功,会动态分配内存,保存结构体链表。所以若不在需要该链表,需要主动调用void freeaddrinfo(struct addrinfo *res)释放内存
  • 对于一个socket来说,可以调用int getsockname(int sockfd, struct sockaddr*addr, socklen_t *addrlen)来获得相关的本地地址信息;也可以调用int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen)来获得对等端相对应的客户端地址信息

你可能感兴趣的:(socket)