iOS的socket開發基礎

因爲博客遷移至www.coderyi.com,文章請看http://www.coderyi.com/archives/429git

socket簡介

首先讓咱們經過一張圖知道socket在哪裏?github

Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。編程

tcp和udp的區別

在這裏就必須講一下udp和tcp的區別了服務器

TCP:面向鏈接、傳輸可靠(保證數據正確性,保證數據順序)、用於傳輸大量數據(流模式)、速度慢,創建鏈接須要開銷較多(時間,系統資源)。網絡

UDP:面向非鏈接、傳輸不可靠、用於傳輸少許數據(數據包模式)、速度快。框架

關於TCP是一種流模式的協議,UDP是一種數據報模式的協議,這裏要說明一下,TCP是面向鏈接的,也就是說,在鏈接持續的過程當中,socket中收到的數據都是由同一臺主機發出的(劫持什麼的不考慮),所以,知道保證數據是有序的到達就好了,至於每次讀取多少數據本身看着辦。socket

而UDP是無鏈接的協議,也就是說,只要知道接收端的IP和端口,且網絡是可達的,任何主機均可以向接收端發送數據。這時候,若是一次能讀取超過一個報文的數據,則會亂套。好比,主機A向發送了報文P1,主機B發送了報文P2,若是可以讀取超過一個報文的數據,那麼就會將P1和P2的數據合併在了一塊兒,這樣的數據是沒有意義的。async

TCP三次握手和四次揮手

相對於SOCKET開發者,TCP建立過程和鏈接拆除過程是由TCP/IP協議棧自動建立的。所以開發者並不須要控制這個過程。可是對於理解TCP底層運做機制,至關有幫助。tcp

所以在這裏詳細解釋一下這兩個過程。函數

TCP三次握手

所謂三次握手(Three-way Handshake),是指創建一個TCP鏈接時,須要客戶端和服務器總共發送3個包。

 三次握手的目的是鏈接服務器指定端口,創建TCP鏈接,並同步鏈接雙方的序列號和確認號並交換 TCP 窗口大小信息.在socket編程中,客戶端執行connect()時。將觸發三次握手。

首先了解一下幾個標誌,SYN(synchronous),同步標誌,ACK (Acknowledgement),即確認標誌,seq應該是Sequence Number,序列號的意思,另外還有四次握手的fin,應該是final,表示結束標誌。

第一次握手:客戶端發送一個TCP的SYN標誌位置1的包指明客戶打算鏈接的服務器的端口,以及初始序號X,保存在包頭的序列號(Sequence Number)字段裏。

第二次握手:服務器發回確認包(ACK)應答。即SYN標誌位和ACK標誌位均爲1同時,將確認序號(Acknowledgement Number)設置爲客戶的序列號加1以,即X+1。

第三次握手:客戶端再次發送確認包(ACK) SYN標誌位爲0,ACK標誌位爲1。而且把服務器發來ACK的序號字段+1,放在肯定字段中發送給對方.而且在數據段放寫序列號的+1。

tcp四次揮手

TCP的鏈接的拆除須要發送四個包,所以稱爲四次揮手(four-way handshake)。客戶端或服務器都可主動發起揮手動做,在socket

編程中,任何一方執行close()操做便可產生揮手操做。

其實有個問題,爲何鏈接的時候是三次握手,關閉的時候倒是四次揮手?

由於當Server端收到Client端的SYN鏈接請求報文後,能夠直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。可是關閉鏈接時,當Server端收到FIN報文時,極可能並不會當即關閉SOCKET,因此只能先回復一個ACK報文,告訴Client端,"你發的FIN報文我收到了"。只有等到我Server端全部的報文都發送完了,我才能發送FIN報文,所以不能一塊兒發送。故須要四步握手。

tcpsocket和udpsocket的具體實現

講了這麼久,終於要開始講socket的具體實現了,iOS提供了Socket網絡編程的接口CFSocket,不過這裏使用BSD Socket。

tcp和udp的socket是有區別的,這裏給出這兩種的設計框架

基本TCP客戶—服務器程序設計基本框架

基本UDP客戶—服務器程序設計基本框架流程圖

經常使用的Socket類型有兩種:流式Socket(SOCK_STREAM)和數據報式Socket(SOCK_DGRAM)。流式是一種面向鏈接的Socket,針對於面向鏈接的TCP服務應用;數據報式Socket是一種無鏈接的Socket,對應於無鏈接的UDP服務應用。

一、socket調用庫函數主要有:

建立套接字 

        Socket(af,type,protocol)

創建地址和套接字的聯繫 

        bind(sockid, local addr, addrlen)

服務器端偵聽客戶端的請求 

        listen( Sockid ,quenlen)

創建服務器/客戶端的鏈接 (面向鏈接TCP) 

        客戶端請求鏈接 

        Connect(sockid, destaddr, addrlen)

        服務器端等待從編號爲Sockid的Socket上接收客戶鏈接請求 

        newsockid=accept(Sockid,Clientaddr, paddrlen)

發送/接收數據 

        面向鏈接:

        send(sockid, buff, bufflen) 
        recv( )

        面向無鏈接:

        sendto(sockid,buff,…,addrlen) 
        recvfrom( )

釋放套接字 

        close(sockid)

tcpsocket的具體實現

服務器的工做流程:首先調用socket函數建立一個Socket,而後調用bind函數將其與本機地址以及一個本地端口號綁定,而後調用listen在相應的socket上監聽,當accpet接收到一個鏈接服務請求時,將生成一個新的socket。服務器顯示該客戶機的IP地址,並經過新的socket向客戶端發送字符串" hi,I am server!"。最後關閉該socket。

#import <Foundation/Foundation.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, const char * argv[])
{
    @autoreleasepool {
//        1
        int err;
        int fd=socket(AF_INET, SOCK_STREAM  , 0);
        BOOL success=(fd!=-1);
//        1
//   2     
        if (success) {
            NSLog(@"socket success");  
            struct sockaddr_in addr;
            memset(&addr, 0, sizeof(addr));
            addr.sin_len=sizeof(addr);
            addr.sin_family=AF_INET;
//            =======================================================================
            addr.sin_port=htons(1024);
//        ============================================================================
             addr.sin_addr.s_addr=INADDR_ANY;
            err=bind(fd, (const struct sockaddr *)&addr, sizeof(addr));
            success=(err==0);
        }
 //   2          
//        ============================================================================
        if (success) {
            NSLog(@"bind(綁定) success");
            err=listen(fd, 5);//開始監聽
            success=(err==0);
        }
//    ============================================================================     
        //3
        if (success) {
            NSLog(@"listen success");
            while (true) {
                struct sockaddr_in peeraddr;
                int peerfd;
                socklen_t addrLen;
                addrLen=sizeof(peeraddr);
                NSLog(@"prepare accept");
                peerfd=accept(fd, (struct sockaddr *)&peeraddr, &addrLen);
                success=(peerfd!=-1);
//    ============================================================================
                if (success) {
                    NSLog(@"accept success,remote address:%s,port:%d",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
                    char buf[1024];
                    ssize_t count;
                    size_t len=sizeof(buf);
                    do {
                        count=recv(peerfd, buf, len, 0);
                        NSString* str = [NSString stringWithCString:buf encoding:NSUTF8StringEncoding];
                        NSLog(@"%@",str);
                    } while (strcmp(buf, "exit")!=0);
                }
//    ============================================================================
                close(peerfd);
            } 
        }  
//3     
    }
    return 0;
}

客戶端的工做流程:首先調用socket函數建立一個Socket,而後調用bind函數將其與本機地址以及一個本地端口號綁定,請求鏈接服務器,經過新的socket向客戶端發送字符串" hi,I am client!"。最後關閉該socket。

//
//  main.m
//  kewai_SocketClient
//
#import <Foundation/Foundation.h>
#include <sys/socket.h>
#include <netinet/in.h>
#import <arpa/inet.h>
int main(int argc, const char * argv[])
{
    @autoreleasepool {
     //        1 
         int err;
        int fd=socket(AF_INET, SOCK_STREAM, 0);
        BOOL success=(fd!=-1);
        struct sockaddr_in addr;
     //        1
        //   2
        if (success) {
            NSLog(@"socket success");
            memset(&addr, 0, sizeof(addr));
            addr.sin_len=sizeof(addr);
            addr.sin_family=AF_INET;
            addr.sin_addr.s_addr=INADDR_ANY;
            err=bind(fd, (const struct sockaddr *)&addr, sizeof(addr));
            success=(err==0);
        }
        //   2
        //3
        if (success) {
//============================================================================           
            struct sockaddr_in peeraddr;
            memset(&peeraddr, 0, sizeof(peeraddr));
            peeraddr.sin_len=sizeof(peeraddr);
            peeraddr.sin_family=AF_INET;
            peeraddr.sin_port=htons(1024);
//            peeraddr.sin_addr.s_addr=INADDR_ANY;
            peeraddr.sin_addr.s_addr=inet_addr("172.16.10.120");
//            這個地址是服務器的地址, 
            socklen_t addrLen;
            addrLen =sizeof(peeraddr);
            NSLog(@"connecting");
            err=connect(fd, (struct sockaddr *)&peeraddr, addrLen);
            success=(err==0);
            if (success) {
//                struct sockaddr_in addr;
                err =getsockname(fd, (struct sockaddr *)&addr, &addrLen);
                success=(err==0);
//============================================================================
//============================================================================
                if (success) {
                      NSLog(@"connect success,local address:%s,port:%d",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
                    char buf[1024];
                    do {
                        printf("input message:");
                        scanf("%s",buf);
                        send(fd, buf, 1024, 0);
                    } while (strcmp(buf, "exit")!=0); 
                }
            }
            else{
                NSLog(@"connect failed");
            }
        }
    //    ============================================================================
        //3
    }
    return 0;
}

udpsocket的具體實現

下面是udpsocket的具體實現

服務器的工做流程:首先調用socket函數建立一個Socket,而後調用bind函數將其與本機地址以及一個本地端口號綁定,接收到一個客戶端時,服務器顯示該客戶端的IP地址,並將字串返回給客戶端。 

/*
 *UDP/IP應用編程接口(API)
 *服務器的工做流程:首先調用socket函數建立一個Socket,而後調用bind函數將其與本機
 *地址以及一個本地端口號綁定,接收到一個客戶端時,服務器顯示該客戶端的IP地址,並將字串
 *返回給客戶端。
 */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#import <arpa/inet.h>
int main(int argc,char **argv)
{
    int ser_sockfd;
    int len;
    //int addrlen;
    socklen_t addrlen;
    char seraddr[100];
    struct sockaddr_in ser_addr;
    /*創建socket*/
    ser_sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(ser_sockfd<0)
    { 
        printf("I cannot socket success\n");
        return 1;
    }
    /*填寫sockaddr_in 結構*/
    addrlen=sizeof(struct sockaddr_in);
    bzero(&ser_addr,addrlen);
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    ser_addr.sin_port=htons(1024);
    /*綁定客戶端*/
    if(bind(ser_sockfd,(struct sockaddr *)&ser_addr,addrlen)<0)
    {
        printf("connect");
        return 1;
    }
    while(1)
    {
        bzero(seraddr,sizeof(seraddr));
        len=recvfrom(ser_sockfd,seraddr,sizeof(seraddr),0,(struct sockaddr*)&ser_addr,&addrlen);
        /*顯示client端的網絡地址*/
        printf("receive from %s\n",inet_ntoa(ser_addr.sin_addr));
        /*顯示客戶端發來的字串*/ 
        printf("recevce:%s",seraddr);
        /*將字串返回給client端*/
        sendto(ser_sockfd,seraddr,len,0,(struct sockaddr*)&ser_addr,addrlen);
    }
}

客戶端的工做流程:首先調用socket函數建立一個Socket,填寫服務器地址及端口號,從標準輸入設備中取得字符串,將字符串傳送給服務器端,並接收服務器端返回的字符串。最後關閉該socket。

/*
 *UDP/IP應用編程接口(API)
 *客戶端的工做流程:首先調用socket函數建立一個Socket,填寫服務器地址及端口號,
 *從標準輸入設備中取得字符串,將字符串傳送給服務器端,並接收服務器端返回的字
 *符串。最後關閉該socket。
 */
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <netinet/in.h>
#import <arpa/inet.h>
int GetServerAddr(char * addrname)
{
    printf("please input server addr:");
    scanf("%s",addrname);
    return 1; 
}
int main(int argc,char **argv)
{
    int cli_sockfd;
    int len;
    socklen_t addrlen;
    char seraddr[14];
    struct sockaddr_in cli_addr;
    char buffer[256];
    GetServerAddr(seraddr);
    /* 創建socket*/
    cli_sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(cli_sockfd<0)   
    {  
        printf("I cannot socket success\n"); 
        return 1; 
    }
    /* 填寫sockaddr_in*/
    addrlen=sizeof(struct sockaddr_in);
    bzero(&cli_addr,addrlen);
    cli_addr.sin_family=AF_INET;
    cli_addr.sin_addr.s_addr=inet_addr(seraddr);
    //cli_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    cli_addr.sin_port=htons(1024);
    bzero(buffer,sizeof(buffer));
    /* 從標準輸入設備取得字符串*/
    len=read(STDIN_FILENO,buffer,sizeof(buffer));
    /* 將字符串傳送給server端*/
    sendto(cli_sockfd,buffer,len,0,(struct sockaddr*)&cli_addr,addrlen);
    /* 接收server端返回的字符串*/
    len=recvfrom(cli_sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&cli_addr,&addrlen);
    //printf("receive from %s\n",inet_ntoa(cli_addr.sin_addr));
    printf("receive: %s",buffer);
    close(cli_sockfd); 
}

 

最後,整篇文章只能用一句話形容,懶婆娘的裹腳布,又長又臭,不過本文的做用是讓咱們瞭解socket的一些原理以及底層基本的結構,其實iOS的socket實現是特別簡單的,我一直都在用github的開源類庫cocoaasyncsocket,地址是https://github.com/robbiehanson/CocoaAsyncSocket,cocoaasyncsocket是支持tcp和udp的,具體操做方法就不介紹了。

相關文章
相關標籤/搜索