Socket 编程介绍
Socket
Socket 网络编程 按照操作系统 Windows 的 socket 编程 *nix 的 socket 编程 按照编程语 言 使 用C++ Java 的 socket 编程 使 用脚本语 言的 socket 编程
Socket 的 一些历史 Sockets 本来是 UNIX 操作系统下流 行行的 一种 网络编程接 口 (API), 在 4.2 BSD 中被 首先引 入的, 被称为 Berkeley Socket API Windows 网络应 用程序编程接 口Windows Sockets API 就是在 1991 年年根据 4.3 BSD 操作系统的 Berkeley Socket API 制定的
Windows sockets, 为 windows 环境下使 用的 一套 网络编程机制 ( 或规范 ), 简称为 Winsock 在 Windows 操作系统下得到 广泛应 用的 开放的 支持多种协议的 网络编程接 口 目前版本 Winsock 2, 由动态链接库 WSOCK32.DLL 提供 支持 Windows 环境下 网络编程事实上的标准
在 Winsock 规范中把 Winsock API 函数集分为与 BSD Socket( 用在 UNIX 中 ) 相兼容的基本函数 网络数据信息检索函数和 Windows 专 用扩展函数三类 可以看出,Winsock 来源于 BSD Socket API, 但它 又根据 Windows 操作系统的特点进 行行了了扩充 可认为是 一种 BSD Sockets 的 方 言 Winsock 规范的核 心内容是符合 Berkeley Socket 风格的库函数, 为了了使程序员能充分地利利 用 Windows 消息驱动机制进 行行编程, 也定义开发了了 一组针对 Windows 的扩展库函数
Socket 的概念与 工作原理理 Socket 可以看成是两个 网络应 用程序进 行行通信时, 各 自通信连接中的 一个端点, 是 一个逻辑上的概念 通信时其中的 一个 网络应 用程序将要传输的 一段信息写 入它所在主机的 Socket 中, 该 Socket 通过与 网络接 口卡 (Network Interface Cards, NIC) 相连的传输介质将这段信息发送到另外 一台主机的 Socket 中, 使这段信息能传送到其他程序中 是否类似 *nix 中的 文件系统?
按 TCP/IP 机制, 网络分层管理理 硬件层通信 : 主要由设备 厂家完成 网络 IP 层 传输层 : 操作系统提供软件服务 应 用层软件 : 由各服务 厂商提供, 如浏览器器 QQ 等 用户 自编的通信应 用程序, 工作在传输层和应 用层之 间 应 用通信程序需要和 工作在传输层之上的 socket 协议栈交换数据
Winsock 的 一个 工作原理理图
Socket 的基本 工作原理理 通过 socket 协议栈交换数据 : 通过 API 函数调 用和操作, 如 send/write, receive/read 等 用户每注册 一个 socket,socket 协议栈 自动为 用 户每 一个 socket 分配两个缓冲区 : 接收缓冲区 发送缓冲区
构建 一个 Socket: 以接电话为例例 类 比 电话 进 行行语 音数据交换 网络 进 行行数字数据交换
接听 一个电话 (Socket) 的第 一步 : 安装电话机 ( 初始化 Socket) #include <sys/socket.h> int socket(int domain, int type, int protocol) import socket s=socket.socket(socket.af_inet,socket.sock_stream)
socket() int socket(int domain, int type, int protocol) domain: domain family 协议族 PF_INET PF_INET6 PF_LOCAL PF_PACKET PF_IPX
socket() int socket(int domain, int type, int protocol) type: SOCK_STREAM, SOCK_DGRAM, SOCK_RAW protocol: 最终采取的协议 : IPPROTO_TCP IPPROTO_UDP
Winsock 的启动与 socket 的建 立 第 一步 :Winsock 头 文件 #include <winsock2.h> #pragma comment(lib, WS2_32 )
Winsock 的启动与 socket 的建 立 第 二步 : 先检测系统中有没有 winsock 的实现 WSAStartup() 本函数必须是应 用程序或 DLL 调 用的第 一个 Windows Sockets 函数, 指明 Windows Sockets API 的版本号及获得特定 Windows Sockets 实现的细节 应 用程序或 DLL 只能在 一次成功的 WSAStartup() 调 用之后, 才能进 一步调 用其它的 Winsock API 函数 ( 如开始建 立 socket)
WSAStartup 格式 int WSAStartup( WORD wversionrequested, LPWSADATA lpwsadata );
typedef struct WSAData{ WORD wversion; WORD whighversion; char szdescription[wsadescription_len+1]; char szsystemstatus[wsasys_status_len+1]; unsigned short imaxsockets; unsigned short imaxudpdg; char FAR * lpvendorinfo; } WSADATA,FAR *LPWSADATA;
WSAStartup() 的返回值 WSASYSNOTREADY: 在 Winsock 的头 文件 Winsock 2.h 中, 该错误代码定义的数值为 10091, 它表明加载的 Winsock DLL 不不存在或底层的 网络 子系统 无法使 用 WSAVERNOTSUPPORTED: 该代码的数值为 10092, 所需的 Windows Sockets API 的版本未由特定的 Windows Sockets 实现提供 如果由 wversion 返回的版本 用户不不能接受, 则要调 用 WSACleanup() 函数清除对 Winsock 的加载 WSAEINVAL: 该代码的数值为 10022, 说明应 用程序指出的 Windows Sockets 版本不不能被该 Winsock DLL 的实现所 支持 WSAEINPROGRESS: 该代码的数值为 10036, 说明 一个阻塞的 Winsock 调 用正在进 行行中 WSAEPROCLIM: 该代码的数值为 10067, 说明已经达到了了 Windows Sockets 实现 所 支持的任务数量量的极限 WSAEFAULT: 该代码数值为 10014, 说明 lpwsadata 参数是 一个 无效的指针
Winsock 的启动与 socket 的建 立 第 一步 :Winsock 头 文件 #include <winsock2.h> pragma comment(lib, WS2_32 ) 第 二步 : 先检测系统中有没有 winsock 的实现 WSAStartup() 第三步 :socket() 或 WSASocket(), 分别对应 winsock1 和 2 的实现
Winsock2 提供的扩展格式 SOCKET WSASocket( int af, int type, int protocol, LPWSAPROTOCOL_INFO lpprotocolinfo, Group g, int iflags );
三种不不同类型 (type) 的 Socket 流套接字 (SOCK_STREAM) 提供了了 一种可靠的 面向连接的双向数据传输服务 被传输的数据看作是 无记录边界的字节流 在 TCP/IP 协议族中, 使 用 TCP 协议来实现字节流的 传输 大批量量的数据, 或者对数据的传输有较 高的要求时 使 用
数据报套接字 (SOCK_DGRAM) 无连接 不不可靠的双向数据传输 数据包以独 立的包形式被发送, 保留留记录边界, 不不 提供可靠性保证 使 用 UDP 协议等 ( 也 支持其它的协议 ) 可 用于出现差错可能性较 小的 网络, 或 广播通信
原始套接字 (SOCK_RAW) 原始套接字可以读写内核没有处理理的 IP 数据包 ( 因为流套接字只能读取 TCP 协议的数据, 数据包 套接字只能读取 UDP 协议的数据 ) 故若要访问其他 协议发送数据必须使 用原始套接字
构建 一个 Socket: 以接电话为例例 接听 一个电话 (Socket) 的第 二步 : 分配 一个电话号码 ( 绑定 Socket 与地址 ) int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen) import socket HOST=?.?.?.? PORT= s=socket.socket(socket.af_inet,socket.sock_stream) s.bind(host,port)
bind() int bind(int sockfd, struct sockaddr *myaddr, socklen_t addrlen) 用本地地址为套接字命名 成功返回 0, 失败返回 -1 什什么情况需要调 用 bind()?
构建 一个 Socket: 以接电话为例例 接听 一个电话 (Socket) 的第三步 : 等候接听电话 (Socket 的 listen 方法 ) int listen(int sockfd, int backlog) import socket s=socket.socket(socket.af_inet,socket.sock_stream) s.bind((host,port)) s.listen(1)
listen() int listen(int sockfd, int backlog) 成功返回 0, 失败返回 -1 backlog: 用于在 TCP 层接收链接的缓冲池的最 大 个数, 当客户链接请求 大于这个数, 则其它的未进 入链接缓冲池的客户会 自动重新链接, 直到超时 Winsock2 中对应函数为 listen()
构建 一个 Socket: 以接电话为例例 接听 一个电话 (Socket) 的第四步 : 接听 (Socket 的 accept 方法 ) int accept(int sockfd, struct sockaddr *myaddr, socklen_t addrlen) import socket s.listen(1) conn,address=s.accept()
accept() int accept(int sockfd, struct sockaddr *myaddr, socklen_t* addrlen) 成功时返回 ( 客户端 socket 的 ) 文件描述符 失败是返回 -1 [py] 返回 (conn, address), 分别为 一个新的 socket 对象, 即对 方 ( 客户端 ) 的 socket 对象, 和对 方的地址 Winsock2 中对应函数为 WSAAccept()
构建 一个 Socket: 以接电话为例例 接听 一个电话 (Socket) 的第五步 : 挂电话 int close(int sockfd) import socket s=socket.socket(socket.af_inet,socket.sock_stream) conn.close()
构建 一个 Socket: 以接电话为例例 总结 上 面我们构建了了以 一个服务器器端 ( 接电话 ) 的 Socket 一般过程为 : 创建 socket bind socket listen to socket accept (data from) socket
构建 一个 Socket: 以拨电话为例例 拨打 一个电话 (Socket): 初始化 Socket( 建 立 Socket) 拨打电话 int connect(int sockfd, struct sockaddr *myaddr, socklen_t addrlen) c=socket.socket(socket.af_inet,socket.sock_stream) c.connect((host,port))
一个例例程
Hello world in socket programming
Socket 编程函数介绍 ( 补充 ) Winsock 中和 read/write 相对应函数 recv() / WSARecv() int recv(socket s, char FAR* buf, int len, int flags) recvfrom() / WSARecvFrom()
send() / WSASend() int send(socket s, const char FAR* buf, int len, int flags) sendto() / WSASendto()
Server Client
Socket 编程函数介绍 ( 补充 ) 字节序转换 htonl() 将主机的 无符号 长整型数本机顺序转换为 网络字节顺 序 (Host to Network Long), 用于 IP 地址 u_long PASCAL FAR htonl( u_long hostlong) hostlong 是主机字节顺序表达的 32 位数 htonl() 返回 一个 网络字节顺序的值
htons() 将主机的 无符号短整型数转换成 网络字节顺序 (Host to Network Short), 用于端 口号 u_short PASCAL FAR htons( u_short hostshort); hostshort: 主机字节顺序表达的 16 位数 htons() 返回 一个 网络字节顺序的值
ntohl() 将 一个 无符号 长整型数从 网络字节顺序转换为主机 字节顺序 (Network to Host Long), 用于 IP 地址 u_long PASCAL FAR ntohl( u_long netlong); netlong 是 一个以 网络字节顺序表达的 32 位数, ntohl() 返回 一个以主机字节顺序表达的数
ntohs() 将 一个 无符号短整型数从 网络字节顺序转换为主机 字节顺序 (Network to Host Sort), 用于端 口号 u_short PASCAL FAR ntohs( u_short netshort); netshort 是 一个以 网络字节顺序表达的 16 位数 ntohs() 返回 一个以主机字节顺序表达的数
Socket 编程函数介绍 ( 补充 ) 格式转换 inet_addr() 将 一个点间隔地址转换成 一个 in_addr unsigned long PASCAL FAR inet_addr(const struct FAR* cp) cp: 一个以 Internet 标准. 间隔的字符串串
inet_ntoa() 将 网络地址转换成. 点隔的字符串串格式 char FAR* PASCAL FAR inet_ntoa(struct in_addr in) in: 一个表示 Internet 主机地址的结构
获取与套接 口相连的端地址 getpeername() int getpeername( SOCKET s, struct sockaddr * name, int * namelen); 获取 一个套接 口的本地名字 getsockname() int getsockname( SOCKET s, struct sockaddr * name, int * namelen);
总结 : 流套接字通 用编程模型 服务端 : 客户端 : 套接字的创建和关闭 套接字创建和关闭 绑定套接字到指定的 IP 地址 和端 口号 设置套接字进 入监听状态 申请建 立连接 收发数据 断开连接, 关闭 接收连接请求 收发数据
Socket ( ) s bi nd( ) s l i st en( ) TCP accept ( ) accept ( ) r ecv( ) / send( ) / cl osesocket ( ) Socket ( ) s Connect ( ) send( ) / r ecv( ) /
总结 : 数据报套接字通 用编程模型 服务端 : 创建套接字 绑定 IP 地址和端 口 收发数据 客户端 : 创建套接字 收发数据 关闭套接字 关闭套接字
总结 : 常 用的 socket 函数 socket() bind() listen() accept()* connect()* send()* sendto()* recv()* recvfrom()* shutdown() closesocket()*
htonl() htons() ntohl() ntohs() Inet_addr() Inet_ntoa() getpeername() getsocketname() 32 16 32 16 IP IP getsocketopt() setsocketopt() ioctlsocket() select()* I/O
总结 : 网络编程中 可能出现 / 需要考虑的问题 Socket 工作中的问题 通信阻塞 : 发送量量超过发送缓冲区容量量, 或者接收 不不及时导致接收缓冲区满 数据到达通知 : 对于接收缓冲区, 由于数据到达是 随机事件, 通信处理理 方式 :1. 定期查询接收缓冲 区 ;2. 有数据到达时, 通知 用户处理理
网络异构特性所产 生的问题 字节序 字的 长度 : 不不同的系统中, 对于相同的数据类型可 能 用不不同的 长度表示 字节定界问题 : 不不同的平台上给结构 (struct) 或 联合 (union) 打包的 方式也是不不同的
通信模式的问题 阻塞与 非阻塞 在 网络编程中, 通信可以选择阻塞与 非阻塞两种模式 对于发送端, 若底层协议没有空间存放 用户数据, 应 用程 序会选择进 行行等待, 或者直接返回 而不不等待 在应 用进程调 用接收函数接收报 文时, 如果是在阻塞模式 下, 若没有到达的数据, 则调 用将 一直阻塞直到有数据到 达或出错为 止 UDP 协议不不同, 因为 UDP 没有发送缓冲, 所有 UDP 协议即 使在阻塞模式下也不不会发 生阻塞, 即没有真正的阻塞模式
阻塞与 非阻塞 ( 续 ) 在连接建 立阶段, 不不管是阻塞还是 非阻塞模式, 发 起连接请求的 一 方总是会使调 用它的进程阻塞, 阻 塞间隔最少等于到达服务器器的 一次往返时间 通信模式对应 用程序性能有的影响 非阻塞模式, 应 用程序轮询耗费 CPU 阻塞模式, 应 用程序 I/O 操作被阻塞
完