TCP是可靠地,如果這個通道壞了,會有其他方法修復或重建通道,消息太多時也會有辦法去處理溢出的信息。
UDP來講,設計角度就是不可靠的,省略其中很多流程,得到較快的速度,數據的可靠性由上層協議即利用層來保證,消息是不是轉達也沒法做出保證。
TCP/IP模型里面,網絡分為5層:從上往下是利用層,傳輸層,網絡層,數據鏈路層,物理層。
*物理層:通訊的物理條件。
*數據鏈路層:隊伍里信號的第1次處理,保證這些數字信號能夠可靠的發送到目標的網絡層。
接收端保證數據的可靠性,校驗算法,對數據進行分組,將IP地址轉換成MAC地址(48位的2進制串,1臺機器的物理地址)
*網絡層:對數據從傳輸的速率優化算法和協議,主要作用是轉發和選路,路由器,IP協議在這1層。
*傳輸層:兩個最主要的協議就是TCP協議和UDP協議。
*利用層:最多見的協議是HTTP(基于SSL的協議,安全性較高),DNS(域名系統)在此層。(程序員工作的部份,socket工作在此層)
TCP是可靠的,由于TCP協議要做流量控制,保證數據傳輸的可靠性,連接收理,堵塞控制等,而這些UDP都沒有,但是優點是比較快,對需要快并且信息不重要的場合如qq比較適合。
在發出1個功能調用時,在沒有得到結果之前,該調用就不返回。依照這個定義,其實絕大多數函數都是同步調用。但是1般而言,我們在說同步、異步的時候,特指那些需要其他部件協作或需要1定時間完成的任務。最多見的例子就是SendMessage。該函數發送1個消息給某個窗口,在對方處理完消息之前,這個函數不返回。當對方處理終了以后,該函數才把消息處理函數所返回值返回給調用者。
異步的概念和同步相對。當1個異步進程調用發出后,調用者不能立刻得到結果。實際處理這個調用的部件在完成后,通過狀態、通知和回調來通知調用者。當1個客戶端通過調用 Connect函數發出1個連接要求后,調用者線程立刻可以向下運行。當連接真正建立起來以后,socket底 層會發送1個消息通知該對象。
阻塞調用是指調用結果返回之前,當前線程會被掛起。函數只有在得到結果以后才會返回。看到這里或許會把阻塞調用和同步調用同等起來,實際上它們是不同的。對同步調用來講,很多時候當前線程還是激活的,只是從邏輯上當前函數沒有返回而已。例如,我們在CSocket中調用Receive函數,如果緩沖區中沒有數據,這個函數就會1直等待,直到有數據才返回。而此時,當前線程還會繼續處理各種各樣的消息。socket接收數據的另外1個函數recv則是1個阻塞調用的例子。當socket工作在阻塞模式的時候, 如果沒有數據的情況下調用該函數,則當前線程就會被掛起,直到有數據為止。
非阻塞和阻塞的概念相對應,指在不能立刻得到結果之前,該函數不會阻塞當前線程,而會立刻返回。對象的阻塞模式和阻塞函數調用對象是不是處于阻塞模式和函數是否是阻塞調用有很強的相干性,但是其實不是1 1對應的。
阻塞就是干不完不準回來,1直處于等待中,直到事情處理完成才返回;
非阻塞就是你先干,我先看看有其他事沒有,1發現事情被卡住,馬上報告領導。
阻塞對象上可以有非阻塞的調用方式,我們可以通過1定的API去輪詢狀態,在適當的時候調用阻塞函數,就能夠避免阻塞。而對非阻塞對象,調用特殊的函數也能夠進入阻塞調用。函數select就是這樣的1個例子。
Socket是進程通訊的1種方式,即調用這個網絡庫的1些API函數。Socket可以理解為客戶端或服務器真個1個特殊的對象,這個對象有兩個關鍵的方法,1個是getInputStream方法,另外一個是getOutputStream方法。getInputStream方法可以得到1個輸入流,客戶真個Socket對象上的getInputStream方法得到的輸入流其實就是從服務器端發回的數據流。GetOutputStream方法得到1個輸出流,客戶端Socket對象上的getOutputStream方法返回的輸出流就是將要發送到服務器真個數據流客戶基于服務器之間使用的大部份通訊組件都是基于socket接口來實現的。
1、 首先調用Socket類的構造函數,以服務器的指定的IP地址或指定的主機名和指定的端口號為參數,創建1個Socket流,在創建Socket流的進程中包括了向服務器要求建立通訊連接的進程實現。
2、 建立了客戶端通訊Socket后。就能夠使用Socket的方法getInputStream()和getOutputStream()來創建輸入/輸出流。這樣,使用Socket類后,網絡輸入輸出也轉化為使用流對象的進程。
3、 使用輸入輸出流對象的相應方法讀寫字節流數據,由于留連接著通訊所用的Socket,Socket又是和服務器端建立連接的1個端點,因此數據將通過連接從服務器得到或發向服務器。這時候我們就能夠對字節流數據按客戶端和服務器之間的協議進行處理,完成雙方的通訊任務。
4、 待通訊任務終了后,我們用流對象的close()方法來關閉用于網絡通訊的輸入輸出流,在用Socket對象的close()方法來關閉Socket。
服務器必須首先啟動,直到它履行完accept()調用,進入等待狀態后,方能接收客戶要求。假設客戶在此前啟動,則connect()將返回出錯代碼,連接不成功。
###UDP
無連接服務器也必須先啟動,否則客戶要求傳不到服務進程。無連接客戶不調用connect()。因此在數據發送之前,客戶與服務器之間還沒有建立完全相干,但各自通過socket()和bind()建立了半相干。發送數據時,發送方除指定本地套接字號外,還需指定接收方套接字號,從而在數據收發進程中動態地建立了全相干。
先說明頭文件# include < WinSock2.h >(windows下)
基本學習的函數以下:socket、bind、listen、connect、accept、send、recv、recvfrom、close、shutdown
int socket(int domain,int type,int protocol)
非負為成功,⑴為失敗。
1.指定通訊產生的區域 (Windows下僅支持AF_INET)
2.套接口類型,主要SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;
3.protocol1般取為0。成功時,返回1個小的非負整數值,與文件描寫符類似.
eg:
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
perror(“opening stream socket”);
exit(1);
}
Int bind( IN SOCKET s, IN const struct sockaddr FAR * name, IN int namelen);
// IN windows的宏,表示輸入
當socket函數返回1個描寫符時,只是存在于其協議族的空間中,并沒有分配1個具體的協議地址(這里指IPv4/IPv6和端口號的組合),bind函數可以將1組固定的地址綁定到sockfd上。
1.是socket函數返回的描寫符;
2.指定了想要綁定的IP和端口號,均要使用網絡字節序-即大端模式;
3.是前面struct sockaddr(與sockaddr_in等價)的長度。
為了統1地址結構的表示方法,統1接口函數,使得不同的地址結構可以被bind()、connect()、recvfrom()、sendto()等函數調用。但1般的編程中其實不直接對此數據結構進行操作,而使用另外一個與之等價的數據結構sockaddr_in。
if (bind(sock, (struct sockaddr *)&server, sizeof(server)) < 0)
{
perror(“binding stream socket”);
exit(1);
}
int listen(int sockfd,int backlog)
此調用用于面向連接服務器,表明它愿意接收連接。listen()需在accept()之前調用
0成功,⑴失敗
1、1個本地已建立、還沒有連接的套接字號,服務器愿意從它上面接收要求
2、表示要求連接隊列的最大長度,用于限制排隊要求的個數,目前允許的最大值為5
if (listen(initsockid , 5) < 0)
error(“listen error”);
這兩個系統調用用于完成1個完全相干的建立,其中connect()用于建立連接。無連接的套接字進程也能夠調用connect(),但這時候在進程之間沒有實際的報文交換,調用將從本地操作系統直接返回。這樣做的優點是程序員沒必要為每數據指定目的地址,而且如果收到的1個數據報,其目的端口未與任何套接字建立“連接”,便能判斷該端便能判斷該端口不可操作。
int connect(int sockfd,conststruct sockaddr *addr, socklen_t addrlen)
0成功,⑴失敗
1.是欲建立連接的本地套接字描寫符。
2.指出說明對方套接字地址結構的指針
3.對方套接字地址長度由namelen說明
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
非負數為成功,⑴失敗
1.為本地套接字描寫符,在用做accept()調用的參數前應當先調用過listen()
2.指向客戶方套接字地址結構的指針,用來接收連接實體的地址。addr的確切格式由套接字創建時建立的地址族決定。
3.為客戶方套接字地址的長度(字節數)。
int send(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags)
成功拷貝至發送緩沖區的字節數(可能小于len),⑴出錯,并置毛病號errno.
1、為已連接的本地套接字描寫符
2、指向存有發送數據的緩沖區的指針
3、buf長度由len 指定
4、指定傳輸控制方式,如是不是發送帶外數據等
int recv(int sockfd,void *buf, size_t len,int flags)
成功時,返回拷貝的字節數,失敗返回⑴
1、為已連接的套接字描寫符
2、指向接收輸入數據緩沖區的指針
3、buf長度由len 指定
4、指定傳輸控制方式,如是不是接收帶外數據等
迷之類似,只有進程的發出者相對不同
// server.cpp
#include <iostream>
#include <cstdio>
#include <Winsock2.h>
using namespace std;
int main()
{
// 加載socket動態鏈接庫(dll)
WORD wVersionRequested;
WSADATA wsaData; // 這結構是用于接收Wjndows Socket的結構信息的
int err;
wVersionRequested = MAKEWORD( 1, 1 ); // 要求1.1版本的WinSock庫
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1; // 返回值為零的時候是表示成功申請WSAStartup
}
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) {
// 檢查這個低字節是否是1,高字節是否是1以肯定是不是我們所要求的1.1版本
// 否則的話,調用WSACleanup()清除信息,結束函數
WSACleanup( );
return -1;
}
// 創建socket操作,建立流式套接字,返回套接字號sockSrv
// SOCKET socket(int af, int type, int protocol);
// 第1個參數,指定地址簇(TCP/IP只能是AF_INET,也可寫成PF_INET)
// 第2個,選擇套接字的類型(流式套接字),第3個,特定地址家族相干協議(0為自動)
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
// 套接字sockSrv與本地地址相連
// int bind(SOCKET s, const struct sockaddr* name, int namelen);
// 第1個參數,指定需要綁定的套接字;
// 第2個參數,指定該套接字的本地地址信息,該地址結構會隨所用的網絡協議的不同而不同
// 第3個參數,指定該網絡協議地址的長度
// PS: struct sockaddr{ u_short sa_family; char sa_data[14];};
// sa_family指定該地址家族, sa_data起到占位占用1塊內存分配區的作用
// 在TCP/IP中,可以使用sockaddr_in結構替換sockaddr,以方便填寫地址信息
//
// struct sockaddr_in{ short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8];};
// sin_family表示地址族,對IP地址,sin_family成員將1直是AF_INET。
// sin_port指定將要分配給套接字的端口。
// sin_addr給出套接字的主機IP地址。
// sin_zero[8]給出填充數,讓sockaddr_in與sockaddr結構的長度1樣。
// 將IP地址指定為INADDR_ANY,允許套接字向任何分配給本地機器的IP地址發送或接收數據。
// 如果想只讓套接字使用多個IP中的1個地址,可指定實際地址,用inet_addr()函數。
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 將INADDR_ANY轉換為網絡字節序,調用 htonl(long型)或htons(整型)
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)); // 第2參數要強迫類型轉換
// 將套接字設置為監聽模式(連接要求), listen()通知TCP服務器準備好接收連接
// int listen(SOCKET s, int backlog);
// 第1個參數指定需要設置的套接字,第2個參數為(等待連接隊列的最大長度)
listen(sockSrv, 10);
// accept(),接收連接,等待客戶端連接
// SOCKET accept( SOCKET s, struct sockaddr* addr, int* addrlen);
// 第1個參數,接收1個處于監聽狀態下的套接字
// 第2個參數,sockaddr用于保存客戶端地址的信息
// 第3個參數,用于指定這個地址的長度
// 返回的是向與這個監聽狀態下的套接字通訊的套接字
// 客戶端與用戶端進行通訊
// send(), 在套接字上發送數據
// int send( SOCKET s, const char* buf, int len, int flags);
// 第1個參數,需要發送信息的套接字,
// 第2個參數,包括了需要被傳送的數據,
// 第3個參數是buffer的數據長度,
// 第4個參數,1些傳送參數的設置
// recv(), 在套接字上接收數據
// int recv( SOCKET s, char* buf, int len, int flags);
// 第1個參數,建立連接后的套接字,
// 第2個參數,接收數據
// 第3個參數,接收數據的長度,
// 第4個參數,1些傳送參數的設置
SOCKADDR_IN addrClient;
int len = sizeof(SOCKADDR);
while(true){ // 不斷等待客戶端要求的到來
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
char sendBuf[100];
sprintf(sendBuf, "Welcome %s to the server program~ \nNow, let's start talking...\n", inet_ntoa(addrClient.sin_addr));
send(sockConn, sendBuf, strlen(sendBuf)+1, 0); // 發送顯示歡迎信息
char recvBuf[100];
recv(sockConn, recvBuf, 100, 0);
printf("%s\n", recvBuf); // 接收第1次信息
char * sockConnName = "Client";
printf("我們可以聊5句話");
int n = 5;
while(n--){
printf("還剩%d次:\n", n+1);
char recvBuf[100];
recv(sockConn, recvBuf, 100, 0);
printf("%s Says: %s\n", sockConnName, recvBuf); // 接收信息
char talk[100];
printf("Please enter what you want to say next(\"quit\"to exit):");
gets(talk);
send(sockConn, talk, strlen(talk)+1, 0); // 發送信息
printf("\n");
}
printf("\nEnd talking... \n");
closesocket(sockConn);
}
printf("\n");
system("pause");
return 0;
}
#include <iostream>
#include <cstdio>
#include <Winsock2.h>
using namespace std;
int main()
{
// 加載socket動態鏈接庫(dll)
WORD wVersionRequested;
WSADATA wsaData; // 這結構是用于接收Wjndows Socket的結構信息的
int err;
wVersionRequested = MAKEWORD( 1, 1 ); // 要求1.1版本的WinSock庫
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return -1; // 返回值為零的時候是表示成功申請WSAStartup
}
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) {
// 檢查這個低字節是否是1,高字節是否是1以肯定是不是我們所要求的1.1版本
// 否則的話,調用WSACleanup()清除信息,結束函數
WSACleanup( );
return -1;
}
// 創建socket操作,建立流式套接字,返回套接字號sockClient
// SOCKET socket(int af, int type, int protocol);
// 第1個參數,指定地址簇(TCP/IP只能是AF_INET,也可寫成PF_INET)
// 第2個,選擇套接字的類型(流式套接字),第3個,特定地址家族相干協議(0為自動)
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
// 將套接字sockClient與遠程主機相連
// int connect( SOCKET s, const struct sockaddr* name, int namelen);
// 第1個參數:需要進行連接操作的套接字
// 第2個參數:設定所需要連接的地址信息
// 第3個參數:地址的長度
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); // 本地回路地址是127.0.0.1;
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(6000);
connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
char recvBuf[100];
recv(sockClient, recvBuf, 100, 0);
printf("%s\n", recvBuf);
send(sockClient, "Attention: A Client has enter...\n", strlen("Attention: A Client has enter...\n")+1, 0);
printf("我們可以聊5句話");
int n = 5;
do{
printf("\n還剩%d次:", n);
char talk[100];
printf("\nPlease enter what you want to say next(\"quit\"to exit):");
gets(talk);
send(sockClient, talk, strlen(talk)+1, 0); // 發送信息
char recvBuf[100];
recv(sockClient, recvBuf, 100, 0);
printf("%s Says: %s\n", "Server", recvBuf); // 接收信息
}while(--n);
printf("End linking...\n");
closesocket(sockClient);
WSACleanup(); // 終止對套接字庫的使用
printf("\n");
system("pause");
return 0;
}
下一篇 你會寫“atoi”嗎???