實驗一 基於TCP和UDP的客戶端和服務器端

一、 實驗要求

(1)分別編寫基於TCP和UDP的Windows和Linux程序客戶端和服務器端;
(2)實現TCP客戶端和服務器端之間的基本的數據收發;
(3)實現UDP客戶端和服務器端之間的基本的數據收發;

二、實驗目的

(1)掌握基於C語言的Socket編程相關函數和數據類型;
(2)掌握WIN32和Linux操作系統下的程序的基本編程方法,以及TCP、UDP編程的基本方法;
(3)熟練掌握UDP、TCP 客戶端/服務器端模式的通信原理,及編程命令;

三、 實驗環境

Windows 2007,DEV C++。

四、 實驗內容

一個簡單的客戶機/服務器程序的實現。基本原理:
服務器端:
(1)調用socket函數創建套接字;
(2)調用bind函數綁定socket和端口號;
(3)調用listen函數監聽連接請求;
(4)調用accept函數接收來自客戶端的連接請求;
(5)調用send()、recv()函數和read()、write()函數進行數據的傳輸;
(6)調用close()函數關閉套接字;
客戶端:
(1)調用socket()函數創建套接字;
(2)調用connect()函數連接指定服務器的端口;
(3)調用send()、recv()函數和read()、write()函數進行數據的傳輸;
(4)調用close()函數關閉套接字;

五、 實驗原代碼

(1)服務端代碼

1.	#include<stdio.h>  
2.	#include<stdlib.h>  
3.	#include<string.h>  
4.	#include<winsock2.h>  
5.	  
6.	#define BUF_SIZE 1024  
7.	void ErrorHandling(char *message);  
8.	  
9.	int main(int argc,char *argv[])  
10.	{  
11.	    WSADATA wsaData;//定義數據類型   
12.	    SOCKET hServSock,hClntSock;  
13.	    char message[BUF_SIZE];//消息數組   
14.	    int strLen,i;  
15.	    SOCKADDR_IN servAdr,clntAdr;//指明地址信息   
16.	    int clntAdrSize;  
17.	//初始化個變量,以及定義結構體      
18.	    if(argc!=2)//輸入兩個值   
19.	    {  
20.	        printf("Usage : %s <port>\n", argv[0]);  
21.	        exit(1);  
22.	    }  
23.	      
24.	    if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)  
25.	/*windows環境下的winsock初始化,成功返回0,失敗返回非0的錯誤代碼, 
26.	第一個參數表示程序員需要用到的winsock版本 ,第二個表示WSADATA結構體變量的地址*/   
27.	       ErrorHandling("WSAStartup() error!");//代碼出錯   
28.	      
29.	    hServSock=socket(PF_INET,SOCK_STREAM,0);//調用socket函數創建套接字   
30.	    if(hServSock==INVALID_SOCKET)//socket創建的套接字爲無效套接字   
31.	        ErrorHandling("socket() error");  
32.	      
33.	    memset(&servAdr,0,sizeof(servAdr));//初始化地址結構體信息   
34.	    servAdr.sin_family=AF_INET;//sin_family指代協議族,在socket編程中只能是AF_INET   
35.	    servAdr.sin_addr.s_addr=htonl(INADDR_ANY);//sin_addr存儲IP地址,使用in_addr這個數據結構   
36.	    //htonl函數將一個32位數從主機字節順序轉換成網絡字節順序。   
37.	    servAdr.sin_port=htons(atoi(argv[1]));//sin_port存儲端口號(使用網絡字節順序)   
38.	    /*htons是將整型變量從主機字節順序轉變成網絡字節順序,  
39.	    就是整數在地址空間存儲方式變爲高位字節存放在內存的低地址處。 
40.	    atoi (表示 ascii to integer)是把字符串轉換成整型數的一個函數*/   
41.	    if(bind(hServSock,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR)  
42.	    /*綁定,即給創建好的套接字分配IP地址和端口號 */   
43.	        ErrorHandling("bind() error");  
44.	      
45.	    if(listen(hServSock,5)==SOCKET_ERROR)//監聽,將套接字轉換成可接收的連接狀態,調用其客戶端的連接   
46.	    /*listen函數進入等待連接請求狀態,成功返回0,失敗返回-1, 
47.	    hServSock表示進入等待的套接字文件描述符(監聽套接字), 
48.	    5:表示連接請求隊列長度,表示最多有5個連接請求進入隊列*/   
49.	        ErrorHandling("listen() error");  
50.	      
51.	    clntAdrSize=sizeof(clntAdr);//調用5次   
52.	      
53.	    for(i=0;i<5;i++)  
54.	    {  
55.	        hClntSock=accept(hServSock,(SOCKADDR*)&clntAdr,&clntAdrSize);  
56.	        /*調用其受理客戶端連接請求,成功返回套接字句柄,失敗返回INVALID_SOCKET 
57.	        hServSock:套接字文件描述符, 
58.	        (SOCKADDR*)&clntAdr:保存發起連接請求的客戶端地址信息的變量地址,服務端的地址信息  
59.	        &clntAdrSize:結構體的長度,函數調用完後,該變量唄填入客戶端地址長度*/  
60.	        if(hClntSock==-1)  
61.	           ErrorHandling("accept() error");  
62.	        else  
63.	            printf("Connected client %d \n",i+1);  
64.	          
65.	    while((strLen=recv(hClntSock,message,BUF_SIZE,0))!=0)  
66.	    /*接收數據,成功時返回套接字的字節數(收到EOF時爲0),失敗時返回SOCKET_ERROR。 
67.	    第一個參數表示數據接收對象連接的套接字句柄,2:表示保存數據的緩衝區地址; 
68.	    3:表示能夠接收的最大字節序;第4個參數表示接收數據時用到的多種選項信息*/   
69.	        send(hClntSock,message,strLen,0);  
70.	    /*發送數據,成功時返回傳輸的字節數,失敗時返回SOCKET_ERROR*/      
71.	    closesocket(hClntSock);  
72.	    }  
73.	    closesocket(hServSock);//關閉套接字   
74.	    WSACleanup();//int WSACleanup(void)成功時返回0,失敗返回SOCK_ERROR。註銷該庫   
75.	    return 0;  
76.	}  
77.	  
78.	void ErrorHandling(char *message)  
79.	{  
80.	    fputs(message,stderr);  
81.	    fputc('\n',stderr);  
82.	    exit(1);      
83.	}  
84.	

(2)客戶端

1.	#include<stdio.h>  
2.	#include<stdlib.h>  
3.	#include<string.h>  
4.	#include<winsock2.h>  
5.	  
6.	#define BUF_SIZE 1024  
7.	void ErrorHandling(char *message);  
8.	  
9.	int main(int argc,char *argv[])  
10.	{  
11.	    WSADATA wsaData;  
12.	    SOCKET hSocket;  
13.	    char message[BUF_SIZE];  
14.	    int strLen;  
15.	    SOCKADDR_IN servAdr;//初始化地址信息   
16.	      
17.	    if(argc!=3){  
18.	        printf("Usage : %s <IP> <port>\n", argv[0]);  
19.	        exit(1);  
20.	    }  
21.	      
22.	    if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)  
23.	       ErrorHandling("WSAStartup() error!");  
24.	      
25.	    hSocket=socket(PF_INET,SOCK_STREAM,0);    //創建套接字 ,  
26.	  
27.	    if(hSocket==INVALID_SOCKET)  
28.	       ErrorHandling("scoket() error");  
29.	      
30.	    memset(&servAdr,0,sizeof(servAdr));//初始化地址結構體信息   
31.	    servAdr.sin_family=AF_INET;  
32.	    servAdr.sin_addr.s_addr=inet_addr(argv[1]);   
33.	    servAdr.sin_port=htons(atoi(argv[2]));  
34.	       
35.	    if(connect(hSocket,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR)  
36.	        ErrorHandling("connect() error!");        
37.	    else  
38.	       puts("Connected.......");  
39.	      
40.	    while(1)  
41.	    {  
42.	        fputs("Input message(Q to qiut): ",stdout);//從終端獲取一個字符串   
43.	        fgets(message,BUF_SIZE,stdin);  
44.	        if(!strcmp(message,"q\n")|| !strcmp(message,"Q\n"))  
45.	           break;  
46.	          
47.	        send(hSocket,message,strlen(message),0);  
48.	        strLen=recv(hSocket,message,BUF_SIZE-1,0);  
49.	        message[strLen]=0;  
50.	        printf("Message from server: %s",message);  
51.	    }  
52.	    closesocket(hSocket);  
53.	    WSACleanup();  
54.	    return 0;  
55.	 }   
56.	   
57.	void ErrorHandling(char *message)  
58.	{  
59.	    fputs(message,stderr);   
60.	    fputc('\n',stderr);  
61.	    exit(1);  
62.	}  

六、 實驗結論

服務端代碼編譯運行結果截圖:
服務端代碼編譯運行結果截圖
第一段代碼表示第一個客戶連接到回聲服務端,接收到服務並終止連接。
客戶端代碼編譯運行結果截圖:
客戶端代碼編譯運行結果截圖
回聲服務器端/客戶端以字符串爲單位傳輸數據,服務端接收到信息後反饋給客戶端。

七、 實驗錯誤及改正

(一)錯誤一:實驗環境
(1)錯誤一描述:在DEV C++環境下編譯代碼發現代碼編譯出錯,通過檢查發現代碼並沒有錯誤,compiler報錯提示爲:link環境出錯,找不到套接字相關文件(以client_win.c文件爲例)。

(2)錯誤分析:通過查閱相關資料以及與同學商量,發現是需要配置link環境,網絡編程需要導入頭文件winsock2.h,以及鏈接ws2_32.lib庫,預實驗我是按照上課老師演示的方法在visual C++6.0版本下更改鏈接庫,編譯成功並且通過,實驗室是按照在DEV C++環境下編程,更改步驟爲:tools——>Compiler Options——>General——>add(-lwsocke32)

(3)更改鏈接庫後問題解決,編譯通過。在Windows環境由於不同的軟件,在開發網絡編程時更改環境變量的位置與方法也不相同,Visual studio 2008版本添加鏈接庫的步驟爲:選擇“配置屬性”——>“輸入”——>“附加依賴項”也可以通過快捷鍵AIT+F7打開屬性頁。通過配置鏈接庫,我也明白到,套接字編程與C語言類似也有相應的鏈接庫,不同的函數,要學會調用。

(二)錯誤二:編譯通過,運行與題目不符合。
(1)錯誤二來源:代碼出錯,通過編譯,運行結果顯示套接字連接錯誤。

(2)錯誤分析:找到connect error的原因以及相關代碼,通過分析,發現是如下語句會提示錯誤:

  1. if(connect(hSocket,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR)
  2.      ErrorHandling("connect() error!"); 
    

通過分析,connect()函數中的hSocket套接字沒有賦值。
(3)錯誤改正與總結:
在main()函數下爲hsocket賦值,語句如下:

  1. hSocket=socket(PF_INET,SOCK_STREAM,0); //創建套接字
    添加該語句後編譯通過。

八、 總結

根據數據傳輸方式的不同,基於網絡協議的套接字一般分成TCP套接字和UDP套接字。
TCP套接字是面向連接的,又稱爲基於流Stream的套接字,TCP,TCP/IP協議棧分成4層架構。不同於OSI 7層架構:物理層–>數據鏈路層–>網絡層–>傳輸層–>會話層–>表示層–>應用層。實驗實習的是TCP實現基於TCP的服務器端和客戶端:

  1. 鏈路層:是物理鏈接領域標準化的結果,也是最基本的領域,
    專門定義LAN,WAN,MAN等網絡標準。
  2. IP層:複雜網絡中,負責路徑的選擇。IP本身是面向消息的,不可靠的協議。
  3. TCP/UDP層:已IP層提供的路勁信息爲基礎完成實際的數據傳輸,
    因此也稱爲傳輸層。TCP比UDP複雜,TCP可以保證可靠的數據傳輸。
  4. 應用層:根據程序特點決定服務器端和客戶端之間數據傳輸規則。
    tcp服務器端函數調用順序
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章