套接字-socket编程:网络通信程序的编写
套接字:操作系统向上层提供的用于实现网络通信的统称
网络通信就是网络中俩台主机之间的通信。(服务端的客户端)
服务端:针对用户请求提供服务的一段。是被动接收请求的一端。
客户端:用户的一端。是主动发出请求的一端。
1.创建套接字,在内核中创建socket结构体,将进程与网卡关联起来
2.为套接字绑定地址信息,给创建的套接字socket结构体描述源端地址信息(IP&PORT)
3.接收数据,从socket的接收缓冲区中取出数据
4.发送数据,把要发送的数据放入缓冲区
5.关闭套接字
1.创建套接字
2.为套接字绑定地址信息(客户端不建议绑定主动地址:1绑定之后,程序只能启动一个。2客户端并不需要固定使用某个地址)
3.向服务器发送数据,发送之前如果socket没有绑定某个指定地址,则系统会自动选择合适的地址信息进行绑定
4.接收数据
5.关闭套接字
int socket(int domain, int type, int protocol );
domain:地址域类型 AF_INET——IPv4地址域类型
type:套接字类型 SOCK_STREAM —— 提供字节流传输服务 & SOCK_DGRAM——提供数据报传输服务
protocol :协议类型-0 IPPROT_TCP & IPPROT_UDP
返回值:成功返回一个套接字描述符,失败返回-1
int bind(int sockfd, const struct sockaddr*addr, socklen_t addrlen );
sockfd:创建套接字返回的描述符
addr:要绑定的地址信息
ipv4:struct sockaddr_in;
ipv6:struct sockaddr_in6;
addrlen:地址信息长度
返回值:成功返回0;失败返回-1
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr* dest_addr, socklen_t addrlen );
sockfd:创建套接字返回的描述符
buf:要发送的数据的空间首地址
len:要发送的数据长度
flag:选项标志0-默认阻塞发送
dest_addr:地址信息长度
addrlen:地址信息长度
返回值:成功返回实际发送的数据字节长度;失败返回-1
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t *addrlen );
sockfd:创建套接字返回的描述符
buf:一块空间首地址,用于存放从内核获取到的数据
len:要获取的数据长度
flag:选项标志0-默认阻塞发送
src_addr:地址信息长度
*addrlen:这是一个输入输出型参数,指定想要多长地址,返回实际长度
返回值:成功返回实际获取的数据字节长度;失败返回-1
int closes(int fd);
小端才需要转换(因为默认大端)
#include
uint32_t htonl(uint32_t hostlong);//主机-->网络字节序 针对类型32位
uint32_t ntonl(uint32_t netlong);//网络-->主机字节序 针对类型32位
uint16_t htonls(uint16_t hostshort);//主机-->网络字节序 针对类型16位
uint16_t ntonls(uint16_t netshort);//网络-->主机字节序 针对类型16位
//"192.168.1.1"这是IP地址的一种点分十进制形式
//下面俩个只能转换IPv4的地址
in_addr_t inet_addr(const char* cp);//字符串IP地址转换为网络字节序的整数IP
char* inet_ntoa(struct in_addr addr);//网络字节序整数IP到字符串IP地址转换
const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);
int inet_pton(int af, const char* src, void* dst);
1.创建套接字
2.为套接字绑定地址信息
3.开始监听:告诉服务器,当前socket可以开始处理连接请求了,如果有客户端发送连接请求过来,服务器会为客户端创建一个新的socket,这个socket专门与指定客户端通信
4.获取新建连接:从已完成连接队列中取出一个新建套接字的描述符,这个描述符对应了指定的socket与指定客户端进行通信
5.收发数据
6.关闭套接字
1.创建套接字
2.为套接字绑定地址信息(不推荐)
3.向服务器发起连接:客户端的tcp套接字也会保存完整的五元组
4.收发数据
5.关闭套接字
int socket(int domain, int type, int protocol );
domain:地址域类型 AF_INET——IPv4地址域类型
type:套接字类型 SOCK_STREAM —— 提供字节流传输服务
protocol :协议类型-0 IPPROT_TCP
int bind(int sockfd, const struct sockaddr*addr, socklen_t addrlen );
sockfd:创建套接字返回的描述符
addr:要绑定的地址信息
ipv4:struct sockaddr_in;
ipv6:struct sockaddr_in6;
addrlen:地址信息长度
返回值:成功返回0;失败返回-1
int connect(int sockfd, struct sockaddr* srv_addr, socklen_t addrlen);
srvaddr:服务器地址信息
返回值:成功返回0;失败返回-1
int listen(int sockfd, int backlog );
sockfd:监听套接字描述符
backlog:服务端同一时间并发连接数
补充:syn泛洪攻击,一个恶意的客户端伪造IP地址,向服务器发送大量连接请求,而服务器端会为每一个客户端的新建连接创建一个新的套接字,这样就会让服务器资源耗尽,系统崩溃。
listen的第二个参数限制的是同一时间所能处理的新建连接请求的数量,而不是服务器所能建立的总体连接数量。
从内核的已完成连接队列中取出一个完成连接的socket,并返回描述符
int accept(int listen_sockfd, struct sockaddr *addr, socklen_t *addrlen );
listen_sockfd:监听套接字,决定了获取的是哪个监听套接字的新建连接
addr:一个地址结构的空间首地址,用于接收新连接的客户端地址信息
&addrlen:用于指定想要获取的地址长度,以及返回实际的长度
返回值:成功返回新建连接的描述符;失败返回-1
因为tcp通信的套接字中已经包含了完整的五元组了,因此收发数据的时候不需要指定或者获取对端的地址信息了
ssize_t recv(int sockfd, char *buf, int len, int flag)
sockfd:新建套接字描述符
buf:空间首地址用于存放接收的数据
len:要获取的数据长度
flag:选项标志0-默认阻塞发送
返回值:成功返回实际发送的数据字节长度;失败返回-1;断开连接返回0
ssize_t send(int sockfd, char *data, int len, int flag)
sockfd:新建套接字描述符
data:空间首地址用于存放接收的数据
len:要获取的数据长度
flag:选项标志0-默认阻塞发送
返回值:成功返回实际发送的数据字节长度;失败返回-1
关闭套接字的时候,监听套接字基本不关闭,因为服务器要7*24小时在线。而是关闭通信套接字,不想和哪一个客户端通信了,就关闭哪一个通信套接字。
当前的服务器在一个执行流中完成多个,有可能会导致流程阻塞的接口:
当前的单执行流中要操作的任务太多了,比如:获取新建连接,与指定的客户端通信。
但是因为当前没有业务,而且流程固定,因此服务器就有可能在没有新建连接的时候去accept,在客户端没有发送数据的时候recv。因此服务器与客户端只能通信一次,或者只能与一个客户端持续通信。
解决方案:采用多执行流并发处理
一个执行流只负责一件事情:主执行流只负责获取新建连接。新建连接获取后,创建新的执行流与指定客户端通信,这样就算一个执行流阻塞了,也不会影响到其他执行流。
1.多进程
稳定——有新连接再创建新的进程。
2.多线程
占用资源少——有新建连接则创建新的线程