注:本系列文章適合初學(xué)網(wǎng)絡(luò)編程的讀者
網(wǎng)絡(luò)程序的實(shí)現(xiàn)可以有很多方式,Windows Socket就是其中一種比較簡(jiǎn)單的方法。socket是連接應(yīng)用程序與網(wǎng)絡(luò)驅(qū)動(dòng)程序的橋梁,socket在應(yīng)用程序中創(chuàng)建,通過(guò)綁定操作與驅(qū)動(dòng)程序建立關(guān)系。此后,應(yīng)用程序送給socket的數(shù)據(jù),由socket交給驅(qū)動(dòng)程序向網(wǎng)絡(luò)上發(fā)送出去。計(jì)算機(jī)從網(wǎng)絡(luò)上收到與該socket綁定的IP地址和端口號(hào)相關(guān)的數(shù)據(jù)后,由驅(qū)動(dòng)程序交給socket,應(yīng)用程序便可從該socket中提取接收到的數(shù)據(jù)。
在TCP/IP網(wǎng)絡(luò)應(yīng)用中,通信的兩個(gè)進(jìn)程間相互作用的主要是(client/server)模式,即客戶(hù)向服務(wù)器提出請(qǐng)求,服務(wù)器接收到請(qǐng)求后,提供相應(yīng)的服務(wù)。
下面通過(guò)一個(gè)簡(jiǎn)單的實(shí)例來(lái)講述基于TCP的socket編程的通信流程。其中服務(wù)器端程序?qū)崿F(xiàn)代碼TCPSrv.cpp如下:

Server
1
//#include <windows.h>
2
#include <Winsock2.h>
3
#include <stdio.h>
4
5
void main()
6

{
7
//加載套接字庫(kù)
8
WORD wVersionRequested;
9
WSADATA wsaData;
10
int err;
11
12
wVersionRequested = MAKEWORD(1, 1);
13
err = WSAStartup(wVersionRequested, &wsaData);
14
if( err != 0 )
15
{
16
return;
17
}
18
if( LOBYTE(wsaData.wVersion) != 1 ||
19
HIBYTE(wsaData.wVersion) != 1)
20
{
21
WSACleanup();
22
return;
23
}
24
25
//創(chuàng)建用于監(jiān)聽(tīng)的套接字
26
SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
27
SOCKADDR_IN addSrv;
28
addSrv.sin_addr.S_un.S_addr = htonl( INADDR_ANY );
29
addSrv.sin_family = AF_INET;
30
addSrv.sin_port = htons(6000);
31
32
//綁定套接字
33
bind(sockSrv, (SOCKADDR*)&addSrv, sizeof(SOCKADDR));
34
//將套接字設(shè)為監(jiān)聽(tīng)模式,準(zhǔn)備接收客戶(hù)請(qǐng)求
35
listen(sockSrv, 5);
36
37
SOCKADDR_IN addClient;
38
int len = sizeof(SOCKADDR);
39
40
while(1)
41
{
42
//等待客戶(hù)請(qǐng)求
43
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addClient, &len);
44
char sendBuf[1000];
45
sprintf(sendBuf, "Welcome %s to http://www.cnblogs.com/lantionzy", inet_ntoa(addClient.sin_addr));
46
//發(fā)送數(shù)據(jù)
47
send(sockConn, sendBuf, strlen(sendBuf)+1, 0);
48
49
char receiveBuf[1000];
50
//接收數(shù)據(jù)
51
recv(sockConn, receiveBuf, 1000, 0);
52
//打印接收的數(shù)據(jù)
53
printf("%s\n", receiveBuf);
54
//關(guān)閉套接字
55
closesocket(sockConn);
56
}
57
}
在這段代碼中,首先定義了一個(gè)WORD類(lèi)型的變量:wVersionRequested,用來(lái)保存WinSocl庫(kù)的版本號(hào),接著調(diào)用MAKEWORD宏創(chuàng)建一個(gè)包含了請(qǐng)求版本號(hào)的WORD值,之后調(diào)用WSAStartup函數(shù)加載套接字庫(kù),如果其返回值不等于0 ,則程序退出。接下來(lái)判斷wsaData.wVersion的低字節(jié)和高字節(jié)是否都等于1,如果不是我們請(qǐng)求的版本,那么調(diào)用WSACleanup函數(shù)終止對(duì)Winsock庫(kù)的使用并返回。
加載套接字庫(kù)后,就可以按照一定流程來(lái)編寫(xiě)實(shí)現(xiàn)代碼了:
1、創(chuàng)建套接字(socket)
利用socket函數(shù)創(chuàng)建套接字,對(duì)于它來(lái)說(shuō),第一個(gè)參數(shù)只能是AF_INET或(PF_INET);本例是基于TCP協(xié)議的網(wǎng)絡(luò)程序,需要?jiǎng)?chuàng)建的是流式套接字,因此將socket函數(shù)第二個(gè)參數(shù)設(shè)置為SOCK_STREAM;將其第三個(gè)參數(shù)指定為0。這樣該函數(shù)將根據(jù)地址格式和套接字類(lèi)別,自動(dòng)選擇一個(gè)合適的協(xié)議。
2、將套接字綁定到一個(gè)本地地址和端口上(bind)
在SOCKADDR_IN結(jié)構(gòu)體中,除了sa_family成員外,其他成員都是按網(wǎng)絡(luò)字節(jié)順序表示的。因此使用htonl函數(shù)將INADDR_ANY值轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)順序。調(diào)用bind函把套接字sockSrv綁定到本地地址和指定端口上。其第一個(gè)參數(shù)為要綁定的套接字,第二個(gè)需要一個(gè)指針,可以用取地址符來(lái)實(shí)現(xiàn),并且addrSrv變量是SOCKADDR_IN結(jié)構(gòu)體類(lèi)型,而這里需要的是SOCKADDR*類(lèi)型,所以要進(jìn)行強(qiáng)制轉(zhuǎn)換。第三個(gè)參數(shù)是指定地址結(jié)構(gòu)的大小,可以利用sizeof操作符來(lái)獲取。
3、將套接字設(shè)為監(jiān)聽(tīng)模式(listen),準(zhǔn)備接收客戶(hù)請(qǐng)求。其中l(wèi)isten函數(shù)第二個(gè)參數(shù)是指等待連接隊(duì)列的最大長(zhǎng)度。
4、等待客戶(hù)請(qǐng)求的到來(lái);當(dāng)請(qǐng)求到來(lái)后,接受連接請(qǐng)求,返回一個(gè)新的對(duì)應(yīng)于此連接的套接字(accept)。
接下來(lái),需要調(diào)用accept函數(shù)等待并接受客戶(hù)的連接請(qǐng)求。因?yàn)樽鳛榉?wù)器端,它需要不斷的等待客戶(hù)端的連接請(qǐng)求的到來(lái),所以設(shè)計(jì)成一個(gè)死循環(huán)。當(dāng)客戶(hù)端有請(qǐng)求時(shí),該函數(shù)接受請(qǐng)求建立連接,同時(shí)返回一個(gè)相對(duì)于當(dāng)前這個(gè)新連接的一個(gè)套接字描述符,保存于sockConn變量中,然后利用這個(gè)套接字就可以與客戶(hù)端進(jìn)行通信了,而我們先前的套接字仍繼續(xù)監(jiān)聽(tīng)客戶(hù)端的連接請(qǐng)求。
5、用返回的套接字和客戶(hù)端進(jìn)行通信(send/recv)
可以調(diào)用send函數(shù)向客戶(hù)端發(fā)送數(shù)據(jù),注意這里使用的套接字是已建立連接的那個(gè)套接字:sockConn,而不是用于監(jiān)聽(tīng)的那個(gè)套接字:addrSrv。使用recv函數(shù)從客戶(hù)端接收數(shù)據(jù)。
6、返回等待另一個(gè)客戶(hù)請(qǐng)求
7、關(guān)閉套接字
上面實(shí)現(xiàn)的服務(wù)器端的程序,下面是客戶(hù)端程序?qū)崿F(xiàn)代碼TCPClient.cpp:

Client
1
//#include <windows.h>
2
#include <Winsock2.h>
3
#include <stdio.h>
4
5
int main()
6

{
7
WORD wVersionRequested;
8
WSADATA wsaData;
9
int err;
10
11
wVersionRequested = MAKEWORD(1, 1);
12
err = WSAStartup(wVersionRequested, &wsaData);
13
if( err != 0 )
14
{
15
return -1;
16
}
17
if( LOBYTE(wsaData.wVersion) != 1 ||
18
HIBYTE(wsaData.wVersion) != 1)
19
{
20
WSACleanup();
21
return -1;
22
}
23
24
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
25
SOCKADDR_IN addSrv;
26
addSrv.sin_addr.S_un.S_addr = inet_addr( "127.0.0.1" );
27
addSrv.sin_family = AF_INET;
28
addSrv.sin_port = htons(6000);
29
30
connect(sockClient, (SOCKADDR*)&addSrv, sizeof(SOCKADDR));
31
32
char receiveBuf[1000];
33
recv(sockClient, receiveBuf, 1000, 0);
34
printf("%s\n", receiveBuf);
35
36
send(sockClient, "This is lantionzy", strlen("This is lantionzy")+1, 0);
37
closesocket(sockClient);
38
39
WSACleanup();
40
}
對(duì)于客戶(hù)端來(lái)說(shuō),它不需要邦定,可以直接連接服務(wù)器端。
首先運(yùn)行服務(wù)器端程序,然后再運(yùn)行客戶(hù)端程序,可以看到客戶(hù)端收到了服務(wù)器端返回的信息:Welcome 127.0.0.1 to http://www.shnenglu.com/lantionzy,而服務(wù)器端收到了客戶(hù)端發(fā)送的信息:This is lantionzy。
剖析網(wǎng)絡(luò)編程(2)-- 基于UDP的的網(wǎng)絡(luò)應(yīng)用程序 剖析網(wǎng)絡(luò)編程(3)-- 基于TCP/UDP網(wǎng)絡(luò)編程應(yīng)注意的幾個(gè)地方