BSD socket API
/**
socket 建立並初始化 socket,返回該 socket 的檔案描述符,如果描述符為 -1 表示建立失敗。
@param addressFamily 是 IPv4(AF_INET) 或 IPv6(AF_INET6)。
@param type 表示 socket 的型別,通常是流stream(SOCK_STREAM) 或資料包文datagram(SOCK_DGRAM)
@param protocol 引數通常設定為0,以便讓系統自動為選擇我們合適的協議,對於 stream socket 來說會是 TCP 協議(IPPROTO_TCP),而對於 datagram來說會是 UDP 協議(IPPROTO_UDP)。
@return 返回該 socket 的檔案描述符
*//
int socket(int addressFamily, int type, int protocol);
/**
伺服器端偵聽客戶端的請求
@param socketFileDescriptor 服務端socket
@param backlogSize 客戶端連線請求緩衝區佇列的大小
@return 0 成功或者其他 錯誤代號,(不是非0即真)
*/
int listen(int socketFileDescriptor, int backlogSize) __DARWIN_ALIAS(listen);
/**
接受客戶端連線請求並將客戶端的網路地址資訊儲存到 clientAddress 中。
當客戶端連線請求被伺服器接受之後,客戶端和伺服器之間的鏈路就建立好了,兩者就可以通訊了
@param socketFileDescriptor 伺服器的socket描述字
@param address 指向struct sockaddr *的指標,用於返回客戶端的協議地址
@param addressStructLength address結構體資料長度
@return 由核心自動生成的一個全新的描述字,代表與返回客戶的TCP連線。
*/
int accept(int socketFileDescriptor, struct sockaddr * __restrict address, socklen_t * __restrict addressStructLength)
__DARWIN_ALIAS_C(accept);
/**
客戶端向特定網路地址的伺服器傳送連線請求,連線成功返回0,失敗返回 -1。
當伺服器建立好之後,客戶端通過呼叫該介面向伺服器發起建立連線請求。對於 UDP 來說,該介面是可選的,如果呼叫了該介面,表明設定了該 UDP socket 預設的網路地址。對 TCP socket來說這就是傳說中三次握手建立連線發生的地。
注意:該介面呼叫會阻塞當前執行緒,直到伺服器返回。
@param socketFileDescriptor 客戶端sockets
@param serverAddress 向資料結構sockaddr的指標,其中包括目的埠和IP地址,伺服器的"結構體"地址;提示:C 語言中沒有物件
@param serverAddressLength 結構體資料長度
@return 0 成功或者其他 錯誤代號,(不是非0即真)
*/
int connect(int socketFileDescriptor, const struct sockaddr *serverAddress, socklen_t serverAddressLength) __DARWIN_ALIAS_C(connect);
/**
從 socket 中讀取資料,讀取成功返回成功讀取的位元組數,否則返回 -1。
一旦連線建立好之後,就可以通過 send或者receive 介面傳送或接收資料了。注意呼叫 connect 設定了預設網路地址的 UDP socket 也可以呼叫該介面來傳送資料。
@param socketFileDescriptor 客戶端sockets
@param buf 接受資料的buffer
@param bufferLength buffer長度
@param flags 接收方式,0表示阻塞,必須等待伺服器返回資料
@return 如果成功,則返回讀入的位元組數,失敗則返回SOCKET_ERROR
*/
ssize_t recv(int socketFileDescriptor, void *buf, size_t bufferLength, int flags) __DARWIN_ALIAS_C(recv);
/**
通過 socket 傳送資料,傳送成功返回成功傳送的位元組數,否則返回 -1。
一旦連線建立好之後,就可以通過 send/receive 介面傳送或接收資料了。注意呼叫 connect 設定了預設網路地址的 UDP socket 也可以呼叫該介面來接收資料。
@param socketFileDescriptor 指定傳送端套接字描述符
@param buf 存放應用程式要傳送資料的緩衝區
@param bufferLength 傳送的資料的位元組數
@param flags 傳送方式,0表示阻塞,必須等待伺服器返回資料
@return 如果成功,則返回傳送的位元組數,失敗則返回SOCKET_ERROR
*/
ssize_t send(int socketFileDescriptor, const void *buf, size_t bufferLength, int flags) __DARWIN_ALIAS_C(send);
/**
將 socket 與特定主機地址與埠號繫結,成功繫結返回0,失敗返回 -1。
成功繫結之後,根據協議(TCPUDP)的不同,我們可以對 socket 進行不同的操作:
UDP:因為 UDP 是無連線的,繫結之後就可以利用 UDP socket 傳送資料了。
TCP:而 TCP 是需要建立端到端連線的,為了建立 TCP 連線伺服器必須呼叫 listen(int socketFileDescriptor, int backlogSize) 來設定伺服器的緩衝區佇列以接收客戶端的連線請求,backlogSize 表示客戶端連線請求緩衝區佇列的大小。當呼叫 listen 設定之後,伺服器等待客戶端請求,然後呼叫 accept 來接受客戶端的連線請求。
@param socketFileDescriptor 主機socket
@param addressToBind 資料結構sockaddr的指標,其中包括目的埠和IP地址,伺服器的"結構體"地址;提示:C 語言中沒有物件
@param addressStructLength sockaddr結構體資料長度
@return 0 成功/其他 錯誤代號,(不是非0即真)
*/
int bind(int socketFileDescriptor, const struct sockaddr *addressToBind, socklen_t addressStructLength) __DARWIN_ALIAS(bind);
/**
伺服器端偵聽客戶端的請求
@param socketFileDescriptor 服務端socket
@param backlogSize 客戶端連線請求緩衝區佇列的大小
@return 0 成功/其他 錯誤代號,(不是非0即真)
*/
int listen(int socketFileDescriptor, int backlogSize) __DARWIN_ALIAS(listen);
/**
接受客戶端連線請求並將客戶端的網路地址資訊儲存到 clientAddress 中。
當客戶端連線請求被伺服器接受之後,客戶端和伺服器之間的鏈路就建立好了,兩者就可以通訊了
@param socketFileDescriptor 伺服器的socket描述字
@param address 指向struct sockaddr *的指標,用於返回客戶端的協議地址
@param addressStructLength address結構體資料長度
@return 由核心自動生成的一個全新的描述字,代表與返回客戶的TCP連線。
*/
int accept(int socketFileDescriptor, struct sockaddr * __restrict address, socklen_t * __restrict addressStructLength)
__DARWIN_ALIAS_C(accept);
一、客戶端操作
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);// 結構體長度
server_addr.sin_family = AF_INET; //sin_family指代協議族,在socket程式設計中只能是AF_INET
server_addr.sin_port = htons(1234);//儲存埠號(使用網路位元組順序),在linux下,埠號的範圍0~65535,同時0~1024範圍的埠號已經被系統使用或保留。
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");//儲存IP地址,使用in_addr這個資料結構
bzero(&(server_addr.sin_zero), 8);//是為了讓sockaddr與sockaddr_in兩個資料結構保持大小相同而保留的空位元組, 初始值應該使用函式 bzero() 來全部置零
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//建立新的socket
int aResult = connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in));
if (aResult == -1) {
NSLog(@"連結失敗");
}else{
self.server_socket = server_socket;
[self acceptFromServer];
}
});
- (void)acceptFromServer {
while (1) {
//接受伺服器傳來的資料
char buf[1024];
long iReturn = recv(self.server_socket, buf, 1024, 0);
if (iReturn > 0) {
NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
//篩選字首
if ([str hasPrefix:@"list:"]) {
NSString *arrayStr = [str substringFromIndex:5];
NSArray *list = [arrayStr componentsSeparatedByString:@","];
self.userArray = [NSMutableArray arrayWithArray:list];
dispatch_async(dispatch_get_main_queue(), ^{
[self.onlineTable reloadData];
});
NSLog(@"當前線上使用者列表:%@",arrayStr);
}else{
//回到主執行緒 介面上顯示內容
[self showLogsWithString:str];
}
}else if (iReturn == -1){
NSLog(@"接受失敗-1");
break;
}
}
}
- (void)sendMsg:(NSString*)msg {
char *buf[1024] = {0};
const char *p1 = (char*)buf;
p1 = [msg cStringUsingEncoding:NSUTF8StringEncoding];
send(self.server_socket, p1, 1024, 0);
}
二、服務端操作
//建立socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
NSLog(@"建立失敗");
[self showLogsWithString:@"socket建立失敗"];
} else {
//繫結地址和埠
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(1234);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero), 8);
int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (bind_result == -1) {
NSLog(@"繫結埠失敗");
[self showLogsWithString:@"繫結埠失敗"];
} else {
if (listen(server_socket, kMaxConnectCount)==-1) {
NSLog(@"監聽失敗");
[self showLogsWithString:@"監聽失敗"];
} else {
for (int i = 0; i < kMaxConnectCount; i++) {
//接受客戶端的連結
[self acceptClientWithServerSocket:server_socket];
}
}
}
}
- (void)acceptClientWithServerSocket:(int)server_socket {
struct sockaddr_in client_address;
socklen_t address_len;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
//建立新的socket
while (1) {
int client_socket = accept(server_socket, (struct sockaddr*)&client_address,&address_len );
if (client_socket == -1) {
[self showLogsWithString:@"接受客戶端連結失敗"];
NSLog(@"接受客戶端連結失敗");
}else{
NSString *acceptInfo = [NSString stringWithFormat:@"客戶端 in,socket:%d",client_socket];
[self showLogsWithString:acceptInfo];
//接受客戶端資料
[self recvFromClinetWithSocket:client_socket];
}
}
});
}
- (void)recvFromClinetWithSocket:(int)client_socket{
while (1) {
//接受客戶端傳來的資料
char buf[1024] = {0};
long iReturn = recv(client_socket, buf, 1024, 0);
if (iReturn > 0) {
NSLog(@"客戶端來訊息了");
NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
[self showLogsWithString:[NSString stringWithFormat:@"客戶端來訊息了:%@",str]];
[self checkRecvStr:str andClientSocket:client_socket];
} else if (iReturn == -1) {
NSLog(@"讀取訊息失敗");
[self showLogsWithString:@"讀取訊息失敗"];
break;
} else if (iReturn == 0) {
NSLog(@"客戶端走了");
[self showLogsWithString:[NSString stringWithFormat:@"客戶端 out socket:%d",client_socket]];
NSMutableArray *array = [NSMutableArray arrayWithArray:self.clientArray];
for (ClientModel *model in array) {
if (model.clientSocket == client_socket) {
[self.clientNameArray removeObject:model.clientName];
[self.clientArray removeObject:model];
}
}
close(client_socket);
break;
}
}
}