在OSI的各層所使用的協議:
1.應用層:telnet,FTP,HTTP,DNS,SMTP,POP3
2.傳輸層:TCP,UDP
TCP:面向連接的可靠的傳輸協議,通信前建立三次握手,握手成功后才能通信,對數據準確性要求較高的場合使用,如從網上載的安裝文件,不能缺少任何信息 UDP:是無連接的,不可靠的傳輸協議,不需要建立連接,也沒有重傳和確認的機制,在實時性要求較高,但對數據準確度要求不是很高的場合使用,如視頻會議,在線觀看電影,當中丟失個別數據包并不影響整體的效果。 3.網絡層:IP
因為OSI七層結構較為復雜,所以使用較多的是TCP/IP模型,現在TCP/IP已經成為Internet上通用的工業標準 TCP/IP模型包括4個層次:應用層,傳輸層,網絡層,網絡接口
端口: 1. 為了標識通信實體中進行通信的進程(應用程序),TCP/IP協議提出了協議端口的概念 2. 端口是一種抽象的軟件結構(包括一些數據結構和I/O緩沖區)。應用程序通過系統調用和某端口建立連接(binding)后,傳輸層傳給該端口的數據都被相應的進程所接收,相應進程發給傳輸層的數據都通過該端口輸出 3. 端口用一個整數型標識符來表示,即端口號。端口號跟協議相關,TCP/IP傳輸層的兩個協議TCP和UDP是完全獨立的的兩個軟件模塊,因此各自的端口號也相互獨立 4. 端口使用一個16位的數字來表示,它的范圍是0~65535,1024以下的端口號保留給預定義的服務,例如,http使用80端口
套接字(Socket) 1. Socket的出現,使得程序員可以很方便的訪問TCP/IP,從而開發各種網絡應用的程序 2. 套接字存在于通信區域中,通信區域也叫地址族,他是一個抽象的概念,主要用于通過套接字通信的進程的共有特性綜合在一起。套接字通常只與同一個區域的套接字交換數據。
套接字的類型 1. 流式套接字(SOCK_STREAM) 提供面向連接的,可靠的數據傳輸服務,數據無差錯,無重復的發送,且按發送的順序接收,基于TCP協議 2. 數據保式套接字(SOCK_DGRAM) 提供無連接的服務,數據包以獨立包形式發送,不提供無錯誤的保證,數據可能丟失或重復,且接收順序混亂,基于UDP協議
基于TCP(面向連接)的Socket編程 服務器端順序: 1. 加載套接字庫 2. 創建套接字(socket) 3. 將套接字綁定到一個本地地址和端口上(bind) 4. 將套接字設為監聽模式,準備接收客戶請求(listen) 5. 等待客戶請求的到來;當請求帶來后,接受連接請求,返回一個新的對應于此次連接的套接字(accept) 6. 用返回的套接字和客戶端進行通信(send/recv) 7. 返回,等待另一個客戶請求 8. 關閉套接字(closesocket)
客戶端程序: 1. 加載套接字庫 2. 創建套接字(socket) 3. 向服務器發送連接請求(connect) 4. 和服務器端進行通信(send/receive) 5. 關閉套接字(closesocket)
基于UDP(面向無連接)的socket編程 服務器端(接收端)程序: 1. 加載套接字庫 2. 創建套接字(socket) 3. 將套接字綁定到一個本地地址和端口上(bind) 4. 等待接收數據(recvfrom) 5. 關閉套接字(closesocket)
客戶端(發送端)程序 1. 加載套接字庫 2. 創建套接字(socket) 3. 向服務器發送數據(sendto) 4. 關閉套接字(closesocket)
創建基于TCP協議的CS程序的Server端所涉及的相關函數說明(按使用的先后順序排列): 1. int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData):作用是加載套接字庫和進行套接字庫的版本協商
a. 參數wVersionRequested:用于指定準備加載的Winsock庫的版本,高位字節指定所需要的Winsock庫的副版本,低位字節則是主版本,可用MAKEWORD(X,Y)(其中,x為高位字節,y為低位字節)方便獲得wVersionRequested的正確值。
b. 參數lpWSAData:指向WSADATA結構的指針,WSAStartup用其加載的庫版本有關的信息填在這個結構中 2. SOCKET socket(int af,int type,int protocol): a. 參數af指定地址族,對于TCP/IP協議的套接字,它只能是AF_INET(也可寫成PF_INET)。 b. 參數type指定Socket類型,對于1.1版本的Socket,它只支持兩種類型的套接字,SOCK_STREAM指定產生流式套接字,SOCK_DGRAM產生數據報套接字。 c. 參數protocol與特定的地址家族相關的協議,如果指定為0,那么他就會根據地址格式和套接字類別,自動為你選擇一個合適的協議。這是推薦使用的一種選擇協議的方式。 3. int bind(SOCKET s,const struct sockaddr FAR *name,int namelen) : a. 第一個參數指定要綁定的套接字,第二個參數指定該套接字的本地地址信息,是指向sockaddr結構的指針變量,由于該地址結構是為了所有的地址家族準備使用的,這個結構可能(通常會)隨使用的網絡協議不同而不同,所以,要用第三個參數指定該地址結構的長度。sockaddr機構定義如下:
struct sockaddr { u_short sa_family; char sa_data[14]; } b. 上述結構第一個字段指定該地址家族,在這里必須設為AF_INET。sa_data僅僅是表示要求一塊內存分配區,起到占位的作用,該區域中指定與協議相關的具體地址信息。由于實際要求的只是內存區,所以對于不同的協議家族,用不同的結構來替換sockaddr。在TCP/IP中,我們可以用SOCKADDR_IN結構來代替sockaddr,以方便我們填寫地址信息。 c. struct SOCKADDR_IN{ short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; sin_family表示地址族,對于IP地址,sin_family成員將一直是AF_INET;成員sin_port指定的將要分配給套接字的端口;成員sin_addr給出的是套接字的主機IP地址;sin_zero只是一個填充數,以使sockaddr_in結構和sockaddr結構的長度一樣,一般不用設置。 除了sin_family外,SOCKADDR_IN其他成員是按網絡字節順序表示的。所以需要進行轉換:htonl(INADDR_ANY),htons(6000),其中6000是端口號。 另外結構體的名稱大寫和小寫指的是同一個。 將IP地址指定為INADDR_ANY,允許套接字向任何分配給本機器的IP地址發送或接收數據。一般一臺機器一個網卡,但對于多網卡的機器,INADDR_ANY將簡化應用程序的編寫。將地址指定為INADDR_ANY,允許一個獨立的應用接受發自多個接口的回應。如果我們只想讓套接字使用多個IP中的一個地址,必須指定實際地址,要做到這一點,可以用inet_addr()函數,這個函數需要一個IP地址(如192.168.80.88),返回一個適合分配給S_addr的u_long類型的數值。Inet_ntoa()函數完成相反的轉換,它接受一個in_addr結構體類型的參數并返回一個以點分十進制的IP地址字符串。 htonl把一個u_long類型從主機字節序轉換為網絡字節序。 htons把一個u_short類型從主機字節序轉換為網絡字節序。 4. int listen(SOCKET s, int backlog):將套接字設置為監聽模式,其中第二個參數設置等待請求連接的最大的值,即如果設置為n,則前n個請求會放置在系統的請求連接隊列中,應用程序會依次對這些請求進行服務,但第n+1個連接請求則會被拒絕。 5. SOCKET accept(SOCKET s, const struct sockaddr FAR * addr, int FAR* addrlen):
從客戶端接收請求,并創建連接,如果連接成功,則會返回一個當前成功建立連接的套接字,該套接字不是上面創建的監聽套接字,而是僅僅適用于當前的一個請求連接,如果建立連接失敗,則返回值是INVALID_SOCKET,并且可以適用WSAGetLastError()函數得到相關的失敗信息,具體的error code具體意義見MSDN中accept函數的最后部分的介紹 6. send 函數:向客戶端發送指定信息 7. recv函數:得到從客戶端傳遞過來的信息 8. closesocket(SOCKET s):將指定的套接字關閉,從而釋放資源 9. WSACleanup():終止對winsocket庫的使用 10. hello
服務器端的實現過程(Win32控制臺程序): 說明: 1. 對于winsock庫的類的使用,必須包含winsock2.h頭文件 2. 在setting中的link下的object/library modules中添加“ws2_32.lib”,注意和前面的字段之間用空格分隔 3. 本服務器程序先于服務器端啟動 #include "winsock2.h" #include "stdio.h" void main() { //加載套接字(winsock)庫,加載這段代碼拷貝于MSDN中WSAStartup的介紹 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 1, 1 ); //版本號為1.1 err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return; }
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return; }
//創建套接字 SOCKET sockServer=socket(AF_INET,SOCK_STREAM,0); //SOCK_STREAM參數設置為TCP連接 SOCKADDR_IN addrServer; //設置服務器端套接字的相關屬性 addrServer.sin_addr.S_un.S_addr=htonl(INADDR_ANY); //設置IP addrServer.sin_family=AF_INET; addrServer.sin_port=htons(1234); //設置端口號
//將套接字綁定到本地地址和指定端口上 bind(sockServer,(SOCKADDR*)&addrServer,sizeof(SOCKADDR));
//將套接字設置為監聽模式,并將最大請求連接數設置成5,超過此數的請求全部作廢 listen(sockServer,5);
SOCKADDR_IN addrClient; //用來接收客戶端的設置,包括IP和端口 int len=sizeof(SOCKADDR); while(1) //不斷監聽 { //得到創建連接后的一個新的套接字,用來和客戶端進行溝通,原套接字繼續監聽客戶的連接請求 SOCKET sockConn=accept(sockServer,(SOCKADDR*)&addrClient,&len); if(sockConn!=INVALID_SOCKET) //創建成功 { char sendInfo[100]; //inet_ntoa將結構轉換為十進制的IP地址字符串 sprintf(sendInfo,"welcome %s to this test!",inet_ntoa(addClient.sin_addr)); //成功建立連接后向客戶端發送數據,結果將顯示在客戶端上 send(sockConn,sendInfo,strlen(sendInfo)+1,0);
//從客戶端接收數據,結果顯示在服務器上 char recvInfo[100]; recv(sockConn,recvInfo,100,0); printf("%s\n",recvInfo);
//將本次建立連接中得到套接字關閉 closesocket(sockConn); } else { int errCode=WSAGetLastError(); printf("the errcode is:%d\n",errCode); } } //如果本程序不是死循環,那么在此處還應添加以下代碼: closesocket(sockServer); //對一直處于監聽狀態的套接字進行關閉 WSACleanup(); //終止對winsocket庫的使用 }
|