?? dosnetworkprogrammimg.txt
字號:
int s, //套接字的句柄;
int level, //屬性的分類;
int optname, //要設(shè)置的屬性;
char far *optval, //要設(shè)置的屬性值;
int optlen //屬性值參數(shù)的長度。
);
比如,允許套接字綁定到已經(jīng)使用的端口:
DWORD value = 1;
int result = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char far *)(&value), sizeof(value));
更詳細(xì)的內(nèi)容請參考本目錄MSTCPSDK.rar中的winsock.txt。或者查看MSDN,內(nèi)容大致相同。
9 錯誤代碼errno
errno是Borland C++編譯器定義的一個整數(shù)型全局變量,很多函數(shù)把執(zhí)行時發(fā)生的錯誤寫到errno變量中,也會把執(zhí)行中執(zhí)行后的狀態(tài)寫到errno變量中,經(jīng)常遇到的有以下這些:
#define EISCONN 118 //socket已經(jīng)連接上
#define ENOTCONN 119 //socket沒有連接上
#define EINPROGRESS 126 //函數(shù)正在執(zhí)行中
第五章 TCP編程模型
和UDP協(xié)議有所區(qū)別,TCP協(xié)議的特點在于:
TCP采用超時重傳及機制來保證不丟失數(shù)據(jù),當(dāng)一個TCP發(fā)送一個數(shù)據(jù)包后,它啟動一個定時器,等待對端確認(rèn)收到這個包,如果在指定的時間內(nèi)沒有得到確認(rèn),將重發(fā)這個包。而接收數(shù)據(jù)包的時候,它將發(fā)送一個確認(rèn),如果檢測到數(shù)據(jù)包有錯,TCP協(xié)議丟棄這個數(shù)據(jù)包,并且不發(fā)送確認(rèn),那么對端會因為超時而重新發(fā)送這個數(shù)據(jù)包。
一組有序的數(shù)據(jù)包,到達(dá)對端時可能會有后發(fā)的數(shù)據(jù)先到的情況,TCP協(xié)議在包首部保存數(shù)據(jù)包序號,如果有必要,它將對收到的數(shù)據(jù)重新排序,并以正確的順序交給應(yīng)用層。
1 客戶端和服務(wù)器端的工作模型
TCP的工作方式用上圖的客戶端/服務(wù)器端模型來描述,通信的發(fā)起方稱為客戶端(Client),通信的等待方稱為服務(wù)器端(Server)。客戶端可以隨時使用connect函數(shù)連接到服務(wù)器端,服務(wù)器檢測到這個連接后,需要使用accept函數(shù)接受這個連接,當(dāng)物務(wù)器接受連接后,一個穩(wěn)定的連接就建立了,雙方可以開始互相通過send和recv函數(shù)收發(fā)數(shù)據(jù)了,這時通信的兩端并沒有任何區(qū)別。
DOS系統(tǒng)通常用作客戶端,服務(wù)端通常用Window系統(tǒng),所以本文主要寫DOS操作系統(tǒng)下的TCP客戶端編程。
2 連接到服務(wù)端
使用connect函數(shù):
int connect(int s, struct sockaddr far *name, int namelen); s是套接字的句柄;name是服務(wù)端的地址;namelen是name數(shù)據(jù)結(jié)構(gòu)的長度;函數(shù)執(zhí)行成功返回0,不成功返回SOCKET_ERROR(-1),然后使用errno變量得到具體的出錯原因。
struct sockaddr和struct sockaddr_in的內(nèi)容一樣,長度一樣,所以使用struct sockaddr_in定義變量name,在調(diào)用connect函數(shù)的時候強制轉(zhuǎn)換成struct sockaddr。
當(dāng)套接字工作在非阻塞模式下的時候,不管連接成功與否,connect函數(shù)會馬上返回并返回SOCKET_ERROR(-1),這時并不意味著連接失敗,而是表示函數(shù)返回的時候連接尚未成功。這時查詢errno變量如果不等于EINPROGRESS(126,表示操作正在進行中),才表示連接失敗。
非阻塞模式下的連接:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6000);
addr.sin_addr.s_addr = inet_addr(“10.0.0.1”);
connect(s, (struct sockaddr far *)(&addr), sizeof(addr));
delay(100);
if(errno != 118)
printf(“fail to connect”);
3 發(fā)送數(shù)據(jù)
使用send函數(shù):
int send(int s, char far *pBuf, int len, int flags); s是套接字句柄;pBuf是數(shù)據(jù)緩沖區(qū);len是要發(fā)送的數(shù)據(jù)長度;flags是發(fā)送選項,這個參數(shù)一般指定為0。如果發(fā)送失敗,函數(shù)返回SOCKET_ERROR(-1),否則返回成功發(fā)送的字節(jié)數(shù)。
dos_sock為每個socket分配一個發(fā)送緩沖區(qū)和接受緩沖區(qū),用send函數(shù)發(fā)送數(shù)據(jù)時,數(shù)據(jù)并沒有馬上在網(wǎng)絡(luò)上進行傳遞,而是先放到socket的發(fā)送緩沖區(qū)中,數(shù)據(jù)會在合時的時候被發(fā)送出去。所以前面的“成功發(fā)送”指的是成功放入發(fā)送緩沖區(qū)而已。
函數(shù)在阻塞模式和非阻塞模式下的表現(xiàn)有些不同,下面已send函數(shù)發(fā)送n字節(jié)數(shù)據(jù)為例說明。
在阻塞模式下,如果發(fā)送緩沖區(qū)的空閑空間足夠大,能容納n字節(jié)的數(shù)據(jù),這時函數(shù)會將數(shù)據(jù)全部放入發(fā)送緩沖區(qū),然后馬上返回;如果緩沖區(qū)不夠大,函數(shù)會一邊放入數(shù)據(jù)一邊等待,直到把全部數(shù)據(jù)放入緩沖區(qū)為止。在這兩種情況下,返回值都是實際發(fā)送的字節(jié)數(shù)n。這時程序比較簡單:
int m = send(s, pBuf, n, 0); //m = n
在非阻塞模式下,如果發(fā)送緩沖區(qū)的空閑空間也能容納n字節(jié)的數(shù)據(jù),這時函數(shù)也會將數(shù)據(jù)全部放入發(fā)送緩沖區(qū),然后馬上返回,返回值就是實際發(fā)送的字節(jié)數(shù)n。當(dāng)緩沖區(qū)不夠大,函數(shù)也不會等待,而是把一部分?jǐn)?shù)據(jù)放入緩沖區(qū)后馬上返回,這時返回值是實際發(fā)送的字節(jié)數(shù)m。程序可以這樣寫。
while(n > 0)
{
m = send(s, pBuf, n, 0);
if(m == SOCKET_ERROR)
{
printf(“send error”);
break;
}
pBuf += m; //移動指針,指向剩余的數(shù)據(jù)
n -= m; //剩余的長度
delay(10);
}
4 接收數(shù)據(jù)
使用recv函數(shù):
int recv(int s, char far *pBuf, int len, int flags); s是套接字句柄;pBuf是用來返回數(shù)據(jù)的緩沖區(qū);len是要接受的數(shù)據(jù)長度;flags是接收選項,一般也指定為0。如果接收失敗,返回SOCKET_ERROR(-1),否則返回實際接收的字節(jié)數(shù)。
在阻塞模式下,函數(shù)等待直到有數(shù)據(jù)到達(dá)為止(接收緩沖區(qū)不為空),有多少數(shù)據(jù)到達(dá)就返回多少數(shù)據(jù)。要接收n字節(jié)長度的數(shù)據(jù),程序如下:
while(n > 0)
{
m = recv(s, pBuf, n, 0);
if(m == SOCKET_ERROR)
{
printf(“recv error”);
break;
}
pBuf += m; //移動指針,指向剩余的數(shù)據(jù)
n -= m; //剩余的長度
}
在非阻塞模式下,如果接收緩沖區(qū)中已經(jīng)有數(shù)據(jù),recv的表現(xiàn)方式和阻塞模式相同,函數(shù)會馬上返回,并視緩沖區(qū)中的數(shù)據(jù)數(shù)量返回1到n之間的數(shù)據(jù)。如果接收緩沖區(qū)空,函數(shù)不會等待,而是馬上返回SOCKET_ERROR(-1)。
5 TCP服務(wù)端的介紹
TCP服務(wù)端一般應(yīng)用在Windows系統(tǒng)下,所以下面描述的函數(shù)都來自MSDN,并且可以在VC++6.0中使用。
服務(wù)端在創(chuàng)建了socket后需要綁定到本地的一個端口上,等待客戶端連接到這個端口。使用bind函數(shù):
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(6000); //本機上的一個端口
addr.sin_addr.s_addr = INADDR_ANY; //表示本機
bind(s, (struct sockaddr far *)(&addr), sizeof(addr));
在綁定之后使用listen函數(shù)使TCP套接字進入監(jiān)聽狀態(tài):
listen(s, 5); 能夠可以同時發(fā)現(xiàn)5個客戶端連接而不遺漏。然后套接字就處于等待連接進入的狀態(tài)了。
當(dāng)有客戶端向監(jiān)聽中的套接字發(fā)起連接后,必須對監(jiān)聽中的套接字調(diào)用accept函數(shù),連接才最后被確認(rèn)。accept函數(shù)將新建一個套接字并返回它的句柄,這個新套接字還是和客戶端連接的,程序以后可以使用它來和客戶端之間收發(fā)數(shù)據(jù)了。
while(1)
{
SOCKET newSocket = accept(s, 0, 0);
if(newSocket != INVALID_SOCKET)
{
//創(chuàng)建一個新線程
AfxBeginThread(newThread, LPVOID(newSocket));
}
}
DWORD WINAPI newThread(LPVOID lParam)
{
SOCKET hSocket = (SOCKET)lParam;
// recv(hSocket, pBuf, len, 0);
// send(hSocket, pBuf, len, 0);
}
總結(jié)
文章詳細(xì)介紹了在MS-DOS操作系統(tǒng)下開發(fā)網(wǎng)絡(luò)客戶端程序,也介紹了Windows中的服務(wù)端。具體代碼請參考董瑣英編寫的過電壓下位機程序。有了上面這些知識,然后仿照過電壓程序?qū)苋菀组_發(fā)出自己的網(wǎng)絡(luò)應(yīng)用。
----------------------------------
楊志朋 2008年4月7日
yzp3646@163.com
yzp3646@sina.com
河北旭輝電氣股份有限公司
第13頁 / 共15頁
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -