欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

网络编程-数据结构与函数详解

程序员文章站 2022-07-14 20:45:40
...

网络编程-一个简单的客户端与服务器程序(0),(看这篇就够了)中已经对程序整体有了宏观的认识。这篇主要对数据结构与函数详解。

数据结构与函数详解

IPv4相关结构:

struct in_addr
{
    in_addr_t          s_addr;  //表示32位的IP地址,32位无符号整型
}

struct sockaddr_in
{
    uint8_t            sin_len;       //表示该结构体的长度,8位无符号整型
    sa_family_t        sin_family;    //表示套接口使用的协议族,8位无符号整型
    in_port_t          sin_port;      //表示套接口使用的端口号,16位无符号整型
    struct in_addr     sin_addr;      //表示IP地址,32位无符号整型
    char               sin_zero[8];   //该成员基本不使用,总是置为0
}

sockaddr_in是IPV4套接字地址结构。

sin_len 成员是不要求一定存在的,即便这个成员存在,也无需设置它或者检查它。换句话说就是一般情况下,我们用不到该成员。

这三个sin_family、sin_port、sin_addr成员是必须存在的,分别表示套接字使用的协议族、端口号、还有IP地址。

sin_zero 该成员不使用,总是设置为0

htonl/htons/ntohl/ntohs

本地序(也称主机序)即指处理器本身所采用的字节序,因此有的大端序,有的小端序。而网络序,是指网络传输采用的字节序。所幸,网络序是标准化的,即一般统一采用大端序。因此,发送网络数据之前需要将数据转换为网络序。

htonl、htons用于本地序转网络序。
ntohl、ntohs用于网络序转本地序。

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

例子:

网络字节是0x0600,而在主机内部所读的字节是0x06.

简单而言,htons()是将一个数的高低位进行互换:(如:06 00 --> 00 06)

inet_ntop/inet_pton

#include <arpa/inet.h>

const char *inet_ntop(int af, const void *restrict src,
       char *restrict dst, socklen_t size);
int inet_pton(int af, const char *restrict src, void *restrict dst);

inet_ntop()函数将一个数字地址转换成一个适合显示的文本字符串。
inet_pton()函数应该将标准文本表示形式的地址转换为数字二进制形式。

af参数根据协议族的不同,选择AF_INET或AF_INET6
src参数指向的字符串,转为数值,存放在addstr指向的内存中

inet_pton/inet_ntop分别用于将字符串ip地址转为4字节大小的无符号整型和将无符号整型转换为ip地址字符串。例子为

#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char *argv[]){

    char ip[] = "192.168.0.114"; 
    struct in_addr addr;
    
    int ret = inet_pton(AF_INET, ip, (void *)&addr);   //IP字符串 ——>网络字节流
    if(0 == ret){
        printf("inet_pton error, return 0\n");
        return -1;
    }else{
        printf("inet_pton ip: %ld\n", addr.s_addr);
        printf("inet_pton ip: 0x%x\n", addr.s_addr);
    }

    const char *pstr = inet_ntop(AF_INET, (void *)&addr, ip, sizeof(ip));  //网络字节流 ——>IP字符串
    if(NULL == pstr){
        printf("inet_ntop error, return NULL\n");
        return -1;
    }else{
        printf("inet_ntop ip: %s\n", ip);
    }
    
    return 0;
}

编译运行:
网络编程-数据结构与函数详解
从运行结果中可以清晰看到两者之间的转换。

socket 函数

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

socket()函数应该在一个通信域中创建一个未绑定的套接字,并返回一个文件描述符,该文件描述符可以在以后对套接字进行操作的函数调用中使用。文件描述符应按文件描述符分配中描述的方式分配。

domain:

协议域,决定了 socket 的地址类型,在通信中必须采用对应的地址。常用的协议族有:AF_INET(ipv4地址与端口号的组合)、AF_INET6(ipv6地址与端口号的组合)、AF_LOCAL(绝对路径名作为地址)。

type:

常用的类型有:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。

其中 SOCK_STREAM 表示提供面向连接的稳定数据传输,即 TCP 协议。SOCK_DGRAM表示数据报套接字,即UDP协议

protocol:

常用的协议有:IPPROTO_TCP(TCP协议)、IPPTOTO_UDP(UDP协议)、IPPROTO_SCTP(STCP协议)。

当值位 0 时,会自动选择 type 类型对应的默认协议。

socket 起源于 UNIX,而 UNIX/Linux 基本哲学之一就是「一切皆文件」,socket 即是一种特殊的文件,一些 socket 函数就是对其进行的操作。 都可以用「open → write/read → close」模式来操作。

bind 函数

调用socket函数之后已经确定了协议族和传输协议,但是还没有确定本地协议,即套接字地址信息。

#include <sys/socket.h>

int bind(int socket, const struct sockaddr *address,
       socklen_t address_len);

socket:

即 socket 描述字,由 socket() 函数创建。

*address:

一个 const struct sockaddr 指针,指向要绑定给 sockfd 的协议地址。
这个地址结构根据地址创建 socket 时的地址协议族不同而不同,例如 ipv4 对应 sockaddr_in,ipv6 对应 sockaddr_in6

这几个结构体在使用的时候,都可以强制转换成 sockaddr。
下面是这几个结构体对应的所在的头文件:

1、sockaddr: sys/socket.h
2、sockaddr_in: netinet/in.h
3、sockaddr_in6: netinet6/in.h

_in 后缀意义:互联网络(internet)的缩写,而不是输入(input)的缩写。

listen 函数

服务器调用,该套接字可以接收来自客户端的连接请求。

#include <sys/socket.h>

int listen(int socket, int backlog);

socket:

即 socket 描述字,由 socket() 函数创建。

backlog:

指定在请求队列中的最大请求数,进入的连接请求将在队列中等待 accept() 它们。

connect 函数

由客户端调用,与目的服务器的套接字建立一个连接。

#include <sys/socket.h>

int connect(int socket, const struct sockaddr *address,
       socklen_t address_len);

socket:

目的服务器的 socket 描述符 。

*address

一个 const struct sockaddr 指针,包含了目的服务器 IP 和端口。

address_len:

协议地址的长度,如果是 ipv4 的 TCP 连接,一般为 sizeof(sockaddr_in);

accept 函数

accept函数在服务端调用,它用于接受来自客户端的连接,从已完成连接队列返回一个已完成连接。

#include <sys/socket.h>

int accept(int socket, struct sockaddr *restrict address,
       socklen_t *restrict address_len);

socket:

服务器的 socket 描述字,由 socket() 函数创建。

*restrict address:

一个 struct sockaddr 指针,用来存放提出连接请求客户端的主机的信息

*restrict address_len:

协议地址的长度,如果是 ipv4 的 TCP 连接,一般为 sizeof(sockaddr_in)。

close 函数

在数据传输完成之后,手动关闭连接。

#include <unistd.h>

int close(int fildes);

fd:

需要关闭的连接 socket 描述符 。

网络 I/O 函数

当客户端和服务器建立连接后,可以使用网络 I/O 进行读写操作。
网络 I/O 操作有下面几组:

read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()

最常用的是 read()/write()

ssize_t read(int fildes, void *buf, size_t nbyte);
ssize_t write(int fildes, const void *buf, size_t nbyte);

鉴于该文是侧重于描述 数据结构与函数详解的工作原理,就不再详细描述这些函数了。

网络编程-数据结构与函数详解
(微信公众号【程序猿编码】)

网络编程-数据结构与函数详解

(添加本人微信号,备注加群,进入程序猿编码交流群,领取学习资料,获取每日干货)

微信公众号【程序猿编码】,这里Linux c/c++ 、Go语言、数据结构与算法、网络编程相关知识,常用的程序员工具。还有汇聚精炼每日时政、民生、文化、娱乐新闻简报,即刻知晓天下事!

相关标签: UNIX网络编程