Socket 套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
大部分系统都提供了一组基于TCP或者UDP的应用程序编程接口(API),该接口通常以一组函数的形式出现,也称为套接字(Socket)
环境说明
python2.7 macOS Catalina Xcode 11.3.1 CocoaAsyncSocket ea517e0cc1b33b4f706a20f521ed298adbb05378
实验 使用python
分别创建Socket服务器与客户端[2]
服务端socket_service.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import socketip_port = ('192.168.31.136' , 9999 ) sk = socket.socket() sk.bind(ip_port) sk.listen(5 ) print ('启动socket服务,等待客户端连接...' )conn, address = sk.accept() while True : client_data = conn.recv(1024 ).decode('utf-8' ) if client_data == "exit" : exit("通信结束" ) print ("来自%s的客户端向你发来信息:%s" % (address, client_data.encode('utf-8' ))) conn.sendall('recv:' +client_data.encode('utf-8' )) conn.close()
用户端socket_client.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import socketip_port = ('192.168.31.136' , 9999 ) s = socket.socket() s.connect(ip_port) while True : inp = input ("请输入要发送的信息: " ).strip() if not inp: continue s.sendall(inp.encode()) if inp == "exit" : print ("结束通信!" ) break server_reply = s.recv(1024 ).decode() print (server_reply) s.close()
在终端中启动socket_service.py
1 2 -> python socket_service.py 启动socket服务,等待客户端连接...
启动socket_client.py
输入"hello"
1 2 3 4 5 6 7 8 -> python socket_client.py 请输入要发送的信息: "hello" recv:hello 请输入要发送的信息: 来自('192.168.31.136' , 58160)的客户端向你发来信息:hello
新开一个服务端socket
窗口,使用浏览器访问
1 2 3 4 5 6 7 8 9 10 11 来自('192.168.31.136' , 58258)的客户端向你发来信息:GET / HTTP/1.1 Host: 192.168.31.136:9999 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
使用浏览器访问,使用HTTP
协议访问,传输层实现了端到端的传输(能力),但是信息要被正确的理解,需要一些说明,比如上面HTTP
请求头 ,操作系统拥有传输层的能力,并包装了一组抽象的编程接口(Socket
),应用层协议通过调用Socket
实现信息的传输并制定格式用于理解信息。
Socket的流程
来自网络
CocoaAsyncSocket CocoaAsyncSocket为macOS,iOS和tvOS提供了易于使用且功能强大的异步套接字库。
客户端Socket 实现客户端socket
,参考上图,主要为创建socket
,连接,发送,接收,关闭流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; NSError *err = nil ;if (![_socket connectToHost:@"192.168.31.136" onPort:9999 error:&err]) { NSLog (@"I goofed: %@" , err); } NSString *requestString = @"AsyncSocket request string 1" ;NSData *requestData = [requestString dataUsingEncoding:NSUTF8StringEncoding ]; [_socket writeData:requestData withTimeout:-1 tag:1 ]; [_socket readDataWithTimeout:10 tag:1 ];
代理回调 连接成功
1 2 3 - (void )socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port { NSLog (@"%@ - %@ - %hu" ,sock,host,port); }
发送成功
1 2 3 4 5 6 7 - (void )socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long )tag { if (tag == 1 ) { NSLog (@"First request sent" ); }else if (tag == 2 ) { NSLog (@"Second request sent" ); } }
接收成功
1 2 3 4 5 6 7 8 - (void )socket:(GCDAsyncSocket *)sender didReadData:(NSData *)data withTag:(long )tag { if (tag == 1 ) { NSLog (@"%@" ,[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding ]); }else if (tag == 2 ) { NSLog (@"%@" ,[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding ]); } }
确保真机和mac
处于同一局域网,输出效果如下:
能成功连接,发送信息给服务端且接收服务端返回的信息
连接 在连接的回调中设置断点,并进行追踪
对于连接操作,采用异步的并发队列进行管理,提高连接效率,调用系统提供socket.h
中的connect
方法
1 int connect (int , const struct sockaddr *, socklen_t ) __DARWIN_ALIAS_C (connect) ;
1 2 3 4 5 6 7 8 connect - initiate a connection on a socket The connect() system call connects the socket referred to by the file descriptor sockfd to the address specified by addr. The addrlen argument specifies the size of addr. If the connection or binding succeeds, zero is returned. On error, -1 is returned, and errno is set appropriately.
连接完成后,统一回调到socketQueue
队列,调用didConnect:
方法
SetupStreamPart1()
经过一些容错判断后,调用CFStreamCreatePairWithSocket
创建连接到套接字的可读和可写流
includeReadWrite
参数为NO
,对Stream
在读写操作中发生错误和完成注册回调函数
dispatch_async(delegateQueue,...
异步回调到代理队列(初始化时可指定,此处为主队列)执行代理方法
SetupStreamsPart2()
回到socketQueue
在startCFStreamThreadIfNeeded
方法中创建一个串行队列,以同步的方式新建一条子线程并开启运行循环
同时在addStreamsToRunLoop
中,将创建的读写流作为事件源顺序添加到刚创建的子线程的runloop
中,确保发生对应事件时,runloop
能顺利派发。
回到didConnect:
方法中,
socket
开启非阻塞IO
,注册读写操作的回调,同时尝试进行读写操作
执行原来的两个读操作
参考
百科 - 套接字
socket编程
connect2
iOS网络编程之CFNetwork
Introduction to non-blocking I/O