网络知识总结---(四)Linux网络编程基础API

socket地址API

主机字节序与网络字节序

主机字节序:PC机多采用小端字节序,因此小端字节序又被称为主机字节序。
网络字节序:大端字节序。

Linux下完成主机字节序和网络字节序的转换:

#include<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned long int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsigned short int ntohs(unsigned long int netlong);

长整型一般用来转换IP地址,短整型一般用来转换端口号。

socket地址

Linux为各个协议提供了专门的socket地址结构体.
UNIX本地协议族

#include<sys/un.h>
struct socketadd_un
{
    sa_family_t sin_family; // 地址族:AF_UNIX
    char sun_path[108]; // 文件路径名
}

TCP/IP协议族有 sockaddr_in 和 sockaddr_in6 两个专用 socket 地址结构体,分别表示IPv4 和IPv6。

#include<sys/in.h>
struct sockaddr_in
{
    sa_family_t sin_family; // 地址族:AF_INET
    u_int16_t sin_port; // 端口号:网络字节序表示
    struct in_addr sin_addr; // IPv4结构体
}

struct in_addr
{
    u_int32_t s_addr; // IPv4地址,网络字节序表示
}

地址族通常与协议族相对应:
这里写图片描述

IP地址转换函数

我们通常读IP地址采用的点分十进制,但是在编程时需要转换为二进制数。

#include<arpa/inet.h>
in_addr_t inet_addr(const char* strptr); // 点分十进制转换为网络字节序,失败返回INADDR_NONE
int inet_aton(const char* cp, struct in_addr* inp); // 点分十进制->网络字节序,结果存在inp指向的地址结构体中
char* inet_ntoa(const in_addr in); // 网络字节序->点分十进制,使用静态变量存储结果,不可重入

创建socket

socket就是一个可读、可写、可控制、可关闭的文件描述符。

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);

domain:使用哪个底层协议族。TCP/IP设置为 PF_INET 或 PF_INNET6。
type:服务类型,SOCK_STREAM(流服务)和SOCK_UGRAM(数据报),可对应TCP与UDP。
protocol:一般置 0 ,表示默认协议。

命名socket(bind)

创建socket时,指定了地址族,但是没有指定该地址族的哪个具体socket地址。将一个 socket 与 socket 地址绑定称为 给socket 命名。客户端不需要命名,操作系统自动分配socket地址。命名socket使用bind函数,

#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);

bind 将 my_addr 所指的 socket 地址分配给未命名的文件描述符 sockfd ,addrlen 参数指出该 socket 地址的长度
成功返回 0 失败返回 -1 并设置 errno。常见两种errno:
- EACCES:被绑定的地址是受保护的地址,普通用户绑定到知名服务器端口号
- EADDRINUSE:被绑定的端口正在使用。比如绑定到一个处于 TIME_WAIT 的 socket 地址。

监听socket(listen)

命名 socket 后创建一个监听队列以存放待处理的客户连接。

#include<sys/socket.h>
int listen(int sockfd, int backlog);

socket:被监听的 socket
backlog:内核监听队列的最大长度,典型值为 5 ,监听队列的长度如果超过 backlog ,服务器将不受理新的客户连接,客户端将收到 ECONNREFUSED 错误信息。

接受连接(accept)

从 listen 队列中接受一个连接。

#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);

sockfd:执行过 listen 系统调用的监听 socket
addr:获取被接受连接的远端 socket 地址
addrlen:addr 的长度
accept成功:返回一个新的连接 socket ,该 socket 唯一的标识了被接受的这个连接,服务器可以通过读写该 socket 来与被接受连接对应的客户端通信。

发起连接(connect)

服务器通过 listen 调用来被动接受连接,客户端通过 connect 主动也服务器建立连接。

#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd, const struct sockadd* serv_addr, soklen_t addrlen);

sockfd:由socket 系统调用返回一个 socket
serv_addr:服务器监听的 socket 地址
addrlen:指定这个地址的长度

关闭连接

关闭对应的 socket ,关闭 sockfd 这个文件描述符即可

#include<unistd.h>
int close(int fd);

fd 是待关闭的 socket ,但是,close 系统调用并非总是立即关闭一个连接,而是将 fd 引用计数减一。只有当 fd 的引用计数为 0 时,才真正关闭连接。
多进程中,一次 fork 系统调用默认使父进程中打开 socket 的引用计数加一,因此,父子进程都要 close 才能关闭连接。

如果无论如何都要终止连接,使用 shutdown 调用

#include<sys/socket.h>
int shutdouwn(int sockfd,int howto);

sockfd:待关闭的 socket
howto:shutdown 行为

这里写图片描述

数据读写

TCP数据读写

对文件的读写操作 read() 和 write() 同样适用 socket。为了增加对数据读写的控制,socket 编程接口提供了专用的系统调用,对于TCP数据流读写:

#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
ssize_t send(int sockfd, const void* buf, size_t len, int flags);

buf 和 len:指定读取缓冲区的位置和大小
falgs:通常设为 0

UDP数据读写

socket 编程接口用于UDP数据报读写的系统调用:

#include<sys/types.h>
#include<sys/socket.h>
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);

因为UDP通信是无连接的,所以每次读取数据都需要获取发送端的 socket 地址,即 src_addr 所指的内容,addrlen 所指的长度。

通用数据读写函数

#include<sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);

结构中包含所需的信息。

猜你喜欢

转载自blog.csdn.net/qq_37934101/article/details/81605519