代碼下載
服務端代碼下載地址
客戶端代碼下載地址
相關概念
socket是一個針對TCP和UDP編程的接口,你可以借助它建立TCP連接等。socket是對TCP/IP協議的封裝,Socket本身并不是協議,而是一個調用接口(API),通過Socket,我們才能使用TCP/IP協議。Socket的出現只是使得程序員更方便地使用TCP/IP協議棧而已,是對TCP/IP協議的抽象,從而形成了我們知道的一些最基本的函數接口。
socket連接:socket連接就是所謂的長連接,理論上客戶端和服務器端一旦建立起連接將不會主動斷掉;但是由于各種環境因素可能會是連接斷開,比如說:服務器端或客戶端主機down了,網絡故障,或者兩者之間長時間沒有數據傳輸,網絡防火墻可能會斷開該連接以釋放網絡資源。所以當一個socket連接中沒有數據的傳輸,那么為了維持連接需要發送心跳消息~~具體心跳消息格式是開發者自己定義的。
TCP連接的三次握手:
第一次握手:客戶端發送syn包(syn=j)到服務器,并進入SYN_SEND狀態,等待服務器確認;
第二次握手:服務器收到syn包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RECV狀態;
第三次握手:客戶端收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器進入ESTABLISHED狀態,完成三次握手。
握手過程中傳送的包里不包含數據,三次握手完畢后,客戶端與服務器才正式開始傳送數據。理想狀態下,TCP連接一旦建立,在通信雙方中的任何一方主動關閉連接之前,TCP 連接都將被一直保持下去。斷開連接時服務器和客戶端均可以主動發起斷開TCP連接的請求,斷開過程需要經過“四次握手”。
socket建立網絡連接的步驟
建立Socket連接至少需要一對套接字,其中一個運行于客戶端,稱為ClientSocket ,另一個運行于服務器端,稱為ServerSocket 。
套接字之間的連接過程分為三個步驟:服務器監聽,客戶端請求,連接確認。
1。服務器監聽:服務器端套接字并不定位具體的客戶端套接字,而是處于等待連接的狀態,實時監控網絡狀態,等待客戶端的連接請求。
2??蛻舳苏埱螅褐缚蛻舳说奶捉幼痔岢鲞B接請求,要連接的目標是服務器端的套接字。為此,客戶端的套接字必須首先描述它要連接的服務器的套接字,指出服務器端套接字的地址和端口號,然后就向服務器端套接字提出連接請求。
3。連接確認:當服務器端套接字監聽到或者說接收到客戶端套接字的連接請求時,就響應客戶端套接字的請求,建立一個新的線程,把服務器端套接字的描述發給客戶端,一旦客戶端確認了此描述,雙方就正式建立連接。而服務器端套接字繼續處于監聽狀態,繼續接收其他客戶端套接字的連接請求。
BSD socket
BSD socket:完全由c語言實現,并且可以在Objective-C代碼中使用。
優點:不同平臺中易于移植
缺點:
無法訪問操作系統內建的網絡特性(比如系統范圍的VPN)。
更糟糕的是初始化socket連接并不會自動打開設備的Wi-Fi或是蜂窩網絡,無線網絡會智能的關閉以節省電池電量,任何通信連接都會失敗,除非其他網絡進程激活了無線網。
CFNetwork對BSD Socket的分裝可以激活設備的無線網,所以幾乎所有場景都建議使用CFNetwork而不是BSD Socket.
最常用的API:
1.int socket(int, int, int):創建并初始化新的socket,如果成功放回一個文件描述符,如果失敗返回0。
2.int bind(int, const struct sockaddr *, socklen_t):為指定的地址與端口號分配socket。
3.int listen(int, int) __DARWIN_ALIAS(listen):用于服務端監聽客戶端
4.int accept(int, struct sockaddr * __restrict, socklen_t * __restrict):接受連接請求,將客戶端地址存儲到clientAddress中。
5.int connect(int, const struct sockaddr *, socklen_t):連接到指定的服務器。
6.ssize_t send(int, const void *, size_t, int):在socket上最多發送XX數據。
7.ssize_t recv(int, void *, size_t, int):在socket上最多讀取XX數據。
……
服務端邏輯:如果接收到客戶端發送過來”1”,那么回復”你猜!”;客戶端發送過來”2”,那么回復”謝謝!”;客戶端發送過來”3”,那么回復”對不起!”;客戶端發送過來”4”,那么回復”好的!”;客戶端發送過來其他的內容,那么一半概率原樣回復,一半概率回復”不知道你在說什么!”。

創建服務端:
1.創建socket
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;//AF_INET互聯網地址簇
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(address);
bzero(&server_addr.sin_zero, 8);
//創建socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0);//SOCK_STREAM 有鏈接
if (server_socket == -1) {
perror("socket error!");
return 1;
}
2.綁定socket:將創建的socket綁定到本地的IP地址和端口,此socket是半相關的,只是負責偵聽客戶端的連接請求,并不能用于和客戶端通信
int bind_result = bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (bind_result == -1) {
perror("bind error!");
return 1;
}
3.listen偵聽
if (listen(server_socket, 5)) {
perror("listen error!");
return 1;
}
4.接受客戶端連接
int client_socket;
socklen_t address_len;
struct sockaddr_in client_address;
for (; ; ) {
address_len = sizeof(client_address);
client_socket = accept(server_socket, (struct sockaddr*)&client_address, &address_len);
//1.使用多線程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//這個函數進行數據操作
str_echo(client_socket);
});
}
5.接收和發送數據
char buf[1024];
while (1) {
bzero(buf, 1024);
long byte_num = recv(socket, buf, 1024, 0);
if (byte_num < 0) {
return;
}
buf[byte_num] = '\0';
printf("client said:%s\n", buf);
char *result;
NSString *str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
if ([str isEqualToString:@"1"]) {
result = "你猜!";
}
else if ([str isEqualToString:@"2"])
{
result = "謝謝!";
}
else if ([str isEqualToString:@"3"])
{
result = "對不起!";
}
else if ([str isEqualToString:@"4"])
{
result = "好的!";
}
else
{
if (arc4random()%2 == 0) {
result = "不知道你在說什么!";
}
else
{
result = buf;
}
}
send(socket, result, 1024, 0);
}
創建客戶端:
struct sockaddr_in server_addr;
server_addr.sin_len = sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(11332);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&(server_addr.sin_zero), 8);
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("socket error");
return 1;
}
char receive_msg[1024];
char reply_msg[1024];
if (connect(server_socket, (struct sockaddr*)&server_addr, sizeof(struct sockaddr_in)) == 0) {
//connect 成功之后,其實系統將你創建的socket綁定到一個系統分配的端口上,且其為全相關,包含服務器端的信息,可以用來和服務器端進行通信。
while (1) {
bzero(reply_msg, 1024);
printf("replay:");
scanf("%s", reply_msg);
if (send(server_socket, reply_msg, 1024, 0) == -1) {
perror("send error!");
return 1;
}
bzero(receive_msg, 1024);
long byte_num = recv(server_socket, receive_msg, 1024, 0);
receive_msg[byte_num] = '\0';
printf("server said:%s\n", receive_msg);
}
}
return 0;
NSStream 讀寫文件
Cocoa Streams包含三個相關的類: NSStream、NSInputStream 和NSOutputStream。
NSStream:是個抽象類,定義了一些基本屬性和方法;
NSInputStream:是NSStream的子類,可通過它從NSData、File和socket中讀取數據流;
NSOutputStream:也是NSStream的子類,可通過它將數據流寫入NSData、File和socket。
還可以給stream對象設置Delegate(NSStreamDelegate),如果沒有精確了給stream指定Delegate,那么默認將Delegate設置為其自己。
NSStreamDelegate只有一個方法
(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
NSStreamEventNone :改默認值不代表任何事件
NSStreamEventOpenCompleted :socket被成功打開
NSStreamEventHasBytesAvailable :有可以讀取的字節
NSStreamEventHasSpaceAvailable :緩存中可以寫入字節
NSStreamEventErrorOccurred :操作出現錯誤CFReadStreamCopyError()和CFWriteStreamCopyError()會提供更多錯誤細節
NSStreamEventEndEncountered :socket達到字節流的末尾
通過NSStream對象讀數據,可以分為如下步驟完成:
a) 從數據源創建和初始化一個NSStream對象;
b) 配置run loop,并打開stream對象;
c) 響應NSInputStream事件(NSStreamDelegate);
d) 關閉NSInputStream對象。
- (void)viewDidLoad {
[super viewDidLoad];
NSString *documentStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *dataPath = [documentStr stringByAppendingPathComponent:@"data.txt"];
NSFileManager *manager = [NSFileManager defaultManager];
if ([manager fileExistsAtPath:dataPath]) {
self.read = YES;
NSLog(@"string:%@", [NSString stringWithContentsOfFile:dataPath encoding:NSUTF8StringEncoding error:nil]);
NSInputStream *inStream = [[NSInputStream alloc] initWithFileAtPath:dataPath];
[inStream setDelegate:self];
[inStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inStream open];
}
else
{
if ([manager createFileAtPath:dataPath contents:nil attributes:nil]) {
self.read = NO;
NSOutputStream *outStream = [[NSOutputStream alloc] initToFileAtPath:dataPath append:YES];
[outStream setDelegate:self];
[outStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outStream open];
}
else
{
NSLog(@"文件創建失??!");
}
}
}
#pragma mark - <NSStreamDelegate>代理方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
if (self.read) {
NSInputStream *inStream = (NSInputStream *)aStream;
switch (eventCode) {
case NSStreamEventHasBytesAvailable:
{
uint8_t data[1024];
[inStream read:data maxLength:1024];
printf("%s", data);
[inStream close];
[inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
inStream = nil;
}
break;
case NSStreamEventEndEncountered:
{
[inStream close];
[inStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
inStream = nil;
}
break;
default:
break;
}
}
else{
NSOutputStream *outStream = (NSOutputStream *)aStream;
switch (eventCode) {
case NSStreamEventHasSpaceAvailable:
{
uint8_t data[] = "{name:'張三', age:10}";
[outStream write:data maxLength:strlen((char *)data)];
[outStream close];
[outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
outStream = nil;
}
break;
case NSStreamEventEndEncountered:
{
[outStream close];
[outStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
outStream = nil;
}
break;
default:
break;
}
}
}
NSStream Socket
NSStream類不支持在IOS平臺上創建socket連接,而CFStream支持在IOS平臺的socket行為。所以若知道遠程主機的DNS或者是IP地址,可以使用CFStreamCreatePairWithSocketToHost函數來創建socket連接,通過該函數創建了CFStream類型為全雙工的socket連接,接著可以利用toll-free bridge,將CFStream對象轉換為NSStream對象。
通過NSStream對象進行Socket通信,與通過NSStream進行IO操作的步驟基本一樣:
a) 創建NSStream對象,通過CFStreamCreatePairWithSocketToHost函數創建CFReadStreamRef 和CFWriteStreamRef 對象;繼而將兩者轉換為NSInputStream 和NSOutputStream 對象;
b) 配置run loop,并打開NSInputStream 和NSOutputStream對象;
c) 響應事件,在Delegate中響應不同的信號;
d) 關閉NSStream對象。
服務端:服務端依然使用上面例子的服務端
客戶端的實現:點擊1~5這五個按鈕,分別從服務端返回不同的數據,進而展示出來。

由于實現相對簡單,就不加闡述說明了,實現代碼如下:
- (void)viewDidLoad {
[super viewDidLoad];
[self startSocket:@"127.0.0.1" andPort:11332];
}
- (void)startSocket:(NSString *)address andPort:(int)port
{
CFReadStreamRef readRef;
CFWriteStreamRef writeRef;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)address, port, &readRef, &writeRef);
NSInputStream *inputStream = (__bridge NSInputStream *)readRef;
NSOutputStream *outputStream = (__bridge NSOutputStream *)writeRef;
inputStream.delegate = self;
outputStream.delegate = self;
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
self.outputStream = outputStream;
}
- (IBAction)sendMsg:(UIButton *)sender {
if (sender.currentTitle > 0) {
const char *output = sender.currentTitle.UTF8String;
[self.outputStream write:(const uint8_t *)output maxLength:strlen(output)];
}
}
- (void)showMessage:(NSString *)msg
{
if (!self.msgLabel) {
UIFont *font = [UIFont systemFontOfSize:14];
UIColor *color = [UIColor blackColor];
UIColor *backColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.2];
UILabel *label = [[UILabel alloc] init];
label.textColor = color;
label.font = font;
label.textAlignment = NSTextAlignmentCenter;
label.backgroundColor = backColor;
self.msgLabel = label;
}
CGFloat screenW = [UIScreen mainScreen].bounds.size.width;
CGFloat screenH = [UIScreen mainScreen].bounds.size.height;
CGFloat W = screenW - 16;
CGSize size = [msg boundingRectWithSize:CGSizeMake(W, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:self.msgLabel.font} context:nil].size;
W = size.width;
CGFloat H = size.height;
CGFloat X = (screenW - W)/2;
CGFloat Y = (screenH - H)/2;
self.msgLabel.frame = CGRectMake(X, Y, W, H);
self.msgLabel.text = msg;
[self.view addSubview:self.msgLabel];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.msgLabel removeFromSuperview];
});
}
#pragma mark - <NSStreamDelegate>代理方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventNone:
break;
case NSStreamEventOpenCompleted:
break;
case NSStreamEventHasBytesAvailable:
{
uint8_t buf[1024];
NSInteger len = 0;
NSInputStream *inputStream = (NSInputStream *)aStream;
len = [inputStream read:buf maxLength:1024];
if (len) {
[self showMessage:[NSString stringWithCString:(const char *)buf encoding:NSUTF8StringEncoding]];
}
break;
}
case NSStreamEventHasSpaceAvailable:
break;
case NSStreamEventErrorOccurred:
{
[aStream close];
break;
}
case NSStreamEventEndEncountered:
{
[aStream close];
[aStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
aStream = nil;
break;
}
default:
break;
}
以上所述是小編給大家介紹的iOS socket網絡編程,希望對大家有所幫助,如果大家有任何疑問歡迎給我留言,小編會及時回復大的!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。