OSI(Open System Interconnection)參考模型將網絡的不同功能劃分爲7層。
應用層 ---》處理網絡應用---》遠程登錄協議Telnet、文件傳輸協議FTP、 超文本傳輸協議HTTP、域名服務DNS、簡單郵件傳輸協議SMTP、郵局協議POP3等。
表示層 ---》數據表示
會話層 ---》主機間通信
傳輸層 ---》端到端的連接---》傳輸控制協議TCP、用戶數據報協議UDP。TCP:面向連接的可靠的傳輸協議。 UDP:是無連接的,不可靠的傳輸協議。
網絡層 ---》尋址和最短路徑---》網際協議IP、Internet互聯網控制報文協議ICMP、Internet組管理協議IGMP。
數據鏈路層 ---》介質訪問(接入)
物理層 ---》二進制傳輸
OSI參考模型中,對等層協議之間交換的信息單元統稱爲協議數據單元(PDU,Protocol Data Unit)。
TCP/IP模型包括4個層次:
應用層----類似於會話層 、表示層 、應用層
傳輸層
網絡層
網絡接口----類似於數據鏈路層、物理層
端口
按照OSI七層模型的描述,傳輸層提供進程(應用程序)通信的能力。爲 了標識通信實體中進行通信的進程(應用程序),TCP/IP協議提出了協議端口(protocol port,簡稱端口)的概念
。
端口是一種抽象的軟件結構(包括一些數據結構和I/O緩衝區)。應用程序通過系統調用與某端口建立連接(binding)後,傳輸層傳給該端口的數據都被相應的進程所接收,相應進程發給傳
輸層的數據都通過該端口輸出。
端口用一個整數型標識符來表示,即端口號。端口號跟協議相關,TCP/IP傳輸層的兩個協議TCP和UDP是完全獨立的兩個軟件模塊,因此各自的端口號也相互獨立。
端口使用一個16位的數字來表示,它的範圍是0~65535,1024以下的端口號保留給預定義的服務。例如:http使用80端口。
SOCKET的引入
爲了能夠方便的開發網絡應用軟件,由美國伯克利大學在Unix上推出了一種應用程序訪問通信協議的操作系統調用socket(套接字)。socket的出現,使程序員可以很方便地訪問TCP/IP,從而
開發各種網絡應用的程序。
隨着Unix的應用推廣,套接字在編寫網絡軟件中得到了極大的普及。後來,套接字又被引進了Windows等操作系統,成爲開發網絡應用程序的非常有效快捷的工具。
套接字存在於通信區域中。通信區域也叫地址族,它是一個抽象的概念,主要用於將通過套接字通信的進程的共有特性綜合在一起。套接字通常只與同一區域的套接字交換數據(也有可能跨
區域通信,但這隻在執行了某種轉換進程後才能實現)。Windows Sockets只支持一個通信區域:網際域( AF_INET),這個域被使用網際協議簇通信的進程使用。
網絡字節的順序
不同的計算機存放多字節值的順序不同,有的機器在起始地址存放低位字節(低位先存),有的機器在起始地址存放高位字節(高位先存)。基於Intel的CPU,即我們常用的PC機採用的是低位先
存。爲保證數據的正確性,在網絡協議中需要指定網絡字節順序。TCP/IP協議使用16位整數和32位整數的高位先存格式。
套接字的類型
流式套接字(SOCK_STREAM)
提供面向連接、可靠的數據傳輸服務,數據無差錯、無重複的發送,且按發送順序接收。
數據報式套接字(SOCK_DGRAM)
提供無連接服務。數據包以獨立包形式發送,不提供無錯保證,數據可能丟失或重複,並且接收順序混亂。
原始套接字(SOCK_RAW)。
基於TCP的SOCKET編程
服務器端程序:
1、創建套接字(socket)。
2、將套接字綁定到一個本地地址和端口上(bind)。
3、將套接字設爲監聽模式,準備接收客戶請求(listen)。
4、等待客戶請求到來;當請求到來後,接受連接請求,返回一個新的對應於此次連接的套接字(accept)。
5、用返回的套接字和客戶端進行通信(send/recv)。
6、返回,等待另一客戶請求。
7、關閉套接字。
客戶端程序:
1、創建套接字(socket)。
2、向服務器發出連接請求(connect)。
3、和服務器端進行通信(send/recv)。
4、關閉套接字。
基於UDP的SOCKET編程
服務器端(接收端)程序:
1、創建套接字(socket)。
2、將套接字綁定到一個本地地址和端口上(bind)。
3、等待接收數據(recvfrom)。
4、關閉套接字。
客戶端(發送端)程序:
1、創建套接字(socket)。
2、向服務器發送數據(sendto)。
3、關閉套接字。
int WSAStartup(
WORD wVersionRequested,----》參數用於指定準備加載的Winsock庫的版本。高位字節指定所需要的Winsock庫的副版本,而低位字節則是主版本。可用MAKEWORD(x,y)(其中,x是高位字節
,y是低位字節)方便地獲得wVersionRequested的正確值。
LPWSADATA lpWSAData----》參數是指向WSADATA結構的指針,WSAStartup用其加載的庫版本有關的信息填在這個結構中。
); ----》1.加載套接字庫2.進行套接字庫的版本協商即確定使用哪一個版本的套接字庫
WSADATA結構定義如下:
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
} WSADATA, *LPWSADATA;
WSAStartup把第一個字段wVersion設成打算使用的Winsock版本。wHighVersion 參數容納的是現有的Winsock庫的最高版本。記住,這兩個字段中,高位字節代表的是Winsock
副版本,而低位字節代表的則是Winsock主版本。szDescription和szSystemStatus這兩個字段由特定的Winsock實施方案設定,事實上沒有用。不要使用下面這兩個字段:iMaxSockets和
iMaxUdpDg,它們是假定同時最多可打開多少套接字和數據報的最大長度。然而,要知道數據報的最大長度應該通過WSAEnumProtocols來查詢協議信息。同時最多可打開套接字的數目不是固
定的,很大程度上和可用物理內存的多少有關。最後,lpVendorInfo字段是爲Winsock實施方案有關的指定廠商信息預留的。任何一個Win32平臺上都沒有使用這個字段。
如果WinSock.dll或底層網絡子系統沒有被正確初始化或沒有被找到,WSAStartup將返回WSASYSNOTREADY。此外這個函數允許你的應用程序協商使用某種版本的WinSock規範
,如果請求的版本等於或高於DLL所支持的最低版本,WSAData的wVersion成員中將包含你的應用程序應該使用的版本,它是DLL所支持的最高版本與請求版本中較小的那個。反之,如果請求
的版本低於DLL所支持的最低版本,WSAStartup將返回WSAVERNOTSUPPORTED。關於WSAStartup更詳細的信息,請查閱MSDN中的相關部分。
對於每一個WSAStartup的成功調用(成功加載WinSock DLL後),在最後都對應一個WSACleanUp調用,以便釋放爲該應用程序分配的資源。
SOCKET socket( int af, int type, int protocol );
該函數接收三個參數。
第一個參數af指定地址族,對於TCP/IP協議的套接字,它只能是AF_INET(也可寫成PF_INET)。
第二個參數指定Socket類型,對於1.1版本的Socket,它只支持兩種類型的套接字,SOCK_STREAM指定產生流式套接字,SOCK_DGRAM產生數據報套接字。
第三個參數是與特定的地址家族相關的協議,如果指定爲0,那麼它就會根據地址格式和套接字類別,自動爲你選擇一個合適的協議。這是推薦使用的一種選擇協議的方法。
如果這個函數調用成功,它將返回一個新的SOCKET數據類型的套接字描述符。如果調用失敗,這個函數就會返回一個INVALID_SOCKET,錯誤信息可以通過WSAGetLastError函數返回。
int bind( SOCKET s, const struct sockaddr FAR *name, int namelen );
這個函數接收三個參數。
第一個參數s指定要綁定的套接字,
第二個參數指定了該套接字的本地地址信息,是指向sockaddr結構的指針變量,由於該地址結構是爲所有的地址家族準備的,這個結構可能(通常會)隨所使用的網絡協議不同而不同,所以
,要用第三個參數指定該地址結構的長度。 sockaddr結構定義如下:
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
sockaddr的第一個字段sa_family指定該地址家族,在這裏必須設爲AF_INET。
sa_data僅僅是表示要求一塊內存分配區,起到佔位的作用,該區域中指定與協議相關的具體地址信息。由於實際要求的只是內存區,所以對於不同的協議家族,用不同的結構來替換
sockaddr。除了sa_family外,sockaddr是按網絡字節順序表示的。在TCP/IP中,我們可以用sockaddr_in結構替換sockaddr,以方便我們填寫地址信息。
sockaddr_in的定義如下:
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結構的長度一樣。如果這個函數調用成功,它將返回0。如果調用失敗,這個函數就會返回一個SOCKET_ERROR,錯誤信息可以
通過WSAGetLastError函數返回。
將IP地址指定爲INADDR_ANY,允許套接字向任何分配給本地機器的IP地址發送或接收數據。多數情況下,每個機器只有一個IP地址,但有的機器可能會有多個網卡,每個網卡
都可以有自己的IP地址,用INADDR_ANY可以簡化應用程序的編寫。將地址指定爲INADDR_ANY,允許一個獨立應用接受發自多個接口的迴應。如果我們只想讓套接字使用多個IP中的一個地址,
就必須指定實際地址,要做到這一點,可以用inet_addr()函數,這個函數需要一個字符串作爲其參數,該字符串指定了以點分十進制格式表示的IP地址(如192.168.0.16)。而且inet_addr()
函數會返回一個適合分配給S_addr的u_long類型的數值。inet_ntoa()函數會完成相反的轉換,它接受一個in_addr結構體類型的參數並返回一個以點分十進制格式表示的IP地址字符串。
u_long htonl(
u_long hostlong
);----》將一個主機字節序轉換爲一個TCP/IP字節序
u_short htons(
u_short hostshort
);---》和上述類似
int listen(
SOCKET s,
int backlog ---》可連接隊列的最大請求數目
);
SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR *addrlen
);
int send(
SOCKET s,
const char FAR *buf,
int len,
int flags
);
int recv(
SOCKET s,
char FAR *buf,
int len,
int flags
);
在Project Settings -》Link-》Object/library modules 添加 ws2_32.lib
TCP服務端:
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
listen(sockSrv,5);
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
while(1)
{
SOCKET sockConn=accept(sockSrv,(SOCKADDR*)&addrClient,&len);
char sendBuf[100];
sprintf(sendBuf,"Welcome %s to http://www.sunxin.org",
inet_ntoa(addrClient.sin_addr));
send(sockConn,sendBuf,strlen(sendBuf)+1,0);
char recvBuf[100];
recv(sockConn,recvBuf,100,0);
printf("%s/n",recvBuf);
closesocket(sockConn);
}
}
TCP客戶端:
int connect(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("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,"This is lisi",strlen("This is lisi")+1,0);
closesocket(sockClient);
WSACleanup();
}
基於UDP的SOCKET
服務端
int recvfrom(
SOCKET s,
char FAR* buf,
int len,
int flags,
struct sockaddr FAR *from,
int FAR *fromlen
);
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
bind(sockSrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
char recvBuf[100];
recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
printf("%s/n",recvBuf);
closesocket(sockSrv);
WSACleanup();
}
客戶端
int sendto(
SOCKET s,
const char FAR *buf,
int len,
int flags,
const struct sockaddr FAR *to,
int tolen
);
#include <Winsock2.h>
#include <stdio.h>
void main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
}
SOCKET sockClient=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
sendto(sockClient,"Hello",strlen("Hello")+1,0,
(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
closesocket(sockClient);
WSACleanup();
}