Linux套接字编程

在上一篇博客中我们对网络中一些基本概念进行了简单阐述,这一篇博客我们来对套接字编程的内容进行初步了解。

目录

1.引入

2.UDP协议

2.1通信两端流程

2.1.1服务端流程

2.1.2客户端流程

2.2套接字相关操作接口

2.2.1创建套接字

2.2.2为套接字绑定地址信息

2.2.3发送数据

2.2.4接收数据

2.2.5关闭套接字

2.3字节序相关操作接口

2.3.1主机字节序到网络字节序转换

2.3.2网络字节序到主机字节序转换

2.3.3本地ip地址到网络ip地址转换

2.3.4网络ip地址到本地ip地址转换

2.4代码实现

1.引入

在上一篇博客中我们讲述了一些网络通信的基础内容,其中对于网络通信中所需要的:源端IP,对端IP,源端端口,对端端口和协议(五元组)进行了了解。我们明白:在网络通信中发起请求的成为客户端,另外被动接受请求的一方成为服务端。网络通信便是客户端与服务端之间的通信。

接下来我们锁讲述到的套接字编程单元,便是对网络通信程序的编写,其中有TCP协议网络程序的编程和UDP协议的网络程序编写。

TCP协议和UDP协议的基本内容和区别:

TCP是传输控制协议,一种面向连接,可靠传输,提供字节流传输的服务。其中面向连接是指:通信前先建立连接,确保双方都具有数据收发的能力;可靠传输是指:在网络情况正常下,保证数据可以安全有序的到达对端;字节流传输是指:传输单元以字节为单位,并不限制传输数据的大小,每次传输内容大小较为灵活。

UDP是用户数据报协议,一种无连接,不可靠,提供数据报传输的服务。其中无连接是指:只需要知道对方地址,便会直接发送数据,不需要建立连接;不可靠是指:不保证数据可以安全有序的到达对端,数据若丢失意味着无法寻找;数据报传输是指:传输交付以数据报为单元,不能传输半条数据或多条数据,并且存在最大长度限制。

总而言之,TCP协议适合传输安全性要求大于实时性要求的情况;UDP协议适合传输实时性要求大于安全性要求的情况。

2.UDP协议

2.1通信两端流程

2.1.1服务端流程

对于服务端而言,首先是创建套接字来建立当前进程和网卡之间的关联,实际上便是在内核中创建一个socket结构体;然后是为套接字绑定地址信息,为创建的socket结构体内部描述源端IP地址和端口,即告诉操作系统收到的数据中凡是对应绑定的地址和端口便交由此进程来处理;(其中所绑定的地址,必须是当前设备所拥有的地址)

其次是接收数据,即从内核中的socket结构体缓冲区中取出相关数据内容;再是发送数据,即将数据内容放到内核socket结构体中的发送缓冲区,并由系统决定具体的发送时间;最后当我们不需要进行通信时,便关闭套接字,释放资源。

2.1.2客户端流程

对于客户端而言,首先同样的是创建套接字,建立当前进程和网卡之间的关联;然后对客户端中套接字绑定地址信息并不重要,因为在实际通信当中客户端本身没有清楚自身地址的必要,它仅需要将需要发送的数据内容,放到创建的socket结构体的发送缓冲区之中,如果我们为其绑定固定地址反而会使得该客户端只能运行一个,还存在端口冲突的风险;

其次是发送数据,如果一开始为客户端绑定了地址信息,则源端地址便是绑定的地址,若未绑定,则操作系统会为其选择一个合适的地址和端口进行自动绑定;再是接收数据,即从对应的socket结构体中的接收缓冲区中读取内容;最后是通信结束,关闭套接字,释放资源。

2.2套接字相关操作接口

2.2.1创建套接字

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

socket接口便是创建套接字,其中domain是地址域类型(域间通信,ipv4通信,ipv6通信),不同的通信方式有不同的地址结构,关键字:AF_INET -- ipv4通信;

type是套接字类型,SOCK_STREAM:流式套接字,提供字节流传输服务,默认是TCP协议,SOCK_DGRAM:数据报套接字,提供数据报传输,默认协议是UDP协议。

protocol是协议类型,默认使用0,则表示使用套接字类型对应的默认协议,关键字:IPPROTO_TCP:值为6;IPPROTO_UDP:值为17。

返回值:创建成功则返回一个套接字描述符,失败则返回-1。

2.2.2为套接字绑定地址信息

int bind(int sockfd, struct sockaddr* addr, socklen_t len);

bind接口便是为套接字绑定相关的地址信息,其中sockfd是socket接口创建套接字成功后,所返回的套接字描述符;

addr是要绑定的地址信息,不同的地址域信息,代表不同的地址结构。不过无论何种地址结构,其中的前2个字节都保证是地址域信息,因此当我们传入地址结构的空间信息时,只需要取出其中前2字节的数据,便可清楚是何种地址结构,并通过与之相应的解析方式来获取其中的数据内容。

因此定义的addr是一个通用的地址结构,当我们需要进行相应的通信时,只需要将类型进行强转传入即可,如此便可实现一个接口绑定不同的地址结构。(ipv4使用struct socketaddr_in结构)

len便是对应的数据长度。

返回值:绑定地址信息成功则返回0,失败返回-1。

2.2.3发送数据

ssize_t sendto(int sockfd, void *buf, size_t dlen, int flag, struct sockaddr* peer, socklen_t alen);

 sendto接口便是发送数据接口,其中sockfd是socket返回的套接字描述符;buf是发送数据空间的起始地址;dlen是要发送数据长度,从buf开始发送dlen长度的数据;flag是发送方式,默认为0,即阻塞发送(发送缓冲区存满则阻塞);peer是对端地址信息,描述数据的发送对象;alen是对端地址信息长度。

返回值:发送成功则返回发送数据的实际字节长度,发送失败返回-1。

2.2.4接收数据

ssize_t recvfrom(int sockfd, void *buf, size_t dlen, int flag, struct sockaddr* peer, socklen_t *alen);

recvfrom接口便是接收数据接口,其中sockfd是socket返回的套接字描述符;buf是一块空间的起始地址,接收到的数据便会放到其中;dlen是想要接口的数据长度;flag是接收方式,默认为0,即阻塞接受(接受缓冲区中不存在数据则阻塞);

peer用于接受数据同时,得知数据的发送方地址,便于通信,因此这里的peer是一个输出参数,用于返回对端地址信息;alen是地址信息的长度,是一个输入输出参数,表示想要接收的地址信息长度,以及实际返回得到的地址信息长度。

返回值:接收成功则返回接受数据的实际字节长度,接收失败返回-1。

2.2.5关闭套接字

int close(int sockfd);

close接口便是用于关闭套接字,释放资源,其中sockfd便是具体的套接字描述符。

返回值:关闭成功返回0,失败返回-1。

2.3字节序相关操作接口

2.3.1主机字节序到网络字节序转换

uint32_t htonl(uint32_t val);
uint16_t htons(uint16_t val);

htonl和htons接口分别是32位下和16位下主机字节序到网络字节序的转换。

2.3.2网络字节序到主机字节序转换

uint32_t ntohl(uint32_t val);
uint16_t ntohs(uint16_t val);

ntohl和ntohs接口分别是32位下和16位下网络字节序到主机字节序的转换。

2.3.3本地ip地址到网络ip地址转换

typedef uint32_t in_addr_t;
in_addr_t inet_addr(const char* ip);

inet_addr接口便是将本地的十进制字符串ip地址,转换为网络字节序的整数ip地址,包括类型转换和字节序转换。

2.3.4网络ip地址到本地ip地址转换

struct in_addr {
    in_addr_t s_addr;
}
const char* inet_ntoa(struct in_addr addr);

inet_ntoa便是将网络字节序整数ip地址,转换为一个点分十进制字符串ip地址。

值得注意的是,ipv4的地址结构如下:

struct sockaddr_in {
    sa_family_t sin_family;//2字节的地址域类型
    in_port_t sin_port;//2字节的端口内容
    struct in_addr sin_addr;//4字节的IP地址内容
    char sin_zero[sizeof(struct sockaddr) - 8];
}

2.4代码实现

我们首先对服务端的操作流程进行代码实现,得到结果如下:

接下来我们为便于用户使用,也为了实现代码的泛用性,我们可以实现一个UdpSocket类来封装通信中的操作。

然后在客户端加以调用即可:

猜你喜欢

转载自blog.csdn.net/m0_56821563/article/details/130329491