心跳包.jpg
何為 socket ?
- 網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱為一個socket。
建立網絡通信連接至少要一對端口號(socket)。socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員做網絡開發所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通信的能力。
流程
- 1、服務器綁定 ip 地址和端口號
- 2、服務器監聽端口號請求,隨時接受客戶端發來的請求鏈接 (這個時候還沒辦法鏈接)
- 3、客戶端創建 socket
- 4、客戶端打開 socket 根據 ip 和端口號 嘗試鏈接服務器的 socket
- 5、服務器 socket 接受客戶端 socket 請求,接受客戶端的請求,等客戶端返回鏈接信息,然后進入阻塞狀態(即 acept 方法 等客戶端返回鏈接信息 才返回,開始接收下一個客戶端請求)
- 6、客戶端 socket 鏈接成功,向服務器 socket 發送狀態信息
- 7、服務器返回信息,鏈接成功
- 8、客戶端 向 服務器 寫入信息
- 9、服務器接收信息 再像客戶端發送信息(客戶端讀取信息)
- 10、 客戶端 和 服務端關閉
我這邊用的 Python 3.x 的版本 來寫的服務器 socekt ,里面方法如圖
Python Socket.png
服務端一次接收和發送 代碼如下
import socket # 導入socket模塊
sk = socket.socket() # 創建socket對象
sk.bind(("127.0.0.1", 6768)) # 綁定端口,“127.0.0.1”代表本機地址,8888為設置鏈接的端口地址
sk.listen(5) # 設置監聽,最多可有5個客戶端進行排隊
conn, addr = sk.accept() # 阻塞狀態,被動等待客戶端的連接
print(conn) # conn可以理解客戶端的socket對象
# <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9005), raddr=('127.0.0.1', 36694)>
print(addr) # addr為客戶端的端口地址
# ('127.0.0.1', 40966)
accept_data = conn.recv(1024) # conn.recv()接收客戶端的內容,接收到的是bytes類型數據,
accept_data2 = str(accept_data, encoding="utf8") # str(data,encoding="utf8")用“utf8”進行解碼
print("".join(("接收內容:", accept_data2, " 客戶端口:", str(addr[1]))))
send_data = input("輸入發送內容:")
conn.sendall(bytes(send_data, encoding="utf8")) # 發送內容必須為bytes類型數據,bytes(data, encoding="utf8")用“utf8”格式進行編碼
conn.close()
簡單的并發 服務端
import socketserver # 導入socketserver模塊
class MyServer(socketserver.BaseRequestHandler): # 創建一個類,繼承自socketserver模塊下的BaseRequestHandler類
def handle(self): # 要想實現并發效果必須重寫父類中的handler方法,在此方法中實現服務端的邏輯代碼(不用再寫連接準備,包括bind()、listen()、accept()方法)
while 1:
conn = self.request
addr = self.client_address
# 上面兩行代碼,等于 conn,addr = socket.accept(),只不過在socketserver模塊中已經替我們包裝好了,還替我們包裝了包括bind()、listen()、accept()方法
while 1:
accept_data = str(conn.recv(1024), encoding="utf8")
print(accept_data)
if accept_data == "byebye":
break
send_data = bytes(input(">>>>>"), encoding="utf8")
conn.sendall(send_data)
conn.close()
if __name__ == '__main__':
sever = socketserver.ThreadingTCPServer(("127.0.0.1", 6768),
MyServer) # 傳入 端口地址 和 我們新建的繼承自socketserver模塊下的BaseRequestHandler類 實例化對象
sever.serve_forever() # 通過調用對象的serve_forever()方法來激活服務端
想在 Python 建立客戶端 代碼如下
import socket
sk = socket.socket()
sk.connect(("127.0.0.1", 6768)) # 主動初始化與服務器端的連接
while True:
send_data = input("輸入發送內容:")
sk.sendall(bytes(send_data, encoding="utf8"))
if send_data == "byebye":
break
accept_data = str(sk.recv(1024), encoding="utf8")
print("".join(("接收內容:", accept_data)))
sk.close()
iOS 端主要代碼
@property (nonatomic, strong) NSInputStream *inputStream;//對應輸入流
@property (nonatomic, strong) NSOutputStream *outputStream;//對應輸出流
- 連接服務器
// 建立連接
NSString *host = @"127.0.0.1";
int port = 6768;
// 定義C語言輸入輸出流
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
// 把C語言的輸入輸出流轉化成OC對象
_inputStream = (__bridge NSInputStream *)(readStream);
_outputStream = (__bridge NSOutputStream *)(writeStream);
// 設置代理
_inputStream.delegate = self;
_outputStream.delegate = self;
#warning 缺少該步驟,代理有可能不工作
// 把輸入輸入流添加到主運行循環
[_inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
// 打開輸入輸出流
[_inputStream open];
[_outputStream open];
- socket 狀態
#pragma mark - NSStreamDelegate
-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode{
NSLog(@"%@",[NSThread currentThread]);
// NSStreamEventOpenCompleted = 1UL << 0,//輸入輸出流打開完成
// NSStreamEventHasBytesAvailable = 1UL << 1,//有字節可讀
// NSStreamEventHasSpaceAvailable = 1UL << 2,//可以發放字節
// NSStreamEventErrorOccurred = 1UL << 3,// 連接出現錯誤
// NSStreamEventEndEncountered = 1UL << 4// 連接結束
switch (eventCode) {
case NSStreamEventOpenCompleted:
NSLog(@"輸入輸出流打開完成");
break;
case NSStreamEventHasBytesAvailable:
NSLog(@"有字節可讀");
[self readData];
break;
case NSStreamEventHasSpaceAvailable:
NSLog(@"可以發送字節");
break;
case NSStreamEventErrorOccurred:
NSLog(@" 連接出現錯誤");
break;
case NSStreamEventEndEncountered:
NSLog(@"連接結束");
// 關閉輸入輸出流
[_inputStream close];
[_outputStream close];
// 從主運行循環移除
[_inputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
break;
default:
break;
}
}
- 讀取服務器返回數據
//建立一個緩沖區 可以放1024個字節
uint8_t buf[1024];
// 返回實際裝的字節數
NSInteger len = [_inputStream read:buf maxLength:sizeof(buf)];
// 把字節數組轉化成字符串
NSData *data = [NSData dataWithBytes:buf length:len];
// 從服務器接收到的數據
NSString *recStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
- 向服務器發送信息
//把Str轉成NSData
NSData *data = [model.text dataUsingEncoding:NSUTF8StringEncoding];
// 發送數據
[weakSelf.outputStream write:data.bytes maxLength:data.length];
最后就變成這樣了,界面是隨意寫的 有點丑 別介意!
示例.gif