VC++深入詳解(12):網路程式設計

thefutureisour發表於2012-11-04
這一小節介紹網路程式設計。首先我們介紹一下計算機網路的基本知識,然後著重介紹一下Windows Socket程式的編寫。
首先,介紹幾個基本概念。什麼是計算機網路?它是相互連線的獨立自主的計算機的集合。它們是如何通訊的呢,需要一個東西來表明我要跟哪個計算機進行通訊,在網路上,為每個計算機分配了一個“IP”地址,通過地址來找到想要通訊的計算機。具體的通訊是計算機的某個程式實現的,一臺計算機可能同時有多個程式在使用網路。為了區分它們,為每個程式提供了一個“埠號”來標識自己。具體通訊時所傳送的內容,成為協議,它規定了我們傳送的格式:除了傳送的內容以外,還包括這些東西是誰傳送的,要發給誰,總共有多長等等。而資訊具體是如何從一臺主機,傳送到另一臺主機,則會有很大的不確定性:
不同的通訊媒介:是通過有線傳輸的,還是無線網路?
不同的作業系統:Unix、Windows
不同的應用環境:移動、固定
不同的業務型別:對時延的要求、對差錯控制的要求。
等等,使得實際中的相互通訊的網路異常複雜,如何解決這個問題呢?國際標準化組織(ISO)提出了OSI(Open System Interconnected)七層參考模型,將網路按照不同的功能劃分為7層:

應用層
表示層
會話層
傳輸層
網路層

資料鏈路層

物理層


它們的功能簡述如下:
物理層:提供二進位制傳輸,確定在通訊通道上如何傳輸位元流。一條物理通道上所能傳送的資訊的最快速度是有限制的,不是我們想傳送多塊就能傳送多快的。為了對抗複雜的傳輸環境(這一點在無線通訊中尤其明顯),物理層通常要使用非常複雜的調製、編碼技術,來在在一定差錯容忍度的前提下,儘可能的多傳送。
資料鏈路層:提供介質訪問,增強物理層的傳輸功能,建立一條無差錯的傳輸線路。比如對於收到物理層傳送過來的資料,需要通過確認請求或者簡單的差錯控制編碼(比如奇偶效驗)來判斷這一幀資料是否有錯誤,如果錯誤了,則通知傳送發重新傳送。
網路層:IP定址和路由。因為網路上的資料可以經由多條路線到達目的地,所以其中要考慮路由演算法、擁塞控制等問題。
傳輸層:為源端主機到目的端主機提供可靠的資料傳輸服務,隔離網路的上下層協議,是得網路應用於下層協議無關。也就是應用程式與應用程式之間的連線。
會話層:兩個相互通訊的應用程式之間建立、組織和協調其相互之間的通訊。比如電影裡使用對講機時,一句話說完後總要加一句“over”。
表示層:被傳送的資料如何表示。
應用層:使用者所提供的服務。
要注意,這7層模型是功能上的劃分,並不是具體一定要有這七層。
下面介紹一下應用層、傳輸層和網路層的常見協議(這是筆試題中常考的):
應用層協議:遠端登入協議(Telnet)、檔案傳輸協議(FTP)、超文字傳輸協議(HTTP)、域名服務(DNS)、簡單喲見傳輸協議(SMTP)、郵局協議(POP3)等。
傳輸層:傳輸控制協議(TCP)、使用者資料包協議(UDP)。
這兩個協議值得仔細說說。
TCP協議是面向連線的,也就是說,在雙方通訊之前,已經安排好了一條通訊線路(不管它具體是什麼)共他倆使用,別人不能使用,等他倆通訊結束後,需要釋放這條線路。TCP是通過3次握手建立的:
1.客戶端給伺服器傳送SYN(syn = j)包,進入SYN_SEND狀態。
2.伺服器接收到SYN包,確認客戶的SYN(ack = j+1),同時自己也傳送一個SYN包(syn = k),把它倆都傳送出去,伺服器進入SYN_RECV狀態。
3.客戶端收到伺服器的SYN+ACK包,向伺服器傳送ACK(ack = k+1)。客戶端和伺服器都進入ESTABLISHED狀態。此時,連線已經建立完畢,可以傳送訊息了。
上面只是正常的建立連線過程,其中的任何一步都有可能失敗,至於失敗以後的操作,這裡就不細說了。
下面再看看UDP協議:這是一種無連線的、不可靠的協議。這意味著可能向對方傳送的訊息時對方無法接到。或者,向一個根本不存在的IP地址或者埠傳送訊息。既然是不可靠的,為什麼還要使用它呢?因為UDP協議不需要建立連線,沒有資料重傳,所以實時性較高。比如我們看視訊時,一兩個畫素的錯誤我們根本不會發覺。
網路層:網際協議(IP),Internet網際網路報文控制協議(ICMP)、Internet組管理協議IGMP。


由於7層模型在使用起來很不方便,實際中應用更廣泛的是TCP/IP模型:


應用層
傳輸層
網路層

網路介面層


它與OSI七層模型的對應關係如下:網路介面層對應於物理層和資料鏈路層,網路層對應於網路層,傳輸層對應於傳輸層,應用層對應於會話層、表示層、應用層。
在TCP/IP網路應用中,通訊的兩個程式間相互作用的主要模式是客戶機、伺服器模式:客戶向伺服器傳送請求,伺服器收到請求後,提供相應的服務。為什麼這麼設計呢?首先,建立網路的原因是因為網路中軟硬體資源、運算能力和資訊分佈不均,需要共享,從而擁有資源多的主機提供服務,資源少的客戶請求服務。其次,網間程式通訊完全是非同步的,相互通訊的程式間即不存在父子關係,又不存在共享的記憶體緩衝區,需要一種機制為希望通訊的程式間建立聯絡,為二者的資料交換提供同步。
它們的通訊過程如下:
伺服器先執行:
1.開啟一個通訊通道並告知本地主機,他願意在某一地址和埠上接收客戶的請求。
2.等待客戶請求到達埠。
3.接收到重複服務請求沒處理該請求併傳送應答訊號。收到併發服務的請求,要啟用一個新的程式或者執行緒來處理這個客戶請求。新的程式或者執行緒處理此客戶的請求,並不需要對其他請求作出相應。等服務完成後,關閉新程式與客戶的通訊鏈路,並終止。
4.返回第二步,等待另一請求。
5.關閉伺服器。


客戶端:
1.開啟一個通訊通道並連線到伺服器所在的主機的特定埠。
2.向伺服器傳送服務請求報文,等待接收應答,繼續提出請求。
3.請求結束後關閉通訊通道並終止。


講了這麼多,我們可以隱約感覺到,網路程式設計是一件很麻煩的事情,為了方便的開發應用軟體程式,美國伯克利大學在UNIX上推出了一種應用程式訪問通訊協議的作業系統套接字(socket),是得程式設計師可以很方便的訪問TCP/IP協議,從而開發各種網路應用程式。後來,socket又引入到windows作業系統。我們先介紹與之相關的函式,然後給出幾個例子:
1.使用WSAStartup進行版本協商。
int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
其中wVersionRequested指明瞭版本號。高位元組是副版本,低位元組是主版本,可以通過MAEWORD巨集來獲得。lpWSAData是用來返回值的,這個函式會把它載入的版本資訊填到這個結構裡面。具體的使用方法可以參照MSDN給出的例子。
2.使用WSACleanup()來釋放為應用程式分配的資源,與WSAStartup相對應。
3.使用socket函式來建立套接字:
SOCKET socket(  int af, int type,int protocol  );
af引數是地址族,對於TCP/IP協議,它只能是AF_INET。
type指明瞭socket的型別,對於1.1版本的socket,他只接受兩種型別:SOCK_STREAM、SOCK_DGRAM
protocol指明瞭與特定地址家族相關的協議,如果為0,則根據地址格式和套接字類別,自動選擇一個合適的協議。
如果函式呼叫成功,則會返回一個SOCK資料型別的套接字描述符:如果呼叫失敗,則返回一個INVALID_SOCKET值。
4.使用bind函式將套接字繫結到本地的某個地址和埠上:
int bind(  SOCKET s, const struct sockaddr FAR *name,int namelen);
s指定要繫結的套接字,name是一個指向sockaddr結構體型別的指標,這個結構體表明瞭本地資訊。
struct sockaddr 
{
  u_short    sa_family;
  char       sa_data[14];
}; 
由於這個結構是為所有地址族準備的,所以不同的協議會有一定的區別,所以用第三個引數指明結構的長度。
再回過頭來看第二個引數,第一成員指明瞭地址族,第二個成員是14個位元組的記憶體區域,對於不同的協議,有不同的內容。對於TCP/IP協議,使用sockaddr_in 結構來替換sockaddr結構:
struct sockaddr_in 
{
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};


sin_family指明瞭地址族,應使用AF_INET;sin_port指明瞭埠號;sin_addr指明瞭主機的IP地址;sin_zero則只是為了填充位元組,是得sockaddr_in 與sockaddr長度相同。其中sin_addr又是一個結構體,定義如下:
typedef struct in_addr 
{
union 
{
struct
{
unsigned char s_b1,s_b2,s_b3,s_b4;
} S_un_b;
struct 
{
unsigned short s_w1,s_w2;
} S_un_w;
unsigned long S_addr;
} S_un;
} IN_ADDR


這個結構其實是一個聯合體,通常我們都是將點分十進位制的IP地址轉換為u_long型別,並賦值給S_addr成員。
一般情況下,我們可以使用INADDR_ANY允許套接字向任何分配本地機器的IP地址傳送或者接收資料。這個引數的實際意義在於,一般情況下,一臺主機只有一個IP地址,但如果主機有兩個網路卡,那麼他會有兩個IP地址,如果我們只想使用其中的一個供套接字使用,我們可以使用inet_addr函式將本地的IP地址(點分十進位制字串形式),轉化為unsigned long並賦給S_addr;與之相反的轉化為inet_ntoa函式,將S_addr轉化為點分十進位制,供列印輸出使用。
5.listen函式將指定的套接字設定為監聽模式。
int listen(  SOCKET s, int backlog  );
s 為指定的套接字;backlog為等待佇列的最大長度。
6.使用accept函式來接受客戶端的連線請求
SOCKET accept(  SOCKET s,  struct sockaddr FAR *addr,  int FAR *addrlen);
s為已被設定為監聽模式的套接字;addr為指向客戶端的sockaddr的地址,通過函式來獲取值;addrlen為地址長度。
6.通過send函式傳送訊息
int send(SOCKET s,const char FAR *buf,int len,int flags);
s為已建立連線的套接字,buf為要傳送訊息的地址,len為訊息長度,flags一般設為0即可。
7.通過recv函式獲取訊息:
int recv(SOCKET s,char FAR *buf,int len,int flags);
s為已建立連線的套接字,buf用來儲存接收的資料,len表示緩衝區的長度,flags一般填0即可。
8.使用connect函式與特定的套接字連線
int connect(  SOCKET s,const struct sockaddr FAR *name,int namelen);
s為即將建立連線的那個套接字;name指定了伺服器端的地址資訊,s為地址資訊長度。
9.使用recvfrom接受訊息
int recvfrom(SOCKET s,char FAR* buf,int len,int flags,struct sockaddr FAR *from,int FAR *fromlen);
s為準備接受資料的套接字,buf為接收資料的緩衝區,len為緩衝區的長度,flag一般填0,from指標用來儲存傳送方的地址資訊,fromlen為地址的長度。
10.使用sendto向一個特定的目的方傳送資料。
int sendto(  SOCKET s,const char FAR *buf,int len,int flags,const struct sockaddr FAR *to,int tolen );
s為套接字,buf為傳送的資料的地址,len為資料的長度,flags一般為0,to指標指向目標套接字的地址,tolen為地址的長度
11.位元組序的轉換函式。
首先先搞清楚什麼是位元組序。一般情況下,我們使用電腦都是低位在前、高位在後的,這被稱為小端位元組序;而網路傳輸時使用的是低位在前,高位在後的大端位元組序。如果不進行轉化一個16為資料0X1234就被網路認為是0X3412了。轉換的函式有兩個:
u_short htons(  u_short hostshort  );將一個16位數轉換為網路位元組序
u_long htonl(  u_long hostlong  );將一個32位數轉換為網路位元組序


介紹完函式,我們就先舉一個利用TCP協議編寫的簡單的網路通訊的例子。我們先看一下基本步驟:
伺服器端:
1.進行版本協商(WSAStartup)。
2.建立一個套接字(socket)。
3.將套接字設為監聽狀態(listen)。
4.接受客戶端的傳送請求(accept)。
5.傳送或者接收資料(send/recv)。
6.關閉套接字(closesocket),一次通訊結束。
7.轉4.


客戶端端:
1.進行版本協商(WSAStartup)。
2.建立一個套接字(socket)。
3.連線到伺服器(connect)。
4.傳送或者接收訊息(send/recv)。
5.關閉套接字(closesocket)。
6.釋放資源(WSACleanup)。

再看我們的程式:

//伺服器端程式
#include <Winsock2.h>
#include <stdio.h>


int main()
{
	//進行版本協商
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	
	wVersionRequested = MAKEWORD( 1, 1 );

	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 )                             
		return -1;
	
	
	if ( LOBYTE( wsaData.wVersion ) != 1 ||
         HIBYTE( wsaData.wVersion ) != 1 ) 
	{

		WSACleanup( );
		return -1; 
	}
	
	//建立套接字
	SOCKET socksrv = socket(AF_INET,SOCK_STREAM,0);

	//填寫本地資訊
	SOCKADDR_IN addrSrv;
	//本機IP地址
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	//協議族
	addrSrv.sin_family = AF_INET;
	//埠資訊,必須使用1024以上,注意位元組序轉換
	addrSrv.sin_port = htons(6000);
	
	//繫結套接字
	bind(socksrv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));

	//設為監聽模式
	listen(socksrv,5);

	//客戶端地址資訊結構體
	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);

	while(1)
	{
		//接受客戶端請求,返回值為已經建立連線的SOCKET
		SOCKET sockConn = accept(socksrv,(SOCKADDR*)&addrClient,&len);
		//儲存資料的緩衝區
		char sendBuf[100];
		//將資料格式化到緩衝區中
		sprintf(sendBuf,"Welcom %s to the Service!",
			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);

	}
	//由於伺服器一直處理工作狀態,所以不呼叫WSACleanup釋放資源
	return 0;
}

//客戶端程式
#include <Winsock2.h>
#include <stdio.h>


int main()
{
	//進行版本協商
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	
	wVersionRequested = MAKEWORD( 1, 1 );

	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 )                             
		return -1;
	
	
	if ( LOBYTE( wsaData.wVersion ) != 1 ||
         HIBYTE( wsaData.wVersion ) != 1 ) 
	{

		WSACleanup( );
		return -1; 
	}

	//建立套接字
	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 revcBuf[100];
	//接收伺服器的資料
	recv(sockClient,revcBuf,100,0);
	//列印資料
	printf("%s",revcBuf);
	
	//傳送資料緩
	send(sockClient,"hello",strlen("hello")+1,0);
	closesocket(sockClient);

	//釋放資源
	WSACleanup();


	return 0;
}
下面我們再看看基於UDP編寫的客戶端/伺服器的應用程式的步驟:
伺服器:
1.進行版本協商(WSAStartup)。
2.建立一個套接字(socket)。
3.繫結套接字(bind)。
4.接收或者傳送訊息(recvfrom/sendto)。
5.關閉套接字(closesocket)。
6.釋放資源(WSACleanup)。
客戶端:
1.進行版本協商(WSAStartup)。
2.建立套接字(socket)。
3.傳送或接收訊息(sendto/recvfrom)。
4.關閉套接字(closesocket)。
5.釋放資源(WSACleanup)。


對應的程式如下:

//伺服器程式
#include <Winsock2.h>
#include <stdio.h>

int main()
{
	//進行版本協商
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	
	wVersionRequested = MAKEWORD( 1, 1 );

	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 )                             
		return -1;
	
	
	if ( LOBYTE( wsaData.wVersion ) != 1 ||
         HIBYTE( wsaData.wVersion ) != 1 ) 
	{

		WSACleanup( );
		return -1; 
	}
	//建立套接字
	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(addrClient);
	char recvBuf[100];
	//接收資料
	recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addrClient,&len);
	printf("%s\n",recvBuf);
	
	//關閉套接字
	closesocket(sockSrv);
	//釋放資源
	WSACleanup();
	return 0;
}

//客戶端程式
#include <Winsock2.h>
#include <stdio.h>


int main()
{
	//進行版本協商
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	
	wVersionRequested = MAKEWORD( 1, 1 );

	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 )                             
		return -1;
	
	
	if ( LOBYTE( wsaData.wVersion ) != 1 ||
         HIBYTE( wsaData.wVersion ) != 1 ) 
	{

		WSACleanup( );
		return -1; 
	}
	
	//建立套接字
	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();
	return 0;
}

感覺要簡單許多,這裡稍微總結一下TPC與UDP編寫程式的區別:

TCP伺服器端要先設定為監聽(listen)模式,然後要等待響應(accept),並且利用響應的套接字進行傳送(send)和接受(recv)。由於是面向連線的方式,所以傳送和接受時都不需要指明地址資訊。而客戶端要連線到伺服器(connect)。而UDP的過程相對簡單,沒有監聽、連線、響應的過程,直接可以使用sendto和recvfrom來傳送和接收訊息。由於UDP是面向非連線的,所以要在引數中指明要傳送或者接收的物件。


最後,我們利用UDP協議實現一個簡單的聊天程式。為什麼時候UDP協議?因為聊天時候,稍微錯一點不要緊,可以通過上下文、語義,甚至讓對法重新傳送來克服;而且,聊天時經常出現一個屌絲向一個女神搭訕,但是女神遲遲不回覆的情況,這時是應該關閉連線呢,還是應該保持連線,讓它們繼續佔用資源?這是一個很糾結的問題。相比之下,UDP協議的優勢就體現出來了。
再考慮一個問題,如何結束聊天?可以通過傳送一個特定的字元來實現,當伺服器收到這個特定的字元時,向客戶端也傳送這個字元,並且關閉自己的套接字;同理客戶端如果收到這個字元,也可以向伺服器傳送這個字元(儘管可能此時伺服器的套接字已經關閉了),並關閉自己的套接字。


想清楚了這兩點,我們就開始編寫程式吧:

//伺服器程式
#include <Winsock2.h>
#include <stdio.h>

int main()
{
	//進行版本協商
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	
	wVersionRequested = MAKEWORD( 1, 1 );

	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 )                             
		return -1;
	
	
	if ( LOBYTE( wsaData.wVersion ) != 1 ||
         HIBYTE( wsaData.wVersion ) != 1 ) 
	{

		WSACleanup( );
		return -1; 
	}

	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(addrSrv));

	char recvBuf[100];
	char sendBuf[100];
	char tempBuf[100];

	SOCKADDR_IN addClient;
	int len = sizeof(SOCKADDR);
	while(1)
	{
		//接收資料
		recvfrom(sockSrv,recvBuf,100,0,(SOCKADDR*)&addClient,&len);
		//判斷對話是否應該被終止
		if('#' == recvBuf[0])
		{
			sendto(sockSrv,"#",strlen("#")+1,0,(SOCKADDR*)&addClient,len);
			printf("chat end!\n");
			break;
		}

		//列印接收的資料
		sprintf(tempBuf,"%s says: %s",inet_ntoa(addClient.sin_addr),recvBuf);
		printf("%s\n",tempBuf);

		//傳送資料
		printf("please input data:\n");
		gets(sendBuf);
		sendto(sockSrv,sendBuf,strlen(sendBuf)+1,0,(SOCKADDR*)&addClient,len);

	}

	closesocket(sockSrv);
	WSACleanup( );
	return 0;
}

//客戶端程式
#include <Winsock2.h>
#include <stdio.h>


int main()
{
	//進行版本協商
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	
	wVersionRequested = MAKEWORD( 1, 1 );

	err = WSAStartup( wVersionRequested, &wsaData );
	if ( err != 0 )                             
		return -1;
	
	
	if ( LOBYTE( wsaData.wVersion ) != 1 ||
         HIBYTE( wsaData.wVersion ) != 1 ) 
	{

		WSACleanup( );
		return -1; 
	}

	//建立套接字
	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);

	char recvBuf[100];
	char sendBUf[100];
	char tempBuf[100];

	int len = sizeof(SOCKADDR);
	while(1)
	{
		//客戶端線傳送資料:

		//輸入傳送資料
		printf("please input data:\n");
		gets(sendBUf);
		//傳送資料
		sendto(sockClient,sendBUf,100,0,(SOCKADDR*)&addrSrv,len);
		//接收資料
		recvfrom(sockClient,recvBuf,100,0,(SOCKADDR*)&addrSrv,&len);
		//判斷對話是否終止
		if('#' == recvBuf[0])
		{
			sendto(sockClient,"#",strlen("#")+1,0,(SOCKADDR*)&addrSrv,len);
			printf("chat over!");
			break;
		}
		
		//列印接收的資料
		sprintf(tempBuf,"%s say: %s",inet_ntoa(addrSrv.sin_addr),recvBuf);
		printf("%s",tempBuf);

	}
	closesocket(sockClient);
	WSACleanup( );
	return 0;
}




相關文章