<li id="fw3su"></li>
  • <li id="fw3su"></li>
  • <div id="fw3su"><tr id="fw3su"></tr></div>
    <dl id="fw3su"></dl>
  • <div id="fw3su"><tr id="fw3su"></tr></div>
  • <sup id="fw3su"></sup>
    <progress id="fw3su"></progress><div id="fw3su"><tr id="fw3su"></tr></div><input id="fw3su"><ins id="fw3su"></ins></input>

    iOS的socket開發基礎

    socket簡介

    首先讓我們通過一張圖知道socket在哪里?

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

    tcp和udp的區別在這里就必須講一下udp和tcp的區別了

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

    UDP:面向非連接、傳輸不可靠、用于傳輸少量數據(數據包模式)、速度快。

    關于TCP是一種流模式的協議,UDP是一種數據報模式的協議,這里要說明一下,TCP是面向連接的,也就是說,在連接持續的過程中,socket中收到的數據都是由同一臺主機發出的(劫持什么的不考慮),因此,知道保證數據是有序的到達就行了,至于每次讀取多少數據自己看著辦。

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

    TCP三次握手和四次揮手相對于SOCKET開發者,TCP創建過程和連接拆除過程是由TCP/IP協議棧自動創建的。因此開發者并不需要控制這個過程。但是對于理解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服務應用。

    1、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,地址是 CocoaAsyncSocket ,cocoaasyncsocket是支持tcp和udp的,具體操作方法就不介紹了。

    我來評幾句
    登錄后評論

    已發表評論數()

    相關站點

    +訂閱
    熱門文章
    11选五