文章目录
- 前言
- TCP协议和UDP协议
- 网络字节序
- socket接口
- sockaddr结构
- 1.创建套接字 cs
- 2.绑定端口号 s
- 3.监听socket s
- 4.接受请求 s
- 5.建立连接 c
- 地址转换函数
- 字符串转in_addr
- in_addr转字符串
- recvfrom和sendto
前言
上篇文章我们学习了计算机网络分层,了解了网络通信的本质是进程间通信,正式通过套接字的方式进行通信。
TCP协议和UDP协议
TCP/UDP协议是工作在传输层的协议,负责数据的传输,主要提供数据传输的策略,而TCP和UDP就是两种不同的传输数据策略。
-
TCP(传输控制协议)
- 面向连接
- 可靠传输
- 面向字节流
-
UDP(用户数据报协议)
- 无连接
- 不可靠传输
- 面向数据报
注意,这里提到的可靠和不可靠不是说TCP好于UDP,而是他们传输的特性,在说明具体协议的时候我们再详谈。
由于UDP协议不面向连接,所以简单是他的巨大优势,今天我们先来详细学习一下简单的UDP套接字。
网络字节序
在学习C语言的时候,我们指定内存中的多字节数据相对于地址有大小端之分,网络流同样也有大端小端之分。
1、发送主机一般将发送缓冲区的数据从低到高的顺序发出
2、接收主句一般把收到的数据按照从低到高的顺序保存
3、所以,网络数据流的地址规定为:先发出的数据是低地址,后发出的是高地址
4、TCP/IP协议规定:网络数据流应当采用大端字节序,即:低地址高字节
接口:
#include <arpa/inet.h>uint16_t htons(uint16_t hostshort)
uint16_t ntohs(uint16_t netshort)
socket接口
socket接口就像我们之前用过的系统调用,是操作系统级别的接口。
sockaddr结构
1、ipv4和ipv6的地址类型分别定义为AF_INET和AF_INET6,位于netinet/in.h中,在使用socketAPI的时候,可以先把对应的sockaddr_in结构转换成sockaddr,在接口内部,会根据16位地址类型进行不同类型的操作,这是C语言早期多态性的体现
2、socket套接字不仅可以网络通信,由于sockaddr转换+16位地址类型存在,socketAPI也支持进程间通信
1.创建套接字 cs
int socket(int domain, int type,int protocol)
参数:
- domain:ipv4写为AF_INET
- type:udp为:SOCK_DGRAM;tcp为:SOCK_STREAM
- protocol:设置为0表示默认
2.绑定端口号 s
int bind(int socket,const struct sockaddr* address,socklen_t address_len);
实例:
struct sockaddr_in local;local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = INADDR_ANY;
顺便一提,云服务是不允许我们bind指定IP地址的,因此我们使用INADDR_ANY绑定本主机的任意IP。
3.监听socket s
int listen(int socket, int backlog);
参数:
- socket:要监听的套接字
- backlog:最长等待队列
4.接受请求 s
int accept(int socket, struct sockaddr* address,socklen* address_len);
参数:
- socket :套接字
- address :用于存储客户端的地址信息
- address_len: 输入输出型参数,作为输入他指定了address指向缓冲区的长度,作为输出,会被设置为实际地址的长度,比如输入时有10个字节,但客户端实际传入只有8个字节,这个参数会被修改成八字节。
5.建立连接 c
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
参数:
- sockfd:客户端创建的文件描述符
- addr:用于指定服务器端的地址信息
- addrlen:addr的实际大小
地址转换函数
我们习惯使用点分十进制的方式来记录ip地址,例如101.34.23.11,但网络中是用32个比特位来记录ip地址的,因此我们需要将点分十进制风格的ip地址转换为网络地址
字符串转in_addr
#include <arpa/inet.h>int inet_aton(const char* strptr,struct in_addr* addrptr);struct in_addr
{in_addr_t s_addr; // 存储32位的IPv4地址
};
参数:
- strptr:要转换的ip字符串
- addrptr:存取转换后的32位地址
返回值:
- 转换成功返回1,失败返回0
int inet_pton(int family,const char* strptr,void* addrptr);
参数:
- family:要转换的协议族
- strptr:要转换的ip字符串(4、6都支持)
- addrptr:转换后放入的缓冲区
返回值:
- 成功返回1
- 失败返回0
- 无效返回-1,并设置errno
in_addr_t inet_addr(const char* strptr);
参数:
- strptr:要转换的ip字符串
返回值:
- 成功返回32位ip地址
- 失败返回INADDR_NONE
in_addr转字符串
char* inet_ntoa(struct in_addr inaddr);
参数:
- inaddr :把32位的ip地址转化为点分十进制
int inet_pton(int family,const void* addrptr,char* strptr);
参数:
- family:协议族
- addrptr:存放字符串ip的指针
- strptr:存放转换后二进制的缓冲区
返回值:
- 成功返回1,失败返回-1
recvfrom和sendto
#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);
参数:
- sockfd:接收数据的套接字
- buf:缓冲区指针,指向存放接收到的数据
- len:缓冲区的大小,期望收到的最大字节
- flags:接收数据的标志,可以为0或者特定接收选项
- src_addr:存放发送方套接字地址的结构体指针,可以为NULL
- addrlen:指定对方结构体的大小
注意:recv里是不带后两个参数的,因为TCP是面向连接的,不需要读取后面两个参数。
#include <sys/types.h>
#include <sys/socket.h>ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
- sockfd:套接字描述符
- buf:要发送的数据
- len:发送的大小
- flags:发送的方式
- dest_addr:目标主机的套接字信息
- addrlen:大小
同样的,send不需要后面两个参数。