Socket编程
# Socket 编程
# 应用编程接口(API)
# 网络程设计接口
- Socket 编程针对传输层
# 应用编程接口 API
应用编程接口 API (Application Programming Interface)
- 应用进程的控制权和操作系统的控制权进行转换的一个系统调用接口
# 几种典型的应用编程接口
典型的应用编程接口
- Berkeley UNIX 操作系统定义了一种 API,称为套接字接口(socket interface),简称套接字 (socket)
- 应用最广
- 微软公司在其操作系统中采用了套接字接口 API,形成了一个稍有不同的 API,并称之为 Windows Socket Interface,WINSOCK
- AT&T 为其 UNIX 系统 V 定义了一种 API,简写为 TLI (Transport Layer Interface)
# Socket API 概述
最初设计
- 面向BSD UNIX-Berkley
- 面向TCP/IP协议栈接口
目前情况
- 事实上的工业标准
- 绝大多数操作系统都支持
Internet网络应用最典型的API接口
通信模型
- 客户/服务器(C/S)
应用进程间通信的抽象机制
示意图
标识通信端点(对外)
- IP地址+端口号
示意图
操作系统/进程如何管理套接字(对内)?
- 套接字描述符(socket descriptor)
- 小整数
套接字对内,对外管理机制是不同的
# Socket 抽象
- 类似于文件的抽象
- 当应用进程创建套接字时,操作系统分配一个数据结构存储该套接字相关信息
- 返回套接字描述符
# 地址结构
已定义结构 sockaddr_in 来表示地址结构
struct sockaddr_in
{
u_char sin_len; /* 地址长度 */
u_char sin_family; /* 地址族(TCP/IP:AF_INET) */
u_short sin_port; /* 端口号 */
struct in_addr sin_addr; /* IP地址 */
char sin_zero[8]; /* 未用(置0) */
}
2
3
4
5
6
7
8
提示
使用TCP/IP协议簇的网络应用程序声明端点地址变量时,使用结构 sockaddr_in
# Socket API 函数(WinSock)
# 常用的 Socket API 函数
# WSAStartup
# WSACleanup
int WSACleanup (void)
- 应用程序在完成对请求的Socket库的使用,最后要调用WSACleanup函数
- 解除与Socket库的绑定
- 释放Socket库所占用的系统资源
# socket
创建套接字
操作系统返回套接字描述符(sd)
第一个参数(协议族)
- protofamily = PF_INET(TCP/IP)
第二个参数(套接字类型):
- type = SOCK_STREAM,SOCK_DGRAM or SOCK_RAW(TCP/IP)
第三个参数(协议号):0为默认
例:创建一个流套接字的代码段
struct protoent *p;
p=getprotobyname("tcp");
SOCKET sd=socket(PF_INET,SOCK_STREAM,p->p_proto);
2
3
Socket面向TCP/IP的服务类型
TCP
- 可靠、面向连接、字节流传输、点对点、全双工
UDP
- 不可靠、无连接、数据报传输
- 原始套接字
# closesocket
int closesocket(SOCKET sd)
- 关闭一个描述符为
sd
的套接字 - 如果多个进程共享一个套接字,调用
closesocket
- 将套接字引用计数减1,减至0才关闭
- 一个进程中的多线程对一个套接字的使用无计数
- 如果进程中的一个线程调用
closesocket
将一个套接字关闭,该进程中的其他线程也将不能访问该套接字
- 如果进程中的一个线程调用
- 返回值:
- 0:成功
- SOCKET_ERROR:失败
# bind
int bind(sd,localaddr,addrlen)
- 绑定套接字的本地端点地址
- IP地址+端口号
- 参数:
- 套接字描述符(
sd
) - 端点地址(
localaddr
)- 结构
sockaddr_in
- 结构
- 套接字描述符(
- 客户程序一般不必调用
bind
函数 - 服务器端需要调用
- 熟知端口号
- 标准应用
- IP地址
- 多个网络地址用地址通配符解决
- 熟知端口号
# listen
int listen(sd,queuesize);
- 置服务器端的流套接字处于监听状态
- 仅服务器端调用
- 仅用于面向连接的流套接字
- 设置连接请求队列大小(queuesize)
- 返回值:
- 0:成功
- SOCKET_ERROR:失败
# connect
connect(sd,saddr,saddrlen)
;客户程序调用
connect
函数来使客户套接字(sd
)与特定计算机的特定端口(saddr
)的套接字(服务)进行连接仅用于客户端
可用于TCP客户端也可以用于 UDP客户端
- TCP客户端:建立TCP连接
- UDP客户端:只是指定服务器端点地址
- 绝对不可能发生连接
# accept
newsock = accept(sd,caddr,caddrlen);
- 服务程序调用
accept
函数从处于监听状态的流套接字sd
的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道- 仅用于TCP套接字
- 仅用于服务器
- 队列可以实现并发 TCP 通信
- 利用新创建的套接字(newsock)与客户通信
# send
, sendto
send(sd,*buf,len,flags);
sendto(sd,*buf,len,flags,destaddr,addrlen);
send函数TCP套接字(客户与服务器)或调用了 connect函数的UDP客户端套接字 sendto函数用于UDP服务器端套接字与未调用 connect函数的UDP客户端套接字
# recv, recvfrom
recv(sd,*buffer,len,flags); recvfrom(sd,*buf,len,flags,senderaddr,saddrlen);
recv函数从TCP连接的另一端接收数据,或者从 调用了connect函数的UDP客户端套接字接收服务 器发来的数据 recvfrom函数用于从UDP服务器端套接字与未调 用connect函数的UDP客户端套接字接收对端数据
# setsockopt, getsockopt
int setsockopt(int sd, int level, int optname, *optval, int optlen); int getsockopt(int sd, int level, int optname, *optval, socklen_t *optlen); setsockopt()函数用来设置套接字sd的选项参数 getsockopt()函数用于获取任意类型、任意状态套 接口的选项当前值,并把结果存入optval
# 小结 🌟
WSAStartup: 初始化socket库(仅对WinSock) WSACleanup: 清楚/终止socket库的使用 (仅对WinSock) socket: 创建套接字 connect:“连接”远端服务器 (仅用于客户端) closesocket: 释放/关闭套接字 bind: 绑定套接字的本地IP地址和端口号(通常客户端不 需要) listen: 置服务器端TCP套接字为监听模式,并设置队列 大小 (仅用于服务器端TCP套接字) accept: 接受/提取一个连接请求,创建新套接字,通过新 套接 (仅用于服务器端的TCP套接字) recv: 接收数据(用于TCP套接字或连接模式的客户端 UDP套接字) recvfrom: 接收数据报(用于非连接模式的UDP套接字) send: 发送数据(用于TCP套接字或连接模式的客户端 UDP套接字) sendto:发送数据报(用于非连接模式的UDP套接字) setsockopt: 设置套接字选项参数 getsockopt: 获取套接字选项参数
# 网络字节顺序
TCP/IP定义了标准的用于协议头中的二进制整 数表示:网络字节顺序(network byte order) 某些Socket API函数的参数需要存储为网络字 节顺序(如IP地址、端口号等) 可以实现本地字节顺序与网络字节顺序间转换 的函数 htons : 本地字节顺序→网络字节顺序(16bits) ntohs : 网络字节顺序→本地字节顺序(16bits) htonl : 本地字节顺序→网络字节顺序(32bits) ntohl : 网络字节顺序→本地字节顺序(32bits)
# 网络应用的Socket API(TCP)调用基本流程
# 客户端软件设计
# 解析服务器 IP 地址
# 解析服务器(熟知端口号)
# 解析协议号
# 客户端软件的实现
# TCP 客户端软件流程
# UDP 客户端软件流程
# 相关函数
# connectsock()
# connectUDP()
# connectTCP
# 异常处理
# 客户端软件实例
# 访问 DAYTIME 服务的客户端 (TCP)
# 访问 DAYTIME 服务的客户端 (UDP)
# 服务器软件设计
# 4 种基本类型服务器
# 循环无连接服务器(Iterative connectionless)
基本流程
- 创建套接字
- 绑定端点地址(INADDR_ANY+端口号)
- 反复接收来自客户端的请求
- 遵循应用层协议,构造响应报文,发送给客户
数据发送
- 服务器端不能使用connect()函数
- 无连接服务器使用sendto()函数发送数据报
获取客户端点地址
# 循环面向连接服务器(Iterative connection-oriented)
基本流程
- 创建(主)套接字,并绑定熟知端口号
- 设置(主)套接字为被动监听模式,准备用于服务器
- 调用accept()函数接收下一个连接请求(通过主套接字),创建新套接字用于与该客户建立连接
- 遵循应用层协议,反复接收客户请求,构造并发送响应(通过新套接字)
- 完成为特定客户服务后,关闭与该客户之间的连接,返回步骤3
# 并发无连接服务器(Concurrent connectionless)
基本流程
主线程1: 创建套接字,并绑定熟知端口号
主线程2: 反复调用 recvfrom()
函数,接收下一个客户请求,并创建新线程处理该客户响应
子线程1: 接收一个特定请求
子线程2: 依据应用层协议构造响应报文,并调用 sendto()
发送
子线程3: 退出(一个子线程处理一个请求后即终止)
# 并发面向连接服务器(Concurrent connection-oriented)
基本流程
- 主线程1: 创建(主)套接字,并绑定熟知端口号;
- 主线程2: 设置(主)套接字为被动监听模式,准备用于服务器;
- 主线程3: 反复调用accept()函数接收下一个连接请求(通过主套接字),并创建一个新的子线程处理该客户响应;
- 子线程1: 接收一个客户的服务请求(通过新创建的套接字);
- 子线程2: 遵循应用层协议与特定客户进行交互;
- 子线程3: 关闭/释放连接并退出(线程终止)
# 服务器的实现
设计一个底层过程隐藏底层代码: passivesock() 两个高层过程分别用于创建服务器端UDP套接字 和TCP套接字(调用passivesock()函数): passiveUDP() passiveTCP()