socket编程

socket编程(一)

socket介绍

1、概述

socket 是一种 IPC 方法,它允许位于同一主机(计算机)或使用网络连接起来的不同主机上的应用程序之间交换数据。

1.1 通信domain

socket存在于一个通信 domain 中,它确定:

  • 识别出一个 socket 的方法(即 socket“地址”的格式);
  • 通信范围(即是在位于同一主机上的应用程序之间还是在位于使用一个网络连接起来的不同主机上的应用程序之间)。

现代操作系统至少支持下列 domain。

  • UNIX (AF_UNIX) domain 允许在同一主机上的应用程序之间进行通信。( POSIX.1g 使用名称 AF_LOCAL 作为 AF_UNIX 的同义词 )
  • IPv4 (AF_INET) domain 允许在使用因特网协议第 4 版( IPv4)网络连接起来的主机上的应用程序之间进行通信。
  • IPv6 (AF_INET6) domain 允许在使用因特网协议第 6 版(IPv6)网络连接起来的主机上的应用程序之间进行通信。

在一些代码中可能会看到名称诸如 PF_UNIX 而不是AF_UNIX 的常量,AF 表示“地址族( address family)”, PF 表示“协议族( protocol family)”。在一开始的时候,设计人员相信单个协议族可以支持多个地址族。但在实践中,没有哪一个协议族能够支持多个已经被定义的地址族, 并且所有既有实现都将 PF_常量定义成对应的 AF_常量的同义词。

Domain 执行的通信 应用程序间的通信 地址格式 地址结构
AF_UNIX 内核中 同一主机 路径名 sockaddr_un
AF_INET 通过 IPv4 通过 IPv4 网络连接起来的主机 32 位 IPv4 地址+16 位端口号 sockaddr_in
AF_INET6 通过 IPv6 通过 IPv6 网络连接起来的主机 128 位 IPv6 地址+16 位端口号 sockaddr_in6

1.2 socket类型

每个 socket 实现都至少提供了两种 socket:流和数据报。这两种 socket 类型在 UNIX 和Internet domain 中都得到了支持

属性 socket 类型
数据报
可靠地递送?
消息边界保留?
面向连接?
流 socket( SOCK_STREAM)提供了一个可靠的双向的字节流通信信道
  • 可靠的:表示可以保证发送者传输的数据会完整无缺地到达接收应用程序(假设网络链接和接收者都不会崩溃)或收到一个传输失败的通知。
  • 双向的:表示数据可以在两个 socket 之间的任意方向上传输。
  • 字节流:表示与管道一样不存在消息边界的概念。

一个流 socket 类似于使用一对允许在两个应用程序之间进行双向通信的管道,它们之间的差别在于( Internet domain) socket 允许在网络上进行通信。

数据报 socket( SOCK_DGRAM)允许数据以被称为数据报的消息的形式进行交换。在数据报 socket 中,消息边界得到了保留,但数据传输是不可靠的。消息的到达可能是无序的、重复的或者根本就无法到达。

1.3 socket系统调用

关键的 socket 系统调用包括以下几种。

  • socket()系统调用创建一个新 socket。
  • bind()系统调用将一个 socket 绑定到一个地址上。通常,服务器需要使用这个调用来将其 socket绑定到一个众所周知的地址上使得客户端能够定位到该 socket 上。
  • listen()系统调用允许一个流 socket 接受来自其他 socket 的接入连接。
  • accept()系统调用在一个监听流 socket 上接受来自一个对等应用程序的连接, 并可选地返回对等 socket 的地址。
  • connect()系统调用建立与另一个 socket 之间的连接。

socket I/O 可以使用传统的 read()和 write()系统调用或使用一组 socket 特有的系统调用(如send()、 recv()、 sendto()以及 recvfrom())来完成。在默认情况下,这些系统调用在 I/O 操作无法被立即完成时会阻塞。

2、创建一个socket

socket()系统调用创建一个新的socket。

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

       domain 参数指定了 socket 的通信 domain。 type 参数指定了 socket 类型。创建流 socket 时会被指定为SOCK_STREAM,创建数据报 socket 时会被指定为SOCK_DGRAM。

       protocol 当创建SOCK_STREAM时值为IPPROTO_TCP,在创建SOCK_DGRAM时值为IPPROTO_UDP。也可以直接指定为0,系统会自动推演出应该使用什么协议。一般 socket 类型中总会被指定为 0。在一些 socket 类型中会使用非零进行描述。

       socket()在成功时返回一个引用在后续系统调用中会用到的新创建的 socket 的文件描述符。

3、将socket绑定到地址

bind()系统调用将一个 socket 绑定到一个地址上。

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

       sockfd 参数是在上一个 socket()调用中获得的文件描述符。

       addr 参数是一个指针,它指向了一个指定该 socket 绑定到的地址的结构。传入这个参数的结构的类型取决于 socket domain。

       addrlen 参数指定了地址结构的大小。

3、通用socket地址结构

每种 socket domain 都使用了不同的地址格式。如 UNIX domain socket 使用路径名,而Internet domain socket 使用了 IP 地址和端口号。sockaddr 结构通常被定义成如下所示的结构,来满足任意类型的地址结构。

struct sockaddr {
    sa_family_t sa_family;
    char      sa_data[14];
}

4、流socket

一个应用程序调用 bind()以将 socket 绑定到一个地址上,然后调用listen()通知内核它接受接入连接。使用 accept()接受连接。

其他应用程序通过调用 connect()建立连接,同时指定需连接的 socket 的地址。

如果在对等应用程序调用 connect()之前执行了 accept(),那么 accept()就会阻塞。

一旦建立了一个连接之后就可以在应用程序之间进行双向数据传输直到其中一个使用 close()关闭连接为止。通信是通过传统的 read()和 write()系统调用或通过一些提供了额外功能的 socket 特定的系统调用(如 send()和 recv())来完成的。

在这里插入图片描述


主动和被动socket

  • 在默认情况下, 使用 socket()创建的socket 是主动的。一个主动的 socket 可用在connect()调用中来建立一个到一个被动 socket 的连接。这种行为被称为执行一个主动的打开。
  • 一个被动 socket(也被称为监听 socket)是一个通过调用 listen()以被标记成允许接入连接的socket。接受一个接入连接通常被称为执行一个被动的打开。

4.1 监听接入连接

listen()系统调用将文件描述符 sockfd 引用的流 socket 标记为被动。这个 socket 后面会被用来接受来自其他(主动的) socket 的连接。

#include <sys/socket.h>
int listen(int sockfd, int backlog);

       sockfd为需要进入监听状态的套接字,backlog 为请求队列的最大长度。

无法在一个已连接的 socket(即已经成功执行 connect()的 socket 或由 accept()调用返回的socket)上执行 listen()。

客户端可能会在服务器调用 accept()之前调用connect()。如服务器可能正忙于处理其他客户端。这将会产生一个未决的连接,如下图:

在这里插入图片描述


内核必须要记录所有未决的连接请求的相关信息,这样后续的 accept()就能够处理这些请求了。backlog 参数允许限制这种未决连接的数量。在这个限制之内的连接请求会立即成功。 之外的连接请求就会阻塞直到一个未决的连接被接受(通过 accept()),并从未决连接队列删除为止。

这里怎么理解呢:就是当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。

4.2 接受连接

accept()系统调用在文件描述符 sockfd 引用的监听流 socket 上接受一个接入连接。如果在调用accept()时不存在未决的连接,那么调用就会阻塞直到有连接请求到达为止。

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept()它会创建一个新 socket, 并且正是这个新 socket 会与执行 connect()的对等 socket 进行连接。accept()调用返回的函数结果是已连接的 socket 的文件描述符。监听socket( sockfd)会保持打开状态,并且可以被用来接受后续的连接。

一个典型的服务器应用程序会创建一个监听socket,将其绑定到一个地址上,然后通过接受该 socket 上的连接来处理所有客户端的请求。

传入 accept()的剩余参数会返回对端 socket 的地址。addr 参数指向了一个用来返回 socket地址的结构。这个参数的类型取决于 socket domain(与 bind()一样)。

addrlen 参数是一个值-结果参数。它指向一个整数,在调用被执行之前必须要将这个整数初始化为 addr 指向的缓冲区的大小,这样内核就知道有多少空间可用于返回 socket 地址了。当 accept()返回之后,这个整数会被设置成实际被复制进缓冲区中的数据的字节数。

如果不关心对等 socket 的地址,那么可以将 addr 和 addrlen 分别指定为 NULL 和 0。

4.3 连接到对等socket

connect()系统调用将文件描述符 sockfd 引用的主动 socket 连接到地址通过 addr 和 addrlen指定的监听 socket 上。在主动socket或者客户端使用。

#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

       addr 和 addrlen 参数的指定方式与 bind()调用中对应参数的指定方式相同。

4.4 流socket I/O

一对连接的流 socket 在两个端点之间提供了一个双向通信信道。

在这里插入图片描述


要执行 I/O 需要使用 read()和 write()系统调用,或使用socket 特有的 send()和 recv()调用。由于 socket 是双向的,因此在连接的两端都可以使用这两个调用。

4.5 连接终止

终止一个流 socket 连接的常见方式是调用close()。如果多个文件描述符引用了同一个socket,那么当所有描述符被关闭之后连接就会终止。

当对等应用程序试图从连接的另一端读取数据时将会收到文件结束(当所有缓冲数据都被读取之后)。 如果对等应用程序试图向其 socket 写入数据, 那么它就会收到一个 SIGPIPE信号,并且系统调用会返回 EPIPE 错误。忽略 SIGPIPE 信号并通过 EPIPE 错误找出被关闭的连接。

5、数据报socket

简要说明数据报的流程

  1. 所有需要发送和接收数据报的应用程序都需要使用 socket()创建一个数据报 socket。
  2. 为允许另一个应用程序发送其数据报,服务端程序需要使用 bind()将其 socket 绑定到一个地址上。客户端会通过向该地址发送一个数据报来发起通信。
  3. 要发送一个数据报,一个应用程序需要调用sendto(),它接收的其中一个参数是数据报发送到的 socket 的地址。
  4. 为接收一个数据报,一个应用程序需要调用recvfrom(),它在没有数据报到达时会阻塞。由于 recvfrom()允许获取发送者的地址,因此可以在需要的时候发送一个响应。
  5. 当不再需要 socket 时,应用程序需要使用close()关闭 socket。

    在这里插入图片描述

5.1 数据报的发送和接收

recvfrom()和 sendto()系统调用在一个数据报 socket 上接收和发送数据。

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

这两个系统调用的返回值和前三个参数与 read()和 write()中的返回值和相应参数是一样的。第四个参数 flags 是一个位掩码, 它控制着了 socket 特定的 I/O 特性。

对于 recvfrom()来讲, src_addr 和 addrlen 参数会返回用来发送数据报的远程socket 的地址。对于 sendto()来讲, dest_addr 和 addrlen 参数指定了数据报发送到的 socket。

对于 recvfrom()来讲如果不关心发送者的地址,那么可以将 src_addr 和 addrlen 都指定为 NULL。在这种情况下, recvfrom()等价于使用 recv()来接收一个数据报。也可以使用 read()来读取一个数据报,这等价于在使用 recv()时将 flags 参数指定为 0。不管 length 的参数值是什么, recvfrom()只会从一个数据报 socket 中读取一条消息。如果消息的大小超过了 length 字节,那么消息会被静默地截断为 length 字节。

5.2 数据报使用connect

数据报 socket 是无连接的,但在数据报 socket 上应用 connect()系统调用仍然起作用。在数据报 socket 上调用 connect()会导致内核记录这个socket 的对等 socket 的地址。
当一个数据报 socket 已连接之后:

  • 数据报的发送可在 socket 上使用 write()(或 send())来完成并且会自动被发送到同样的对等 socket 上。与 sendto()一样,每个 write()调用会发送一个独立的数据报;
  • 在这个 socket 上只能读取由对等 socket 发送的数据报。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340