
简介
使用
WebSocket API 是 HTML5 标准的一部分, 但这并不代表 WebSocket 一定要用在 HTML 中,或者只能在基于浏览器的应用程序中使用。
实际上,许多语言、框架和服务器都提供了 WebSocket 支持:
- 基于 C 的 libwebsocket.org
- 基于 Node.js 的 Socket.io
- 基于 Python 的 ws4py
- 基于 C++ 的 WebSocket++
- Apache 对 WebSocket 的支持: Apache Module mod_proxy_wstunnel
- Nginx 对 WebSockets 的支持: NGINX as a WebSockets Proxy 、 NGINX Announces Support for WebSocket Protocol 、WebSocket proxying
- lighttpd 对 WebSocket 的支持:mod_websocket
nodejs
java
Spring
服务端
1 | package me.gacl.websocket; |
客户端
1 | <%@ page language="java" pageEncoding="UTF-8" %> |
Web
C++
c++下没有原生的websocket低矮用,但是可以使用一些开源库实现,如websocketpp等,下面讲解的是socket通信相关的实现
IOS
数据结构
sockaddr
和sockaddr_in
在字节长度上都为16个BYTE,可以进行转换
1 | /* 通用的socket地址 */ |
1 | /* Internet socket */ |
1 | /* 表示32位的IPv4地址 */ |
- **inet_addr(“192.168.0.1”)**:将一个点分制的IP地址(如192.168.0.1)转换为上述结构中需要的32位二进制方式的IP地址
函数
1 | int socket(int domain, int type, int protocol); |
- domain(IOS系统使用
AT_
)- PF_UNIX Unix IPC通信
- PF_INET IPV4通信(默认)
- PF_INET6 IPV6
- PF_IPX Novell IPX
- PF_NETLINK Kernel用户接口驱动程序
- PF_X25 X.25
- PF_AX25 AX.25
- PF_ATMPVC ATM PVC
- PF_APPLETALK AppleTalk协议
- PF_PACKET 低级包接口
- type
- SOCK_STREAM 使用TCP面向连接的通信包(默认)
- SOCK_DGRAM 使用UDP无连接的通信包
- SOCK_SEQPACKET 使用有固定最大长度的面向连接的通信包
- SOCK_RAW 使用原IP包
- SOCK_RDM 使用不保证次序的可靠数据报
- Protocol
- 一般使用与type对应的默认协议,用0表示。
1 | int recv(SOCKET socket, char *buf, int len, int flags); |
- 参数:
- socket:已建立连接的套接字;
- buf:存放接收到的数据的缓冲区指针;
- len:buf的长度
- flags:调用方式:
- 0:接收的是正常数据,无特殊行为。
- MSG_PEEK:系统缓冲区数据复制到提供的接收缓冲区,但是系统缓冲区内容并没有删除。
- MSG_OOB:表示处理带外数据。
- 返回值:接收成功时返回接收到的数据长度,连接结束时返回0,连接失败时返回SOCKET_ERROR。
1 | int send(SOCKET socket, const char *buf, int len, int flags) |
- 参数
- socket:已建立连接的套接字
- buf:存放将要发送的数据的缓冲区指针;
- len:发送缓冲区中的字符数
- flags:控制数据传输方式:
- 0:接收的是正常数据,无特殊行为。
- MSG_DONTROUTE:表示目标主机就在本地网络中,无需路由选择。
- MSG_OOB:表示处理带外数据。
- 返回值:发送成功时返回发送的数据长度,连接结束时返回0,连接失败时返回SOCKET_ERROR。
1 | extern void bzero(void *s, int n); |
- 头文件:#include <string.h>
- 功能:置字节字符串s的前n个字节为零且包括‘\0’
- 描述:
- string.h曾经是posix标准的一部分,但是在POSIX.1-2001标准里面,这些函数被标记为了遗留函数而不推荐使用。在POSIX.1-2008标准里已经没有这些函数了。推荐使用
memset
替代bzero
。 - bzero函数TC和VC中都没有,gcc中提供了
- string.h曾经是posix标准的一部分,但是在POSIX.1-2001标准里面,这些函数被标记为了遗留函数而不推荐使用。在POSIX.1-2008标准里已经没有这些函数了。推荐使用
- 无返回值
使用无阻塞的I/O方法
什么是阻塞?
比如使用recv(),如果函数接受不到数据,就会阻塞程序的继续执行。如何防止阻塞?
使用fcntl()函数,把套接字设置为无阻塞模式。
1
2
3 >int newsocket;
>newsocket = socket(PF_INET, SOCK_STREAM, 0 );
>fcntl( newsocket, F_SETEL, O_NONBLOCK );以后使用recv()就不会阻塞了。
另一种方式是使用多路套接字
select()
select
select这个系统调用,是一种多路复用IO方案,可以同时对多个文件描述符进行监控,从而知道哪些文件描述符(File Descriptor,FD)可读,可写或者出错,不过select方法是阻塞的,可以设定超时时间。
select使用的步骤如下:
- 创建一个
fd_set
变量(fd_set实为包含了一个整数数组的结构体),用来存放所有的待检查的文件描述符 - 清空
fd_set
变量,并将需要检查的所有文件描述符加入fd_set
- 调用select。若返回-1,则说明出错;返回0,则说明超时,返回正数,则为发生状态变化的文件描述符的个数
- 若select返回大于0,则依次查看哪些文件描述符变的可读,并对它们进行处理
- 返回步骤2,开始新一轮的检测
1 |
|
- 参数:
- maxfd:需要监视的最大的文件描述符值+1;
- rdset:需要检测的可读文件描述符的集合
- wrset:可写文件描述符的集合
- exset:异常文件描述符的集合
- timeval:用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。
fd_set
类型通过下面四个宏来操作:
FD_ZERO(fd_set *fdset);
将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。FD_SET(fd_set *fdset);
用于在文件描述符集合中增加一个新的文件描述符。FD_CLR(fd_set *fdset);
用于在文件描述符集合中删除一个文件描述符。FD_ISSET(int fd, fd_set *fdset);
用于测试指定的文件描述符是否在该集合中。
kqueue
Mac是基于BSD的内核,所使用的是kqueue(kernel event notification mechanism,详细内容可以Mac中 man 2 kqueue
)
- kqueue比select先进的地方就在于使用事件触发的机制,且其调用无需每次对所有的文件描述符进行遍历,返回的时候只返回需要处理的事件,而不像select中需要自己去一个个通过FD_ISSET检查。
- kqueue默认的触发方式是level 水平触发,可以通过设置event的flag为
EV_CLEAR
使得这个事件变为边沿触发,可能epoll的触发方式无法细化到单个event,需要查证。
kqueue中涉及两个系统调用,kqueue()和kevent()
kqueue()
创建kernel级别的事件队列,并返回队列的文件描述符kevent()
往事件队列中加入订阅事件,或者返回相关的事件数组
kqueue使用的流程一般如下:
- 创建kqueue
- 创建
struct kevent
变量(注意这里的kevent是结构体类型名),可以通过EV_SET
这个宏提供的快捷方式进行创建 - 通过kevent系统调用将创建好的kevent结构体变量加入到kqueue队列中,完成对指定文件描述符的事件的订阅
- 通过kevent系统调用获取满足条件的事件队列,并对每一个事件进行处理
操作流程

1. sever
1 | /* 绑定server socket的ip、端口等信息 */ |
一般情况下都用
server_addr.sin_addr.s_addr = htonl(INADDR_ANY)
比如你的机器有三个ip
192.168.1.1
202.202.202.202
61.1.2.3如果你serv.sin_addr.s_addr=inet_addr(“192.168.1.1”);
然后监听100端口
这时其他机器只有connect 192.168.1.1:100端口才能成功。
connect 202.202.202.202:100和connect 61.1.2.3:100都会失败。如果serv.sin_addr.s_addr=htonl(INADDR_ANY); 的话,无论连接哪个ip都可以连上的,这就是为什么这样选择的理由
1 | /* server socket工作流程 */ |
1 | /* 信息交流 */ |
2. client
1 | /* 创建需要通信的server socket的IP、端口等信息 */ |
1 | /* 创建socket通信连接 */ |
WIN
模仿Unix Socket技术实现
操作流程

php
- Post title:WebSocket
- Post author:Wei Jieyang
- Create time:2021-01-25 14:37:08
- Post link:https://jieyang-wei.github.io/2021/01/25/WebSocket/
- Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.