Article directory
By accident, I learned that WebSocket is used to send messages between the backend and the frontend, so I checked how to write this thing in iOS, and used the interface written by my roommate to simply realize the communication between the two users.
WebSocket
WebSocket
Is a protocol that enables two-way communication in web applications. It allows a persistent connection to be established between a client and a server so that bidirectional communication can take place at any point in time.- The traditional
HTTP
request -response mode only supports the client to initiate a request and the server to respond. That is, when a page is loaded, it usually initiates aHTTP
request for certain resources, such asHTML
,CSS
andJavaScript
files . Once these resources are sent to the browser, the connection is closed unless another request is made. - However, for some
Web
applications , more real-time interaction is required. This is the advantage offeredWebSocket
by : they allow an open connection between a client and a server so that both parties can send messages at any time. WebSocket
Handshake usingHTTP
the protocol , then upgrade toWebSocket
the protocol. Once this upgrade is complete, the client and server will communicate usingWebSocket
the protocol .WebSocket
This is achieved by using standardTCP/IP
sockets to send and receive data over the network.WebSocket
It is very practical in many scenarios, such as online games, real-time chat, stock market push, etc., which can greatly improve the interaction effect and user experience of the application.
Features of WebSocket
1. 建立在 TCP 协议之上,服务器端的实现比较容易。
2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
3. 数据格式比较轻量,性能开销小,通信高效。
4. 可以发送文本,也可以发送二进制数据。
5. 没有同源限制,客户端可以与任意服务器通信。
6. 协议标识符是ws(如果加密,则为wss),服务器网址就是URL。`ws://example.com:80/some/path`
7. 全双工通信。
SocketRocket
The method of use is the same as the previous third-party library, using cocoapods to guide the library, the specific operation is not described in detail, and it was also written in the previous blog.
Import the header file to set the proxy
Import #import <SocketRocket/SRWebSocket.h> where needed
and follow the proxy SRWebSocketDelegate
to declare a property SRWebSocket *webSocket;
SRWebSocket initialization and connection establishment
self.socket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"ws://101.43.193.62:9090/ws?id=%d", self.senderSubscriberNumber]]]];
self.socket.delegate = self;
NSLog(@"Opening Connection...");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_async(queue, ^{
[self.socket open];
});
See here why I put [self.socket open];
this code in an asynchronous concurrent queue. Before I called open
the method directly on the main thread, the compiler reported a bunch of strange things to me. After searching, it told me that it was the thread priority Caused by inversion:
In iOS, each thread has a Quality of Service (QoS) level associated with it, which is used to specify its relative priority. QOS_CLASS_USER_INTERACTIVE indicates that the thread needs to respond immediately and has the highest priority, while QOS_CLASS_DEFAULT indicates that the thread has the default priority.
The reason for this problem should be the bottom layer of this third-party library. I read some source code and found that many GCD things are called, but I haven't found the reason yet.
SRWebSocketDelegate proxy method implementation
//socket 连接成功
- (void)webSocketDidOpen:(SRWebSocket *)webSocket {
NSLog(@"Websocket Connected");
}
//socket 连接失败
- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {
NSLog(@":( Websocket Failed With Error %@", error);
self.socket = nil;
// 断开连接后每过1s重新建立一次连接
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self setSocket];
});
}
//socket连接断开
- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {
NSLog(@"WebSocket closed");
self.socket = nil;
}
//收到消息
- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {
NSLog(@"Received \"%@\"", message);
}
The four most commonly used methods respectively indicate that the connection is successful, the connection fails, the connection is disconnected, and the message is received. We can do what we should do in each protocol function.
The information sent to me by the backend is as follows:
I wanted to use a dictionary to store the message when I received it, but there was always a problem at the beginning of the conversion, and the conversion was successful after many attempts.
NSString *messageString = (NSString *)message;
NSData *messageData = [messageString dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *messageDictionary = [NSJSONSerialization JSONObjectWithData:messageData options:kNilOptions error:nil];
Plus simple UI for simple communication between two users
I looked at a little bit of source code (I don’t understand deeply)
1. Basic initialization method
- (id)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
_SR_commonInit
The underlying method is called
//初始化
- (void)_SR_commonInit;
{
//得到url schem小写
NSString *scheme = _url.scheme.lowercaseString;
//如果不是这几种,则断言错误
assert([scheme isEqualToString:@"ws"] || [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"wss"] || [scheme isEqualToString:@"https"]);
_readyState = SR_CONNECTING;
_webSocketVersion = 13;
//初始化工作的队列,串行
_workQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
//给队列设置一个标识,标识为指向自己的,上下文对象为这个队列
dispatch_queue_set_specific(_workQueue, (__bridge void *)self, maybe_bridge(_workQueue), NULL);
//设置代理queue为主队列
_delegateDispatchQueue = dispatch_get_main_queue();
//retain主队列?
sr_dispatch_retain(_delegateDispatchQueue);
//读Buffer
_readBuffer = [[NSMutableData alloc] init];
//输出Buffer
_outputBuffer = [[NSMutableData alloc] init];
//当前数据帧
_currentFrameData = [[NSMutableData alloc] init];
//消费者数据帧的对象
_consumers = [[NSMutableArray alloc] init];
_consumerPool = [[SRIOConsumerPool alloc] init];
//注册的runloop
_scheduledRunloops = [[NSMutableSet alloc] init];
....省略了一部分代码
}
the whole process:
- Determine the protocol type
- Initialized _workQueue, this GCD queue is used to handle the main business logic, including handling errors, sending content, closing connections, etc.
- Initialize _delegateDispatchQueue, which is used to send out notifications. We can customize this queue by
- (void)setDelegateOperationQueue:(NSOperationQueue*) queue
or .- (void)setDelegateDispatchQueue:(dispatch_queue_t) queue
2. Create input and output streams
//初始化流
- (void)_initializeStreams;
{
//断言 port值小于UINT32_MAX
assert(_url.port.unsignedIntValue <= UINT32_MAX);
//拿到端口
uint32_t port = _url.port.unsignedIntValue;
//如果端口号为0,给个默认值,http 80 https 443;
if (port == 0) {
if (!_secure) {
port = 80;
} else {
port = 443;
}
}
NSString *host = _url.host;
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
//用host创建读写stream,Host和port就绑定在一起了
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
//绑定生命周期给ARC _outputStream = __bridge transfer
_outputStream = CFBridgingRelease(writeStream);
_inputStream = CFBridgingRelease(readStream);
//代理设为自己
_inputStream.delegate = self;
_outputStream.delegate = self;
}
3. open
Method
//开始连接
- (void)open;
{
assert(_url);
//如果状态是正在连接,直接断言出错
NSAssert(_readyState == SR_CONNECTING, @"Cannot call -(void)open on SRWebSocket more than once");
//自己持有自己
_selfRetain = self;
//判断超时时长
if (_urlRequest.timeoutInterval > 0)
{
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, _urlRequest.timeoutInterval * NSEC_PER_SEC);
//在超时时间执行
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
//如果还在连接,报错
if (self.readyState == SR_CONNECTING)
[self _failWithError:[NSError errorWithDomain:@"com.squareup.SocketRocket" code:504 userInfo:@{
NSLocalizedDescriptionKey: @"Timeout Connecting to Server"}]];
});
}
//开始建立连接
[self openConnection];
}
//开始连接
- (void)openConnection;
{
//更新安全、流配置
[self _updateSecureStreamOptions];
//判断有没有runloop
if (!_scheduledRunloops.count) {
//SR_networkRunLoop会创建一个带runloop的常驻线程,模式为NSDefaultRunLoopMode。
[self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
}
//开启输入输出流
[_outputStream open];
[_inputStream open];
}
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode;
{
[_outputStream scheduleInRunLoop:aRunLoop forMode:mode];
[_inputStream scheduleInRunLoop:aRunLoop forMode:mode];
//添加到集合里,数组
[_scheduledRunloops addObject:@[aRunLoop, mode]];
}
I haven’t read it since then, but I have briefly read these two or three methods, and the methods that will be adjusted later:
didConnect
-
- Build HTTP Headers
-
- Send HTTP Header
-
- Register a listener that receives the header information returned by the server, and perform corresponding processing in the callback
- (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream
-
- This is the callback method of NSStream, the common callback of input and output streams
-
NSStreamEventOpenCompleted
Connection open;NSStreamEventHasBytesAvailable
readable;NSStreamEventHasSpaceAvailable
data can be written
-
NSStreamEventOpenCompleted
It is used[self _pumpScanner];
to trigger 3 in Article 5 to process the handshake Header information returned by the server