Socket网络编程
1、Socket网络编程流程
2、Socket网络编程(客户端)
一、socket
socket是什么:
socket是一个套接字,也就是一个标准的通信接口
为什么要有socket:
在早期的计算机网络中,通信使用的是不同的协议,这些协议之间缺乏标准化的接口,使得应用程序的编写和移植变得非常困难。
怎么创建一个socket:
头文件:
sockets.h
函数:
int socket(int af, int type, int protocol);
参数:
(1) af (Address Family):IP 地址类型
常见的有 AF_INET(值为2) 和AF_INET6(值为10)。
AF_INET是IPv4地址。
AF_INET6是IPv6地址。
(2) type:传输类型
常见的有SOCK_STREAM(1),SOCK_DGRAM(2),SOCK_RAW(3)。
SOCK_STREAM是流式套接口,面向连接的,底层是TCP协议;
SOCK_DGRAM是数据报套接口,无连接的,底层是UDP协议;
SOCK_RAW是原始套接口,用户自己定义底层协议,也就是在使用原始套接口发送数据时,需要提供完整的传输层头部,比如TCP、UDP或ICMP头部等。
(3) protocol:传输协议
常见的有IPPROTO_TCP(6),IPPTOTO_UDP(17),IPPROTO_ICMP(1),IPPROTO_IP(0)。
IPPROTO_TCP是TCP传输协议;
IPPTOTO_UDP是UDP传输协议;
IPPROTO_ICMP是ICMP传输协议;
IPPROTO_IP是IP传输协议,也就是说系统根据其他的参数自动推演出应该使用什么协议。
返回:
成功返回socket的文件套接字,失败返回-1。
案例:
(1) 创建有一个IP 地址类型为IPv4,传输类型是流式套接口的套接字:
int socketTcp = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
(2) 创建一个一个IP 地址类型为IPv4,传输类型是数据报套接口的套接字:
int socketUdp = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
怎么关闭一个socket:
函数:
int shutdown(int sockfd,int howto);
作用:
函数用于关闭套接字的读或写功能。它可以在套接字关闭之前,确保所有的数据都已经发送或接收完毕。
参数:
(1) sockfd:套接字描述符
(2) howto:功能参数。
可为SHUT_RD(0),SHUT_WR(1),SHUT_RDWR(2)。
SHUT_RD:关闭套接字的读功能,即禁止从套接字中读取数据。
SHUT_WR:关闭套接字的写功能,即禁止向套接字中写入数据。
SHUT_RDWR:关闭套接字的读写功能,即禁止从套接字中读取数据和向套接字中写入数据。
函数:
int close(int sockfd);
作用:
关闭套接字的所有功能,包括读、写和监听。它会立即关闭套接字,并释放所有相关资源。如果在关闭套接字之前还有未发送的数据,这些数据会被丢弃。
参数:
(1) sockfd:套接字描述符。
使用建议:
在关闭套接字时,应该先关闭读功能,再关闭写功能,最后再使用 close() 函数关闭套接字本身。
二、connect
connect是什么:
connect是一个在标准库里的API函数
connet有什么用:
顾名思义,connet函数起连接作用,帮助客户端连接服务端
怎么使用connect:
头文件:
sockets.h
函数:
int connect(int sock, struct sockaddr *addr, socklen_t addrlen);
参数:
(1)sock:套接字的文件描述符
(2)addr:sockaddr结构体变量的指针。
(3)addrlen:addr变量的大小
返回:
成功返回0,失败返回-1,并且设置errno。
三、套接字地址结构体
(1)sockaddr
sockaddr是什么:
sockaddr是一种通用的套接字地址结构体,也就是说可以用于保存多种类型的IP和端口,但在实际开发中由于该结构体没有具体的字段来存储相应的信息,所以一般都会使用更加详细的套接字地址结构体去赋值,然后再强制类型转化回去。
sockaddrx的原型:
struct sockaddr
{
u8_t sa_len; //长度(一个字节)
sa_family_t sa_family; //地址类型(一个字节)
char sa_data[14]; // sa_family 类型的IP地址和端口号
};
(2)sockaddr_in
sockaddr_in是什么:
sockaddr_in 是一个用于表示 IPv4 地址的套接字地址结构体,总字节数和sockaddr一样,可以进行强制类型转化成sockaddr。
sockaddr_in的原型:
struct in_addr
{
in_addr_t s_addr; // 4个字节
};
struct sockaddr_in
{
u8_t sin_len; //长度(一个字节)
sa_family_t sin_family; //地址类型(一个字节,一般为AF_INET)
in_port_t sin_port; //端口号(两个字节)
struct in_addr sin_addr; //IP地址(4个字节)
char sin_zero[8]; //不使用(8个字节)
};
字段解释:
sin_len:这个结构体的长度
一般不写也可以。
sin_family:IP地址类型
常见的有 AF_INET(2) 和 AF_INET6(10),这里使用AF_INET。
sin_port:端口号
这里使用的是网络字节序(也就是大端字节序,主机字节序指正常的设备中存储数据的方式,可能是大端字节序也可能是小端字节序,所以在给端口号赋值的时候要注意使用函数将主机字节序转化网络字节序)。
sin_addr:IP地址
这里使用的IP地址是32位的大端网络字节序(我们一般的IP地址使用的是点分十进制的字符串,所以要使用函数将点分十进制的字符串转化为32位数据)。
sin_zero[8]:闲置的
该字段只是为了兼容sockaddr_in字节数和sockaddr一样,可忽略。
使用到的转化函数:
(1)uint16_t htons(uint16_t host);
功能:
将 16 位无符号整数从主机字节序转换为网络字节序,用于上述端口号的转换。
参数:
host,主机字节序的端口号
返回:
网络字节序的端口号
其他同类型函数:
Htonl():
将 32 位无符号整数从主机字节序转换为网络字节序。
ntohl():
将 32 位无符号整数从网络字节序转换为主机字节序。
ntohs():
将 16 位无符号整数从网络字节序转换为主机字节序。
(2)uint32_t inet_addr(const char *ip);
功能:
将点分十进制的IP地址字符串转化成网络字节序的32位数据。
参数:
ip,点分十进制的IP地址字符串。
返回:
网络字节序的32位数据。
其他同类型函数:
inet_aton():
将点分十进制的IPv4的IP地址字符串转换32位大端网络字节序整数。
inet_ntoa():
将一个网络字节序的32位整数转换为点分十进制的IPv4的IP地址字符串。
inet_pton():
将点分十进制的IPv4或IPv6的IP地址字符串转换32位大端网络字节序整数。
inet_ntop():
将一个网络字节序的32位整数转换为点分十进制的IPv4或IPv6的IP地址字符串。
(3)sockaddr_in6
sockaddr_in6是什么:
是一个用于表示 IPv6 地址的套接字地址结构体,总字节数和sockaddr不一样,但在使用中也可以进行强制类型转化成sockaddr。
sockaddr_in6的原型:
struct in6_addr
{
union
{
u32_t u32_addr[4];
u8_t u8_addr[16];
}
};
struct sockaddr_in6
{
u8_t sin6_len; //长度(一个字节)
sa_family_t sin6_family; //地址类型(一个字节,一般为AF_INET6)
in_port_t sin6_port; //端口号(两个字节)
u32_t sin6_flowinfo; //IPv6流信息(四个字节)
struct in6_addr sin6_addr; //IPv6地址(16个字节)
u32_t sin6_scope_id; //作用域的接口集(四个字节)
};
四、发送函数
(1)send
send是什么:
send是一个在标准库里的API函数。
send有什么用:
send把要发送的数据写到socket的缓存空间里。
send的工作逻辑:
(1) 判断要发送的数据大小是否超过socket的缓存空间的大小,如果超过,返回错误,如果没有超过,进行下一步。
(2) 判断socket中有没有正在发送的数据,如果有,就等待发送完再进入下一步,如果没有,进入下一步。
(3) 在没有正在发送数据的前提下,判断是否有socket缓存空间的大小是否足够,如果够,就往缓存中写入数据,如果不够,等缓存发送完在写入
(4) 如果写入过程中发生错误导致无法写入,会返回错误,否则返回发送的字节数
(5) 如果在已经写入缓存完成的情况下,socket自动发送遇到未知错误导致无法发送时在下一次使用与socket相关的函数时会报错。
send原型:
头文件:
sockets.h
函数:
ssize_t send(int s, const void * dataptr, size_t size, int flags);
参数:
(1) s: 套接字的文件描述符
(2) dataptr: 要发送的数据
(3) size: 发送数据长度
(4) flags: 一般置0
返回:
成功返回发送的字节数,失败返回-1,并且设置errno。
(2)write
write是什么:
write是一个在标准库里的API函数。
write有什么用:
把要发送的数据写到socket的缓存空间里,和send区别为send函数需要指定flags标识。
write原型:
头文件:
sockets.h
函数:
ssize_t write(int s, const void *data, size_t size);
参数:
(1) s: 套接字的文件描述符
(2) data: 要发送的数据
(3) size: 发送数据长度
返回:
成功返回发送的字节数,失败返回-1,并且设置errno。
(3)sendto
sendto是什么:
sendto是一个在标准库里的API函数。
sendto有什么用:
sendto把要发送的数据写到socket的缓存空间里,和send区别为sendto函数在参数中带有目标套接字的地址,所以哪怕不进行connet这一步也可以使用sendto函数发送数据,一般用于UDP传输协议的socket中。在TCP传输协议的socket中,一般要进行connet,这时目标套接字地址参数为NULL即可。
sendto原型:
头文件:
sockets.h
函数:
ssize_t sendto(int s,const void *dataptr,size_t size,int flags,const struct sockaddr *to,socklen_t tolen)
参数:
(1)s: 套接字的文件描述符
(2)dataptr: 要发送的数据
(3)size: 发送数据长度
(4)flags: 一般置0
(5)to: 目标套接字地址
(6)tolen: 目标套接字地址长度
返回:
成功返回发送的字节数,失败返回-1,并且设置errno。
(4)sendmsg
sendmsg是什么:
sendmsg是一个在标准库里的API函数
sendmsg有什么用:
sendmsg函数可以批量发送数据,有没有connet都可以使用。
sendmsg原型:
头文件:
sockets.h
函数:
ssize_t sendmsg(int s,const struct msghdr *message,int flags);
参数:
(1)s: 套接字的文件描述符
(2)message: 要发送的数据的信息结构体
(3)flags: 一般置0
msghdr结构体解析:
struct iovec
{
void *iov_base; //要发送的某一组数据的首地址
size_t iov_len; //要发送的某一组数据的长度
};
struct msghdr
{
void *msg_name;
socklen_t msg_namelen;
struct iovec *msg_iov;
int msg_iovlen;
void *msg_control;
socklen_t msg_controllen;
int msg_flags;
};
(1) msg_name:放目标套接字的地址信息
(2) msg_namelen:目标套接字地址你信息的长度
(3) msg_iov:要发送数据的地址和长度信息的数组
(4) msg_iovlen:有多少组数据要发送
(5) msg_control:控制数据,可不写
(6) msg_controllen:控制数据长度,可不写
(7) msg_flags:标识符,一般为0
(5)writev
writev是什么:
writev是一个在标准库里的API函数。
writev有什么用:
writev函数可以批量发送数据,原理和sendmsg函数相似,但有connet才可以使用。
sendmsg原型:
头文件:
sockets.h
函数:
ssize_t writev(int s, const struct iovec *iov, int iovcnt)
参数:
(1)s: 套接字的文件描述符
(2)iov: 要发送的数据组结构体
(3)iovcnt: 要发送的数据组的数量
五、接收函数
(1)recv
recv是什么:
recv是一个在标准库里的API函数。
recv有什么用:
recv可以将socket接收缓存中的数据复制到指定的接收空间中。
recv的工作逻辑:
(1) 判断socket是否在发送数据,如果是则等待发送完成再进行下一步,如果不是进行下一步。
(2) 判断socket是否在接收数据,如果是则等待接收完成再进行下一步,如果不是进行下一步。
(3) 判断socket的接收缓存中是否有数据,如果有则将接收缓存里面的数据复制到本地缓存空间(如果本地缓存空间没有接收缓存中数据那么大,那要进行多次接收),如果没有就继续等待。
recv原型:
头文件:
sockets.h
函数:
static ssize_t recv(int s, void * mem, size_t len, int flags);
参数:
(1)s: 套接字的文件描述符
(2)mem: 接收空间
(3)len: 接收空间的大小
(4)flags: 一般为0
返回:
成功返回接收的字节数,失败返回-1,并且设置errno(在接收过程中对端关闭或网络中断,返回0)。
(2)read
read是什么:
read是一个在标准库里的API函数。
read有什么用:
read可以将socket接收缓存中的数据复制到指定的接收空间中,与recv的区别是read函数不需要指定flags标识。
read原型:
头文件:
sockets.h
函数:
ssize_t read(int s, void *mem, size_t len)
参数:
(1)s: 套接字的文件描述符
(2)mem: 接收空间
(3)len: 接收空间的大小
返回:
成功返回接收的字节数,失败返回-1,并且设置errno(在接收过程中对端关闭或网络中断,返回0)。
(3)recvfrom
recvfrom是什么:
recvfrom是一个在标准库里的API函数。
recvfrom有什么用:
recvfrom可以将socket接收缓存中的数据复制到指定的接收空间中,与recv相比多了发送端的地址信息,一般在UDP协议下使用。
recvfrom原型:
头文件:
sockets.h
函数:
ssize_t recvfrom(int s, void *mem, size_t len, int flags,
struct sockaddr *from, socklen_t *fromlen)
参数:
(1)s: 套接字的文件描述符
(2)mem: 接收空间
(3)len: 接收空间的大小
(4)flags: 一般为0
(5)from: 发送端地址信息
(6)fromlen: 发送端地址信息长度
返回:
成功返回接收的字节数,失败返回-1,并且设置errno。
3、Socket网络编程(服务端)
一、socket
参考Socket网络编程(客户端)
二、bind
bind是什么:
connect是一个在标准库里的API函数。
bind有什么用:
bind() 函数将套接字与特定的 IP 地址以及端口绑定,一般服务端会使用到,而客户端以服务器套接字绑定的IP、端口作为目标IP、端口进行通信。
怎么使用bind:
头文件:
sockets.h
函数:
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
参数:
(1) sock:套接字的文件描述符
(2) addr:sockaddr结构体变量的指针
(3) addrlen:addr变量的大小
三、listen
listen是什么:
listen是一个在标准库里的API函数。
listen有什么用:
listen函数起监听socket的作用,也就是说当有客户端调用connect()发出连接请求时,服务器端可以知道有这个请求(这时服务端并没有接收这个请求)。
怎么使用listen:
头文件:
sockets.h
函数:
int listen(int s, int backlog);
参数:
(1)s: 套接字的文件描述符
(2)backlog: socket可以排队的最大连接个数
返回:
成功返回0,失败返回-1,并且设置errno
四、accept
accept是什么:
accept是一个在标准库里的API函数。
accept有什么用:
在listen监听到有客户端发起连接请求后,accept函数将客户端信息绑定到一个新的socket上,通过返回值将新的socket的文件描述符传出(因为一个服务端socket会被很多客户端连接,而原始socket的文件描述符只有一个,所以要为每一个单独的客户端创建一个自己的socket文件描述符,后面和这个客户端的读写数据都是用这个文件描述符),同时将客户端的IP地址和端口号保存在第二个参数中。
怎么使用accept:
头文件:
socket.h
函数:
int accept(int s, struct sockaddr *addr, socklen_t *addrlen)
参数:
(1)s: 套接字的文件描述符
(2)addr: 用于储存接受到的客户端的网络信息的结构体
(3)addrlen: addr结构体长度
返回:
发起请求的客户端的socket的文件描述符
五、read
参考Socket网络编程(客户端)
六、write
参考Socket网络编程(客户端)
七、close
参考Socket网络编程(客户端)