• 【Linux】【网络】UDP、TCP 网络接口及使用



    socket 及 相关补充

    Socket 是一种通信机制,通过它可以在不同主机之间进行数据交换

    • 在Socket 编程中,有两种常见的通信模式:客户端-服务器模式点对点模式

    • 它基于 TCP/IP 协议栈,并使用 IP地址端口号 来标识通信的目标。

    • Socket 编程可以通过 TCP(传输控制协议)UDP(用户数据报协议) 实现不同类型的连接。

    • socket 接口 创建的是文件,不同主机通过这个接口进行通信,即 网络通信依附的也是文件系统

    0. netstat - - 查询当前服务器上网络服务器

    netstat -naup:

    -n:拒绝显示别名,能显示数字的全部转化成数字
    -a:(all)显示所有选项
    -u:(udp)仅显示 udp 相关选项
    -p:显示建立相关链接的程序名
    
    • 1
    • 2
    • 3
    • 4

    netstat -nltp:

    -n:拒绝显示别名,能显示数字的全部转化成数字
    -l:仅列出有在 Listen (监听) 的服务状态
    -t:(tcp)仅显示tcp相关选项
    -p:显示建立相关链接的程序名
    
    • 1
    • 2
    • 3
    • 4

    1. 端口号(port)

    端口号是传输层协议的内容

    • 数据类型为 uint16_t,即 16 位的 2 字节整数

    • 一个端口号只能标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理

    • IP地址 + 端口号,就能够标识网络上的某一台主机的某一个进程

    注意:进程 pid 是唯一表示的,端口号也是唯一表示的,没有必然关系,就好比身份证和工号自己标记自己的

    2. 网络字节序

    讨论网络字节序还是主机字节序的意义,保证了主机能正确收发数据信息

    发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出,这需要接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存。

    网络数据流的地址这样规定:先发出的数据是低地址,后发出的数据是高地址。

    • TCP/IP 协议规定,网络数据流应采用大端字节序,即低地址高字节。
    • 不管这台主机是大端机还是小端机,都会按照这个 TCP/IP 规定的网络字节序来发送/接收数据。
    • 如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可。

    网络字节序和主机字节序的转换:

    #include 
    	
    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);
    uint32_t ntohl(uint32_t netlong);
    uint16_t ntohl(uint16_t hostshort);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。

    • 接收和发送端口号,用这些接口转化

    例如:htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。

    如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
    如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

    3. sockaddr 结构体

    是用于储存网络地址信息的结构体,实现了多态。

    IPv4 和 IPv6 的地址格式定义在 头文件 netinet/in.h

    IPv4 地址用 sockaddr_in 结构体表示,包括:

    • 16 位地址类型
    • 16 位端口号和
    • 32 位 IP 地址
    /* Structure describing an Internet socket address.  */
    struct sockaddr_in
    {
    	__SOCKADDR_COMMON (sin_);	/*16位地址类型*/
    	in_port_t sin_port;			/* Port number.端口号,无符号16位整数*/
    	struct in_addr sin_addr;	/* Internet address.IP 地址,无符号32位整数*/
    	
    	/* Pad to size of `struct sockaddr'.  */
    	unsigned char sin_zero[sizeof (struct sockaddr) -
    		  __SOCKADDR_COMMON_SIZE -
    		  sizeof (in_port_t) -
    		  sizeof (struct in_addr)];
     };
    
    /* This macro is used to declare the initial common members
       of the data types used for socket addresses, `struct sockaddr',
       `struct sockaddr_in', `struct sockaddr_un', etc.  */
    
    #define	__SOCKADDR_COMMON(sa_prefix) \
      sa_family_t sa_prefix##family
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • IPv4 地址类型 定义为常数 AF_INET

    • IPv6 地址类型 定义为常数 AF_INET6

    这样,只要取得某种 sockaddr 结构体的首地址不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容。

    • socket API 可以都用 struct sockaddr * 类型表示,在使用的时候需要强制转化成 sockaddr_in

    这样的好处是程序的通用性,可以接收 IPv4、IPv6、以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数


    一、socket 常见 API

    UDP

    0. IP 地址转化 函数

    // 字符串风格的 IP 地址,转换成为 4 字节 int,同时将主机序列转化成为网络序列
    in_addr_t inet_addr(const char *cp);
    
    // 4 字节 int,转化为字符串风格 IP 地址
    char *inet_ntoa(struct in_addr in);
    
    // string 类型 IP 地址,转换成网络序列的 4 字节 IP 地址
    // &struct sockaddr.sin_addr(点的优先级高)
    int inet_aton(const char *cp, struct in_addr *inp);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    1. socket 函数:创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)

    #include          
    #include 
    
    • 1
    • 2

    网络通信依附的也是文件系统!创建的是文件描述符。

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

    参数 domian(地址类型,即选择通信方式):

    • AF_INET,IPv4 网络通信
    • AF_INET6,IPv6 网络通信
    • AF_UNIX,本地通信

    参数 type:

    • SOCK_STREAM,流式套接(TCP)
    • SOCK_DGRAM,用户数据报(支持无连接、不可靠的数据通信,UDP)

    参数 protocol:

    • 默认设为 0,就可以自动识别是 TCP 还是 UDP

    返回值:

    • 创建成功返回文件描述符,创建失败返回 -1,并设置错误码

    2. bind 函数:绑定端口号 (TCP/UDP, 服务器)

    比如我们在实现某个函数时对 socket 进行了填充,可是创建的都是临时变量,于是就需要使用 bind 对填充数据进行于 socket 的绑定

    #include          
    #include 
    #include 
    #include 
    
    • 1
    • 2
    • 3
    • 4
    int bind(int socket, const struct sockaddr *address, socklen_t address_len);
    
    • 1

    参数 socket:

    • 套接字的文件描述符

    参数 address:

    • 用户自定义的用于填充数据的结构体,需要强转成 (struct sockaddr*) 使用

    参数 address_len:

    • 实际传入 address 的大小,即 sizeof(address)

    返回值:

    • 成功返回 0 ,失败返回 -1

    3. recvfrom 函数:接收来自socket的信息

    #include          
    #include 
    
    • 1
    • 2
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
     	               struct sockaddr *src_addr, socklen_t *addrlen);  
    
    • 1
    • 2

    参数 sockfd:

    • 服务端所绑定的套接字,后续接收和访问都从这里来

    参数 buf:

    • 未来读到的数据要放在哪一个用户或者缓冲区里

    参数 len:

    • 缓冲区的长度

    参数 flags:

    • 0,默认以阻塞方式读取

    参数 src_addr:

    • 接收缓冲区,获取到客户端的 IP 和 端口号,需要强转成 (struct sockaddr*) 使用

    参数 addrlen:

    • 接收缓冲区的大小的地址

    返回值:

    • 成功则返回接收的字节数,失败返回 -1 并设置错误码

    4. sendto 函数:发送信息给socket

    #include          
    #include 
    
    • 1
    • 2
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
    
    • 1
    • 2

    参数 sockfd:

    • 服务端所绑定的套接字,后续接收和访问都从这里来

    参数 buf:

    • 未来发送的数据是哪一个用户或者缓冲区里的

    参数 len:

    • 缓冲区的长度

    参数 flags:

    • 0,默认以阻塞方式调用

    参数 dest_addr:

    • 发送缓冲区,需要发送到哪一个客户端,需要强转成 (struct sockaddr*) 使用

    参数 addrlen:

    • 接收缓冲区的大小

    返回值:

    • 成功则返回发送的字节数,失败返回 -1 并设置错误码

    TCP

    1. listen 函数:开始监听socket (TCP, 服务器)

    #include           /* See NOTES */
    #include 
    
    • 1
    • 2
    int listen(int sockfd, int backlog); 
    
    • 1

    参数 sockfd:

    • 监听的套接字的文件描述符

    参数 backlog:

    • 此处设置的整数,是 TCP 协议 需要在底层维护 全连接队列 的最大长度 +1 。

    返回值:

    • 成功返回 0,失败返回 -1,并设置错误码

    2. accept 函数:接收连接请求 (TCP, 服务器)

    #include 
    #include 
    
    • 1
    • 2
    int accept(int socket, struct sockaddr* address, socklen_t* address_len); 
    
    • 1

    参数 socket:

    • 监听套接字的文件描述符

    参数 address:

    • 接收缓冲区,获取到客户端的 IP 和 端口号,需要强转成 (struct sockaddr*) 使用

    参数 address_len:

    • 接收缓冲区的大小的地址

    返回值:

    • 如果接收成功,返回一个文件描述符(这一个套接字是完成具体业务的),接收失败则返回 -1,并设置错误码

    3. read 函数:读取文件(网络)中的内容

    ps:tcp 是面向流的,可以用;udp 是面向数据报的,不可以用

    #include 
    
    • 1
    ssize_t read(int fd, void *buf, size_t count);
    
    • 1

    返回值:

    • 成功返回读取到的字符数,对方关闭连接返回 0,读取失败返回 -1,并设置错误码

    4. write 函数:写入数据到文件(网络)

    ps:tcp 是面向流的,可以用;udp 是面向数据报的,不可以用

    #include 
    
    • 1
    ssize_t write(int fd, const void *buf, size_t count);
    
    • 1

    返回值:

    • 成功返回写入的字符数,写入失败返回 -1,并设置错误码

    5. connect 函数:发起链接请求 (TCP, 客户端)

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 
    
    • 1

    参数 sockfd:

    • 发起连接的套接字的文件描述符

    参数 addr:

    • 记录要发送对象的信息缓冲区,转成 (struct sockaddr *) 可以使用

    参数 addrlen:

    • 缓冲区的大小

    返回值:

    • 成功返回 0,失败返回 -1,并设置错误码

    6. recv 函数:接收来自套接字的信息

    使用和 read 几乎一样。

    #include          
    #include 
    
    • 1
    • 2
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags);  
    
    • 1

    参数 sockfd:

    • 服务端所绑定的套接字,后续接收和访问都从这里来

    参数 buf:

    • 未来读到的数据要放在哪一个用户或者缓冲区里

    参数 len:

    • 缓冲区的长度

    参数 flags:

    • 0,默认以阻塞方式读取

    返回值:

    • 成功则返回接收的字节数,失败返回 -1 并设置错误码
  • 相关阅读:
    Dubbo链路追踪——生成全局ID(traceId)
    Leetcode刷题【hot100】盛最多水的容器
    ————python网络并发测试题————
    拼多多启动第四届农货节:携手10万涉农店铺,与8.8亿消费者共享“秋收喜悦”
    C# --- 坦克大战开发 --- 绘制游戏界面
    【postgresql】ERROR: cannot alter type of a column used by a view or rule
    利用vue做一个倒计时抢购的组件
    谷歌硬件工程师年薪165万,苹果外籍员工222万,在大厂打工“香”吗?
    c++:二叉搜索树的简析与详细实现(万字)
    超级计算/先进计算的十大用途
  • 原文地址:https://blog.csdn.net/m0_67470729/article/details/132722936