socket簡(jiǎn)介(摘取自百度百科)
描述
網(wǎng)絡(luò)上的兩個(gè)程序通過(guò)一個(gè)雙向的通信連接實(shí)現(xiàn)數(shù)據(jù)的交換症歇,這個(gè)連接的一端稱為一個(gè)socket。
建立網(wǎng)絡(luò)通信連接至少要一對(duì)端口號(hào)(socket)哗总。
socket本質(zhì)是編程接口(API)砚亭,對(duì)TCP/IP的封裝,TCP/IP也要提供可供程序員做網(wǎng)絡(luò)開(kāi)發(fā)所用的接口炫刷,這就是Socket編程接口橘原;HTTP是轎車籍铁,提供了封裝或者顯示數(shù)據(jù)的具體形式涡上;Socket是發(fā)動(dòng)機(jī),提供了網(wǎng)絡(luò)通信的能力拒名。
在連接成功時(shí)吩愧,應(yīng)用程序兩端都會(huì)產(chǎn)生一個(gè)Socket實(shí)例,操作這個(gè)實(shí)例增显,完成所需的會(huì)話雁佳。對(duì)于一個(gè)網(wǎng)絡(luò)連接來(lái)說(shuō),套接字是平等的同云,并沒(méi)有差別糖权,不因?yàn)樵诜?wù)器端或在客戶端而產(chǎn)生不同級(jí)別。不管是Socket還是ServerSocket它們的工作都是通過(guò)SocketImpl類及其子類完成的炸站。
連接過(guò)程
根據(jù)連接啟動(dòng)的方式以及本地套接字要連接的目標(biāo)星澳,套接字之間的連接過(guò)程可以分為三個(gè)步驟:服務(wù)器監(jiān)聽(tīng),客戶端請(qǐng)求武契,連接確認(rèn)。
(1)服務(wù)器監(jiān)聽(tīng):是服務(wù)器端套接字并不定位具體的客戶端套接字荡含,而是處于等待連接的狀態(tài)咒唆,實(shí)時(shí)監(jiān)控網(wǎng)絡(luò)狀態(tài)此熬。
(2)客戶端請(qǐng)求:是指由客戶端的套接字提出連接請(qǐng)求抄囚,要連接的目標(biāo)是服務(wù)器端的套接字。為此闭翩,客戶端的套接字必須首先描述它要連接的服務(wù)器的套接字误债,指出服務(wù)器端套接字的地址和端口號(hào)浸船,然后就向服務(wù)器端套接字提出連接請(qǐng)求。
(3)連接確認(rèn):是指當(dāng)服務(wù)器端套接字監(jiān)聽(tīng)到或者說(shuō)接收到客戶端套接字的連接請(qǐng)求寝蹈,它就響應(yīng)客戶端套接字的請(qǐng)求李命,建立一個(gè)新的線程,把服務(wù)器端套接字的描述發(fā)給客戶端箫老,一旦客戶端確認(rèn)了此描述封字,連接就建立好了。而服務(wù)器端套接字繼續(xù)處于監(jiān)聽(tīng)狀態(tài)耍鬓,繼續(xù)接收其他客戶端套接字的連接請(qǐng)求阔籽。
iOS寫一個(gè)原生socket
1. 導(dǎo)入頭文件以及宏定義
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
//htons : 將一個(gè)無(wú)符號(hào)短整型的主機(jī)數(shù)值轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序,不同cpu 是不同的順序 (big-endian大尾順序 , little-endian小尾順序)
#define SocketPort htons(8040) //端口
//inet_addr是一個(gè)計(jì)算機(jī)函數(shù)牲蜀,功能是將一個(gè)點(diǎn)分十進(jìn)制的IP轉(zhuǎn)換成一個(gè)長(zhǎng)整數(shù)型數(shù)
#define SocketIP inet_addr("127.0.0.1") // ip
2. 創(chuàng)建socket
函數(shù)原型:
int socket(int domain, int type, int protocol);
函數(shù)使用:
//屬性笆制,用于接收socket創(chuàng)建成功后的返回值
@property (nonatomic, assign) int clinenId;
_clinenId = socket(AF_INET, SOCK_STREAM, 0);
if (_clinenId == -1) {
NSLog(@"創(chuàng)建socket 失敗");
return;
}
參數(shù)說(shuō)明:
domain:協(xié)議域,又稱協(xié)議族(family)涣达。常用的協(xié)議族有AF_INET(ipv4)在辆、AF_INET6(ipv6)证薇、AF_LOCAL(或稱AF_UNIX,Unix域Socket)开缎、AF_ROUTE等棕叫。協(xié)議族決定了socket的地址類型,在通信中必須采用對(duì)應(yīng)的地址奕删,如AF_INET決定了要用ipv4地址(32位的)與端口號(hào)(16位的)的組合俺泣、AF_UNIX決定了要用一個(gè)絕對(duì)路徑名作為地址。
type:指定Socket類型完残。常用的socket類型有SOCK_STREAM伏钠、SOCK_DGRAM、SOCK_RAW谨设、SOCK_PACKET熟掂、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一種面向連接的Socket扎拣,針對(duì)于面向連接的TCP服務(wù)應(yīng)用赴肚。數(shù)據(jù)報(bào)式Socket(SOCK_DGRAM)是一種無(wú)連接的Socket,對(duì)應(yīng)于無(wú)連接的UDP服務(wù)應(yīng)用二蓝。
protocol:指定協(xié)議誉券。常用協(xié)議有IPPROTO_TCP、IPPROTO_UDP刊愚、IPPROTO_STCP踊跟、IPPROTO_TIPC等,分別對(duì)應(yīng)TCP傳輸協(xié)議鸥诽、UDP傳輸協(xié)議商玫、STCP傳輸協(xié)議、TIPC傳輸協(xié)議牡借。
注意:type和protocol不可以隨意組合拳昌,如SOCK_STREAM不可以跟IPPROTO_UDP組合。當(dāng)?shù)谌齻€(gè)參數(shù)為0時(shí)钠龙,會(huì)自動(dòng)選擇第二個(gè)參數(shù)類型對(duì)應(yīng)的默認(rèn)協(xié)議地回。
返回值:如果調(diào)用成功就返回新創(chuàng)建的套接字的描述符,如果失敗就返回INVALID_SOCKET(Linux下失敗返回-1)俊鱼。套接字描述符是一個(gè)整數(shù)類型的值刻像。
3. 創(chuàng)建連接
函數(shù)原型:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
函數(shù)使用:
3.1 創(chuàng)建socketAddr
/**
__uint8_t sin_len; 假如沒(méi)有這個(gè)成員,其所占的一個(gè)字節(jié)被并入到sin_family成員中
sa_family_t sin_family; 一般來(lái)說(shuō)AF_INET(地址族)PF_INET(協(xié)議族)
in_port_t sin_port; // 端口
struct in_addr sin_addr; // ip
char sin_zero[8]; 沒(méi)有實(shí)際意義,只是為了 跟SOCKADDR結(jié)構(gòu)在內(nèi)存中對(duì)齊
*/
struct sockaddr_in socketAddr;
socketAddr.sin_family = AF_INET;//當(dāng)前這個(gè)是ipv4
socketAddr.sin_port = SocketPort; //這里定義了一個(gè)宏
struct in_addr socketIn_addr;
socketIn_addr.s_addr = SocketIP; // 也是宏
socketAddr.sin_addr = socketIn_addr;
3.2 連接
int result = connect(_clinenId, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
if (result != 0) {
NSLog(@"連接socket 失敗");
return;
}
NSLog(@"連接成功");
參數(shù)說(shuō)明:
sockfd:標(biāo)識(shí)一個(gè)已連接套接口的描述字并闲,就是我們剛剛創(chuàng)建的那個(gè)_clinenId细睡。
addr:指針,指向目的套接字的地址帝火。
addrlen:接收返回地址的緩沖區(qū)長(zhǎng)度溜徙。
返回值:成功則返回0湃缎,失敗返回非0,錯(cuò)誤碼GetLastError()蠢壹。
4. 發(fā)送消息
函數(shù)原型:
ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
函數(shù)使用:
const char *msg = @"消息內(nèi)容".UTF8String;
//send() 等同于 write() 多提供了一個(gè)參數(shù)來(lái)控制讀寫操作
ssize_t sendLen = send(self.clinenId, msg, strlen(msg), 0);
NSLog(@"發(fā)送了:%ld字節(jié)",sendLen);
參數(shù)說(shuō)明:
sockfd:一個(gè)用于標(biāo)識(shí)已連接套接口的描述字嗓违。
buff:包含待發(fā)送數(shù)據(jù)的緩沖區(qū)。
nbytes:緩沖區(qū)中數(shù)據(jù)的長(zhǎng)度图贸。
flags:調(diào)用執(zhí)行方式蹂季。
返回值:如果成功,則返回發(fā)送的字節(jié)數(shù)疏日,失敗則返回SOCKET_ERROR偿洁,一個(gè)中文UTF8 編碼對(duì)應(yīng) 3 個(gè)字節(jié)。所以上面發(fā)送了3*4字節(jié)沟优。
5. 接收數(shù)據(jù)
函數(shù)原型:
ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
函數(shù)使用:
uint8_t buffer[1024];
//recv() 等同于 read() 多提供了一個(gè)參數(shù)來(lái)控制讀寫操作
ssize_t recvLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
NSLog(@"接收到了:%ld字節(jié)",recvLen);
if (recvLen==0) {
NSLog(@"此次傳輸長(zhǎng)度為0 如果下次還為0 請(qǐng)檢查連接");
}
// 接收到的數(shù)據(jù)轉(zhuǎn)換
NSData *recvData = [NSData dataWithBytes:buffer length:recvLen];
NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
NSLog(@"%@",recvStr);
參數(shù)說(shuō)明:
sockfd:一個(gè)用于標(biāo)識(shí)已連接套接口的描述字涕滋。
buff:包含待發(fā)送數(shù)據(jù)的緩沖區(qū)。
nbytes:緩沖區(qū)中數(shù)據(jù)的長(zhǎng)度挠阁。
flags:調(diào)用執(zhí)行方式宾肺。
返回值:如果成功,則返回讀入的字節(jié)數(shù)侵俗,失敗則返回SOCKET_ERROR锨用。
完整代碼
#pragma mark - 創(chuàng)建socket建立連接
- (IBAction)socketConnetAction:(UIButton *)sender {
// 1: 創(chuàng)建socket
int socketID = socket(AF_INET, SOCK_STREAM, 0);
self.clinenId= socketID;
if (socketID == -1) {
NSLog(@"創(chuàng)建socket 失敗");
return;
}
// 2: 連接socket
struct sockaddr_in socketAddr;
socketAddr.sin_family = AF_INET;
socketAddr.sin_port = SocketPort;
struct in_addr socketIn_addr;
socketIn_addr.s_addr = SocketIP;
socketAddr.sin_addr = socketIn_addr;
int result = connect(socketID, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
if (result != 0) {
NSLog(@"連接失敗");
return;
}
NSLog(@"連接成功");
// 調(diào)用開(kāi)始接受信息的方法
// while 如果主線程會(huì)造成堵塞
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self recvMsg];
});
}
#pragma mark - 發(fā)送消息
- (IBAction)sendMsgAction:(id)sender {
//3: 發(fā)送消息
if (self.sendMsgContent_tf.text.length == 0) {
return;
}
const char *msg = self.sendMsgContent_tf.text.UTF8String;
ssize_t sendLen = send(self.clinenId, msg, strlen(msg), 0);
NSLog(@"發(fā)送 %ld 字節(jié)",sendLen);
}
#pragma mark - 接受數(shù)據(jù)
- (void)recvMsg{
// 4. 接收數(shù)據(jù)
while (1) {
uint8_t buffer[1024];
ssize_t recvLen = recv(self.clinenId, buffer, sizeof(buffer), 0);
NSLog(@"接收到了:%ld字節(jié)",recvLen);
if (recvLen == 0) {
continue;
}
// buffer -> data -> string
NSData *data = [NSData dataWithBytes:buffer length:recvLen];
NSString *str= [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@---%@",[NSThread currentThread],str);
}
}
調(diào)試
首先 運(yùn)行效果
方式一,簡(jiǎn)單快捷
- 打開(kāi)命令行工具輸入 nc -lk 8040
-
點(diǎn)擊連接socket
-
命令行工具隨便輸入字符 回車
4.模擬器隨便輸入字符 發(fā)送
方式二 自己寫一個(gè)本地socket服務(wù)端
聽(tīng)起來(lái)好想很牛逼坡慌,其實(shí)跟上面寫客戶端差不多黔酥。
多了bind()藻三,listen()洪橘,accept()三步。
頭文件棵帽、宏熄求、屬性
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#define SocketPort htons(8040)
#define SocketIP inet_addr("127.0.0.1")
@property (nonatomic, assign) int serverId;
@property (nonatomic, assign) int client_socket;
- socket()
self.serverId = socket(AF_INET, SOCK_STREAM, 0);
if (self.serverId == -1) {
NSLog(@"創(chuàng)建socket 失敗");
return;
}
NSLog(@"創(chuàng)建socket 成功");
- bind()
struct sockaddr_in socketAddr;
socketAddr.sin_family = AF_INET;
socketAddr.sin_port = SocketPort;
struct in_addr socketIn_addr;
socketIn_addr.s_addr = SocketIP;
socketAddr.sin_addr = socketIn_addr;
bzero(&(socketAddr.sin_zero), 8);
// 2: 綁定socket
int bind_result = bind(self.serverId, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
if (bind_result == -1) {
NSLog(@"綁定socket 失敗");
return;
}
NSLog(@"綁定socket成功");
- listen()
// 3: 監(jiān)聽(tīng)socket
int listen_result = listen(self.serverId, kMaxConnectCount);
if (listen_result == -1) {
NSLog(@"監(jiān)聽(tīng)失敗");
return;
}
NSLog(@"監(jiān)聽(tīng)成功");
- accept()
dispatch_async(dispatch_get_global_queue(0, 0), ^{
struct sockaddr_in client_address;
socklen_t address_len;
// accept函數(shù)
int client_socket = accept(self.serverId, (struct sockaddr *)&client_address, &address_len);
self.client_socket = client_socket;
if (client_socket == -1) {
NSLog(@"接受 %u 客戶端錯(cuò)誤",address_len);
}else{
NSString *acceptInfo = [NSString stringWithFormat:@"客戶端 in,socket:%d",client_socket];
NSLog(@"%@",acceptInfo);
//開(kāi)始接受消息
[self receiveMsgWithClietnSocket:client_socket];
}
});
- recv()
while (1) {
// 5: 接受客戶端傳來(lái)的數(shù)據(jù)
char buf[1024] = {0};
long iReturn = recv(clientSocket, buf, 1024, 0);
if (iReturn>0) {
NSLog(@"客戶端來(lái)消息了");
// 接收到的數(shù)據(jù)轉(zhuǎn)換
NSData *recvData = [NSData dataWithBytes:buf length:iReturn];
NSString *recvStr = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
NSLog(@"%@",recvStr);
}else if (iReturn == -1){
NSLog(@"讀取消息失敗");
break;
}else if (iReturn == 0){
NSLog(@"客戶端走了");
close(clientSocket);
break;
}
}
- send()
const char *msg = @"給客戶端發(fā)消息".UTF8String;
ssize_t sendLen = send(self.client_socket, msg, strlen(msg), 0);
NSLog(@"發(fā)送了:%ld字節(jié)",sendLen);
- close()
int close_result = close(self.client_socket);
if (close_result == -1) {
NSLog(@"socket 關(guān)閉失敗");
return;
}else{
NSLog(@"socket 關(guān)閉成功");
}
那么這篇文章就到這里,寫這篇文章的主要目的是為了讓后面學(xué)習(xí)GCDAsyncSocket時(shí)逗概,加深印象弟晚、深入理解其實(shí)現(xiàn)原理。