套接字简介

套接字是通信端点的一个抽象,套接字描述符和文件描述符一样是一个索引下标,普通文件在操作时通过open打开文件初始化文件结构体,返回文件描述符,当需要读写操作时操作系统通过文件描述符找到,描述文件的结构体,然后通过结构体中提供的信息进行操作,套接字描述符,通过socket函数调用进行初始化创建struct file 结构体和 struct socket结构体,当需要进行操作时,通过文件描述符,找到struct file结构体中的private_data 指针找到struct socke结构体然后找到const struct proto_ops*ops这个指针指向结构体对应的操作。
socket套接字及相关函数介绍-LMLPHP

套接字类型

常见的TCP/IP协议的套接字有三种

  1. 流式套接字(SOCK_STREAM):提供面向连接的可靠数据传输,其传输方式为面向字节流传输,通过TCP即传输控制协议进行传输控制
  2. 数据报套接字(SOCK_DGRAM):提供了一种无连接的服务,其传输方式为面向数据报传输,使用UDP协议进行数据的传输,该服务并不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据
  3. 原始套接字(SOCK_RAW):可以操作访问底层协议数据,如IP数据报,ARP,以太网桢,ICMP报文等,可以通过原始套接字进行收发下层协议报文。

套接字控制函数

  1. 创建套接字socket
    函数原形:int socket(int domain, int type, int protocol);
    函数功能:
    创建套接字,建立相应的描述结构体(struct file 和 struct socket),对其数据进行初始化,返回文件描述符。
    参数:
    domain协议族,决定socket使用的协议地址类型,有AF_INET(ipv4的地址32位和端口号16位的组合), AF_INET6(ipv6), AF_UNIX(使用一个绝对地址)等
    type:创建的socket类型,即SOCK_STREAM,SOCK_DGRAM,SOCK_RAW等
    protocol:协议类型IPPROTO_TCP,IPPROTO_UDP等如果取0会自动根据socket类型进行判断
    返回值:失败返回 -1 ,成功返回描述符值
  2. 绑定函数bind
    函数原形:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    函数功能,把一个本地协议地址赋予一个套接字
    参数: sockfd socket返回的描述符
    对于sockaddr结构,因为系统需要支持多种地址结构,因此在这里如果需要使用AF_INET地址类型,需要使用struct sockaddr_in结构体进行操作,然后进行绑定时进行强转为struct sockaddr类型
    addrlen:第二个参数的长度
    返回值:成功返回0,失败返回-1
    另外:TCP客户端如果不绑定,在调用connect时系统会为其分配一个临时端口和IP用于连接(TCP客户端推荐使用这种,可以减少冲突的可能性)TCP服务端和UDP服务端在绑定时,可以选择将监听套接字IP地址INADDR_ANY这个宏,可以监听多个IP
    struct sockaddr_in{
    	unsigned short  sin_family;
    	unsigned short int  sin_port;
    	struct_addr   sin_addr;
    	unsigned char  sin_zero[8];
    };
    
  3. 建立连接connect
    函数原形:int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    函数功能:发起TCP连接请求,socket 客户端通过调用 connect 函数在 socket 数据结构中保存本地和远端信息
    函数参数:参数同bind函数, 区别在于bind参数是自己的接口地址,connect参数是服务端的接口地址。
    返回值:成功返回0,出错返回-1
  4. 监听函数listen
    函数原型:int listen(int sockfd, int backlog);
    函数功能:socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求,调用listen导致套接字从CLOSED状态转换到LISTEN状态
    函数参数:
    内核为任何一个给定的监听套接字维护两个队列:
    未完成连接队列,每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程。这些套接字处于SYN_RECV状态
    已完成连接队列,每个已完成TCP三路握手过程的客户对应其中一项。这些套接字处于ESTABLISHED状态。
    backlog参数:是这两个队列之和的最大长度
    返回值:成功返回0,出错返回-1
  5. 接受连接accept
    当进程调用accept时,已完成连接队列中的队头项将返回给进程,或者该队列为空,那么进程就被投入睡眠,直到TCP在该队列中放入一项才唤醒它
    函数原型:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    函数参数:sockfd是监听后的套接字, addr用于存放客户端的接口地址信息,addrlen为第二个参数长度,
    返回值:成功是一个新的套接字,它代表的是和客户端的新的连接,失败 -1
  6. close函数
    关闭套接字,如果是已连接的套接字会首先终止连接,然后关闭套接字。

套接字IO函数

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);
ssize_t write(int fd, const void *buf, size_t count);

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);
ssize_t read(int fd, void *buf, size_t count);

在这些函数中其中UDP服务只能使用sendto/recefrom 和 sendmsg/recvmsg进行收发,因为其是无连接面向数据报的,需要接收方接口地址或者接受发送方接口地址信息,另外TCP数据是有连接面向字节流的,在TCP调用send/recv write/read sendmsg/recvmsg 时并没有将数据直接发送而是先将数据放入其缓冲区,如果数据长度较长会进行分组,较短会进行等待当数据足够进行合并,之后构建成报文段到下层;另外UDP调用IO函数也是将数据放入缓存区不过其直接构成UDP数据报到IP层,不对数据进行合并和拆分。

字节序转换函数

网络字节续为大端字节序,即低地址存放高位,主机大端和小端字节序均有取决于主机CPU,小端是低地址存放低位,因此在发数据时需要进行统一字节序,转换为网络字节序
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
其中h表示host主机,n为network网络,l为32位长整型,s为16位短整型,
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

地址转换函数

通常IP地址通过点分十进制的字符串进行表示,在socket中struct in_addr为32位IP地址,因此需要进行转换
点分十进制字符串转in_addr:
int inet_aton(const char *cp, struct in_addr *inp);
int inet_pton(int af, const char *src, void *dst);
in_addr_t inet_addr(const char *cp);
in_addr转点分十进制字符串:
char *inet_ntoa(struct in_addr in);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
其中inet_pton/inet_ntop即可用于ipv4,也可以用于ipv6只要改变第一个参数AF_INET或AF_INET6
另外,inet_ntoa不是线程安全函数,并且因为函数直接返回,char * 即函数在内部开辟了空间,而且这个是一个静态变量,多次调用会修改其值,建议使用inet_ntop,这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题;

12-01 08:27