2、套接字22-24

套接字-socket编程:网络通信程序的编写

套接字:操作系统向上层提供的用于实现网络通信的统称

一、网络通信

网络通信就是网络中俩台主机之间的通信。(服务端的客户端)

服务端:针对用户请求提供服务的一段。是被动接收请求的一端。

客户端:用户的一端。是主动发出请求的一端。

二、传输层协议

  1. TCP:传输控制协议,面向连接,可靠传输,面向字节流。应用于对安全性高于实时性的场景——文件传输
  2. UDP:用户数据报协议,无连接,不可靠,面向数据报。应用于对安全性低于实时性的场景——视频、音频传输

三、UDP通信程序过程

1、服务端

1.创建套接字,在内核中创建socket结构体,将进程与网卡关联起来

2.为套接字绑定地址信息,给创建的套接字socket结构体描述源端地址信息(IP&PORT)

  1. 告诉系统,网卡收到的哪个数据应该交给我
  2. 当发送数据的时候使用绑定的地址信息作为源端地址信息(sip,sport)

3.接收数据,从socket的接收缓冲区中取出数据

4.发送数据,把要发送的数据放入缓冲区

5.关闭套接字

2、客户端

1.创建套接字

2.为套接字绑定地址信息(客户端不建议绑定主动地址:1绑定之后,程序只能启动一个。2客户端并不需要固定使用某个地址)

3.向服务器发送数据,发送之前如果socket没有绑定某个指定地址,则系统会自动选择合适的地址信息进行绑定

4.接收数据

5.关闭套接字

3、套接字接口介绍

1、创建套接字

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

domain:地址域类型                        AF_INET——IPv4地址域类型

type:套接字类型                             SOCK_STREAM —— 提供字节流传输服务 &                                                              SOCK_DGRAM——提供数据报传输服务

protocol :协议类型-0                       IPPROT_TCP     &      IPPROT_UDP

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

2、绑定地址信息

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

sockfd:创建套接字返回的描述符

addr:要绑定的地址信息

        ipv4:struct  sockaddr_in;

        ipv6:struct  sockaddr_in6;

addrlen:地址信息长度

返回值:成功返回0;失败返回-1

3、发送数据

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

4、接收数据

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

5、关闭套接字

int closes(int  fd);

四、字节序转换接口(了解)

小端才需要转换(因为默认大端)

#include<arpa/inet.h>
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);

五、TCP通信程序的编写

1、服务端

1.创建套接字

2.为套接字绑定地址信息

3.开始监听:告诉服务器,当前socket可以开始处理连接请求了,如果有客户端发送连接请求过来,服务器会为客户端创建一个新的socket,这个socket专门与指定客户端通信

4.获取新建连接:从已完成连接队列中取出一个新建套接字的描述符,这个描述符对应了指定的socket与指定客户端进行通信

5.收发数据

6.关闭套接字

2、客户端

1.创建套接字

2.为套接字绑定地址信息(不推荐)

3.向服务器发起连接:客户端的tcp套接字也会保存完整的五元组

4.收发数据

5.关闭套接字

3、接口介绍

1.创建套接字

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

domain:地址域类型                        AF_INET——IPv4地址域类型

type:套接字类型                             SOCK_STREAM —— 提供字节流传输服务

protocol :协议类型-0                       IPPROT_TCP   

2.绑定地址信息

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

sockfd:创建套接字返回的描述符

addr:要绑定的地址信息

        ipv4:struct  sockaddr_in;

        ipv6:struct  sockaddr_in6;

addrlen:地址信息长度

返回值:成功返回0;失败返回-1

3.客户端向服务端发起连接

int  connect(int  sockfd,   struct  sockaddr* srv_addr,   socklen_t  addrlen);

 srvaddr:服务器地址信息

返回值:成功返回0;失败返回-1

4.监听

int  listen(int  sockfd,  int  backlog );

sockfd:监听套接字描述符

backlog:服务端同一时间并发连接数

补充:syn泛洪攻击,一个恶意的客户端伪造IP地址,向服务器发送大量连接请求,而服务器端会为每一个客户端的新建连接创建一个新的套接字,这样就会让服务器资源耗尽,系统崩溃。

listen的第二个参数限制的是同一时间所能处理的新建连接请求的数量,而不是服务器所能建立的总体连接数量。

5.获取新建连接

从内核的已完成连接队列中取出一个完成连接的socket,并返回描述符

int  accept(int  listen_sockfd,  struct  sockaddr *addr,   socklen_t *addrlen );

listen_sockfd:监听套接字,决定了获取的是哪个监听套接字的新建连接

addr:一个地址结构的空间首地址,用于接收新连接的客户端地址信息

&addrlen:用于指定想要获取的地址长度,以及返回实际的长度

返回值:成功返回新建连接的描述符;失败返回-1

6.收发数据

因为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.关闭套接字

关闭套接字的时候,监听套接字基本不关闭,因为服务器要7*24小时在线。而是关闭通信套接字,不想和哪一个客户端通信了,就关闭哪一个通信套接字。

8.问题

当前的服务器在一个执行流中完成多个,有可能会导致流程阻塞的接口:

  • accept——获取新建连接,如果没有新建连接就会阻塞
  • recv——接收数据,如果没有数据recv就会阻塞

当前的单执行流中要操作的任务太多了,比如:获取新建连接,与指定的客户端通信。

但是因为当前没有业务,而且流程固定,因此服务器就有可能在没有新建连接的时候去accept,在客户端没有发送数据的时候recv。因此服务器与客户端只能通信一次,或者只能与一个客户端持续通信。

解决方案:采用多执行流并发处理

4、多执行流

一个执行流只负责一件事情:主执行流只负责获取新建连接。新建连接获取后,创建新的执行流与指定客户端通信,这样就算一个执行流阻塞了,也不会影响到其他执行流。

1.多进程

稳定——有新连接再创建新的进程。

  1. 注意僵尸进程的处理(自定义SIGCHLD信号处理)
  2. 父子进程数据独有,父子进程需要将新建套接字关闭

2.多线程

占用资源少——有新建连接则创建新的线程

  1. 线程之间共享文件描述符表,不要随便关闭描述符
  2. 线程入口函数传参需要注意参数的生命周期

猜你喜欢

转载自blog.csdn.net/weixin_56316833/article/details/131733371