1.Socket簡介
Socket
百度百科:
網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱為一個socket。
Socket的英文原義是“孔”或“插座”。作為BSD UNIX的進程通信機制,取后一種意思。通常也稱作"套接字",用于描述IP地址和端口,是一個通信鏈的句柄,可以用來實現不同虛擬機或不同計算機之間的通信。在Internet上的主機一般運行了多個服務軟件,同時提供幾種服務。每種服務都打開一個Socket,并綁定到一個端口上,不同的端口對應于不同的服務。Socket正如其英文原意那樣,像一個多孔插座。一臺主機猶如布滿各種插座的房間,每個插座有一個編號,有的插座提供220伏交流電, 有的提供110伏交流電,有的則提供有線電視節目。 客戶軟件將插頭插到不同編號的插座,就可以得到不同的服務。
圖說Socket
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層, 它是一組接口。
2.TCP和UDP的區別
TCP
面向連接、傳輸可靠(保證數據正確性,保證數據順序)、用于傳輸大量數據(流模式)、速度慢,建立連接需要開銷較多(時間,系統資源)。
UDP
面向非連接、傳輸不可靠、用于傳輸少量數據(數據包模式)、速度快。
區別
關于TCP是一種流模式的協議,UDP是一種數據報模式的協議,這里要說明一下,TCP是面向連接的,也就是說,在連接持續的過程中,socket中收到的數據都是由同一臺主機發出的(劫持什么的不考慮),因此,知道保證數據是有序的到達就行了,至于每次讀取多少數據自己看著辦。
而UDP是無連接的協議,也就是說,只要知道接收端的IP和端口,且網絡是可達的,任何主機都可以向接收端發送數據。這時候,如果一次能讀取超過一個報文的數據,則會亂套。比如,主機A向發送了報文P1,主機B發送了報文P2,如果能夠讀取超過一個報文的數據,那么就會將P1和P2的數據合并在了一起,這樣的數據是沒有意義的。
3.TCP的三次握手和四次揮手
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報文,因此不能一起發送。故需要四步握手。
5.TCP的Socket具體實現
iOS提供了Socket網絡編程的接口CFSocket,不過這里使用BSD Socket。
基本TCP客戶—服務器程序設計基本框架
常用的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)
recvform()
釋放嵌套字
close(socked)
TCP下Socket具體實現
服務器的工作流程:首先調用socket函數創建一個Socket,然后調用bind函數將其與本機地址以及一個本地端口號綁定,然后調用listen在相應的socket上監聽,當accpet接收到一個連接服務請求時,將生成一個新的socket。服務器顯示該客戶機的IP地址,并通過新的socket向客戶端發送字符串" hi,I am server!"。最后關閉該socket。
服務器參考代碼:
#import <Foundation/Foundation.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int err = 0;
int fd = socket(AF_INET, SOCK_STREAM, 0);
BOOL success = (fd != -1);
// 第一次握手
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);
}
// 第二次握手
if (success) {
NSLog(@"bind(綁定) success");
err = listen(fd, 5);//開始監聽
success = (err == 0);
}
// 第三次握手
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);
}
}
}
return 0;
}
客戶端參考代碼
#import <Foundation/Foundation.h>
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
int err;
int fd = socket(AF_INET, SOCK_STREAM, 0);
BOOL success = (fd != -1);
struct sockaddr_in addr;
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);
}
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");
}
}
}
return 0;
}