socket编程
服务端
- 创建套接字。
(通过套接字使进程与网卡建立联系。在内核中创建了struct_socket
结构体,其中包含了实质为缓冲区的发送队列与接收队列,每个端口都有自己独立的一个缓冲区用来保存等待发送的数据包) - 为套接字绑定地址信息。(
ip
+port
) - 接收数据。
(服务端一定不会先发送数据,因为不知道客户端地址) - 发送数据。
- 关闭套接字。
【缓冲区
讲解】:
客户端发送数据到服务端,根据冯诺依曼体系结构,网卡拿到数据,数据处理之前要放入内存(缓冲区)。那么多个端口数据都到达网卡(mysql
数据、ssh
数据等)。
此时如果是一个缓冲区,那么其中会有很多繁杂的数据,如果再去读取数据就要进行筛选,速度性能就下降了。
此时这里存在空间换时间的思想,每创建一个socket
都会有一个对应的缓冲区。为socket
绑定地址信息的过程,实际上就是为socket
标记,标记的是其存放的哪个端口的数据。当网卡接受到数据,根据这个标记放到相应端口的缓冲区。取出数据处理时,每个端口也是只取出自己端口的数据。
客户端
- 创建套接字。
- 为套接字绑定地址信息。
(通常客户端不推荐用户手动绑定地址信息,因为有可能此时端口被占用,一般在下一步选择地址与端口,但是手动绑定也是可以的~) - 发送数据。
(如果用户socket
还没有绑定地址,此时操作系统会选择一个合适的地址端口进行绑定) - 接收数据。
- 关闭套接字。
常见端口号介绍:
ssh
:22mysql
:3306http
:80https
:443
接口介绍
创建套接字:socket()
int socket(int domain, int type, int protocol);
domain
:地址域。
选项:
AF_LOCAL Local communication(本地地址域) unix(7)
AF_INET IPv4 Internet protocols(IPV4 网络协议地址域) ip(7)
AF_INET6 IPv6 Internet protocols(IPV6 网络协议地址域) ipv6(7)
AF_PACKET Low level packet interface(低级包接口(原始数据包)) packet(7)
...
type
:套接字类型。
选项:
SOCK_STREAM 流式套接字(二进制数据流) 默认协议TCP,不可以支持UDP。
SOCK_DGRAM 数据报套接字 默认协议UDP,不可以支持TCP。
...
protocol
:协议类型。
选项:
0 使用套接字默认协议
6 / IPPROTO_TCP(宏) tcp协议
17 / IPPROTO_UDP(宏) udp协议
...
- 返回值:套接字操作句柄(文件描述符)。
失败返回-1
。
绑定地址:bind()
int bind(
int sockfd,
struct sockaddr *my_addr,
socklen_t addrlen
);
sockfd
:创建套接字返回的文件描述符。my_addr
:地址信息,他是一个(struct sockaddr *
)类型的结构数据。给定这么一个结构体是为了通用,既可以使用IPV4
方式,也可以使用IPV6
方式存储数据。addrlen
:地址信息长度。- 返回值:成功返回
0
,失败返回-1
。
[注] sockaddr结构体
需要包含头文件:
< netinet/in.h >
sockaddr
结构体的定义如下:
struct sockaddr {
unsigned short sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
};
说明:
sa_family
:是2字节的地址家族,地址域。一般都是“AF_xxx”
的形式,它的值包括三种:AF_INET
,AF_INET6
和AF_UNSPEC
。只需要取出前两个字节,就可以知道到底是IPV4
的地址还是IPV6
的地址,从而判定到底通过sockaddr_in
还是sockaddr_in6
进行解析~
- 如果指定
AF_INET
,那么函数就不能返回任何IPV6
相关的地址信息; - 如果仅指定了
AF_INET6
,则就不能返回任何IPV4
地址信息。 AF_UNSPEC
则意味着函数返回的是适用于指定主机名和服务名且适合任何协议族的地址。如果某个主机既有AAAA
记录(IPV6
)地址,同时又有A
记录(IPV4
)地址,那么AAAA
记录将作为sockaddr_in6
结构返回,而A
记录则作为sockaddr_in
结构返回- 通常用的都是
AF_INET
。
sockaddr_in
结构体的定义如下:
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
sin_family
:指代协议族,在socket
编程中只能是AF_INET
。sin_port
:存储端口号(使用网络字节顺序)。sin_addr
:存储IP地址,使用in_addr
这个数据结构。sin_zero
:是为了让sockaddr
与sockaddr_in
两个数据结构保持大小相同而保留的空字节。
而其中in_addr
结构的定义如下:
typedef struct in_addr {
union {
struct{ unsigned char s_b1,s_b2, s_b3,s_b4;} S_un_b;
struct{ unsigned short s_w1, s_w2;} S_un_w;
unsigned long S_addr;
} S_un;
} IN_ADDR;
阐述下in_addr
的含义,很显然它是一个存储ip
地址的共用体有三种表达方式:
- 第一种用四个字节来表示IP地址的四个数字(点分十进制);
- 第二种用两个双字节来表示IP地址;
- 第三种用一个长整型来表示IP地址。
给in_addr
赋值的一种最简单方法是使用inet_addr()
函数,它可以把一个代表IP地址的字符串赋值转换为in_addr
类型,如addrto.sin_addr.s_addr=inet_addr("192.168.0.2");
其反函数是inet_ntoa()
,可以把一个in_addr
类型转换为一个字符串。
接收数据:recvfrom()
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
:发送端的地址信息。addrlen
:地址信息长度。(输入输出型参数)
(不但要指定想要接受的数据长度,还要保存实际接收数据的长度)- 返回值:实际接收到的数据长度。
失败返回-1
发送数据:sendto()
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
:0(默认阻塞接收)。dest_addr
:目的端的地址信息。addrlen
:地址信息长度。- 返回值:实际接受到的数据长度
失败返回-1
关闭套接字:close()
int close(int fd);
close()
接口为:关闭文件描述符。
所以socket
在内核创建结构、与网卡创建关联等,最终返回的是一个文件描述符。(操作句柄)
用C++封装一个UDP的socket类,实现客户端与服务端程序的udp通信
【 https://blog.csdn.net/qq_42351880/article/details/90302109 】