一。套接字概念:
- 套接字是一种进程间通信的方法,不同于其他进程间通信的方式(e.g.,管道、共享内存、信号量、消息队列),套接字可以在不同计算机的进程之间通信,明确区分C/S。
- 套接字接口(socket interface)由伯克利版本UNIX引入,可以认为是对管道概念的扩展。
套接字工作流程(服务器端)
- 首先,服务器应用程序通过socket系统调用创建一个套接字,它是系统分配
给该服务器进程的类似文件描述符的资源,不能与其他进程共享。 - 其次,服务器进程使用bind系统调用给套接字命名。
- 本地套接字的名字是linux文件系统的文件名, 一般将其放在/tmp或者
/usr/tmp目录下。 - 网络套接字的名字是与客户相连接的特定网络有关的服务标识符。此标
识符允许linux将进入的针对特定端口号的连接转到正确的服务器进程。
- 本地套接字的名字是linux文件系统的文件名, 一般将其放在/tmp或者
- 接下来,服务器进程开始等待客户连接到这个命名套接字,调用listen创建
一个等待队列,以便存放来自客户的进入连接。 - 最后,服务器通过accept系统调用来接受客户的连接。此时,会产生一个
与原有的命名套接字不同的新套接字,它仅用于与这个特定的客户通信,而
命名套接字则被保留下来继续处理来自其他客户的连接。
套接字工作流程(客户端)
- 调用socket创建一个未命名套接字,将服务器的命名套接字作为一个地址来
调用connect与服务器建立连接。 - 一旦建立了连接,就可以像使用底层文件描述符(write,read)那样来用套接字进行双向的数据通信。
套接字属性:域( domain) 类型( type) 协议( protocol)
- 域(domain):指定套接字通信中使用的网络介质,包括地址格式。最常
用的有以下两种:- AF_INET, 即互联网络,基于IP协议,并且每个服务对应一个端口号
,套接字地址由IP地址+端口号决定 - AF_UNIX, 基于本地机器,底层协议使用文件输入/输出,地址为绝对
路径的文件名
- AF_INET, 即互联网络,基于IP协议,并且每个服务对应一个端口号
- 类型(type):一个套接字域可能有不同的通信方式,每种通信方式有不同的特性。F_UNIX域的套接字没有通信方面的问题,因为其基于文件系统,提供了一个可靠的双向通信路径。
- AF_INET域中,需要注意底层网络的特性:
- 流套接字:由类型SOCK_STREAM指定,基于TCP/IP实现,提供一个有序、可靠、双向字节流的连接,发送的数据不会丢失、乱序、重复。大的消息会被分块、传输、重组,很像一个文件流。
- 数据报套接字:由SOCK_DGRAM指定,基于UDP/IP协议,不建立和维持可靠连接,开销小,服务器崩溃不需要客户端重启,因为基于数据报的服务器不保留连接信息
- AF_INET域中,需要注意底层网络的特性:
- 协议(protocol):默认
二。套接字函数
套接字接口函数
- 创建套接字
socket系统调用创建一个套接字,并返回一个描述符,该描述符可以用来访
问这个套接字。创建的套接字是一条通信链路的一个端点。
#include <sys/types.h>
#include <sys/socket.h>
int socket( int domain, int type, int protocol);
domain: 指定域
type:指定套接字的通信类型
protocol:指定使用的协议
- 套接字地址
每个套接字域都有自己的地址格式。
- AF_UNIX:地址格式由sockaddr_un描述:
struct sockaddr_un{sa_family_t sun_family; /*AF_UNIX*/char sun_path[]; /*pathname*/
}
- AF_INET:地址格式由sockaddr_in来指定:
struct sockaddr_in{short int sin_family; /*AF_INET*/unsigned short int sin_port;struct in_addr sin_addr
}
- 命名套接字
- 要想让通过socket调用创建的套接字可以被其他进程使用,服务器程序必须
给套接字命名,这样AF_UNIX套接字才会关联到一个文件系统的路径名上
, AF_INET套接字关联到一个IP的端口上。 - bind调用把参数address中的地址分配给文件描述符socket关联的未命名套
接字,地址长度由address_len来传递
#include <sys/socket.h>
int bind(int socket, const struct sockaddr* address, size_t address_len);
- 关于地址
- 地址长度取决于地址族, bind调用将一个特定的地址结构指针转换为指向
通用地址类型: struct sockadd
#include <sys/socket.h>
int bind(int socket, const struct sockaddr* address, size_t address_len);
网络套接字地址类型: struct sockaddr_in
本地文件系统地址类型: struct sockaddr_un
- bind的返回值
bind函数调用成功返回0,失败返回-1. 并把errno设置为下表值
EBADF | 文件描述符无效 |
---|---|
ENOTSOCK | 文件描述符对应的不是一个套接字 |
EINVAL | 文件描述符对应的是一个已命名的套接字 |
。。。 | 。。。 |
- 创建套接字队列
- 为了能够在套接字上接受进入的连接,服务器程序必须创建一个队列来保持
未处理的请求,它用listen系统调用来完成这一工作
#include <sys/socket.h>
int listen( int socket, int backlog);
- 当服务器正忙于处理一个客户请求时,后续的客户连接放入队列等待处
理。函数执行成功返回0,失败返回-1 - 套接字队列中,等待处理的进入连接的个数最多不能超过backlog这个
数字,多出的连接请求将被拒绝,导致客户连接失败。
- 接受连接
- 一旦服务器程序创建并命名了套接字之后,就可以通过accept系统调用来
等待客户建立对该套接字的连接
- 一旦服务器程序创建并命名了套接字之后,就可以通过accept系统调用来
#include <sys/socket.h>
int accept( int socket, struct sockaddr* address, size_t* address_len);
- 连接客户的地址将被放入address参数指向的sockaddr结构中。
- address_len指定客户结构的长度,如果客户地址的长度超出这个值将
被截断,所以必须先得到这个合适的值
- 关于accept调用
- accept函数只有当客户程序试图连接到由socket参数指定的套接字上时才
- accept函数将创建一个新的套接字来与该客户通信,并且返回新套接字的
描述符,新套接字的类型与服务器监听套接字一致。
返回,否则将一直阻塞
- 请求连接
- 客户程序通过在一个未命名套接字与服务器监听套接字之间建立连接,以此
来连接到服务器。
#include <sys/socket.h>
int connect( int socket, struct sockaddr* address, size_t* address_len);
- 参数socket指定的套接字将连接到参数address指定的服务器套接字,
address指向的结构长度由参数address_len指定。
- 关闭套接字
- 可以通过close函数终止服务器和客户上的套接字连接,操作过程与对文件
描述符进行关闭一样 - 应该在连接的两端都关闭套接字。