一、 實驗要求
(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的原因以及相關代碼,通過分析,發現是如下語句會提示錯誤:
- if(connect(hSocket,(SOCKADDR*)&servAdr,sizeof(servAdr))==SOCKET_ERROR)
-
ErrorHandling("connect() error!");
通過分析,connect()函數中的hSocket套接字沒有賦值。
(3)錯誤改正與總結:
在main()函數下爲hsocket賦值,語句如下:
- hSocket=socket(PF_INET,SOCK_STREAM,0); //創建套接字
添加該語句後編譯通過。
八、 總結
根據數據傳輸方式的不同,基於網絡協議的套接字一般分成TCP套接字和UDP套接字。
TCP套接字是面向連接的,又稱爲基於流Stream的套接字,TCP,TCP/IP協議棧分成4層架構。不同於OSI 7層架構:物理層–>數據鏈路層–>網絡層–>傳輸層–>會話層–>表示層–>應用層。實驗實習的是TCP實現基於TCP的服務器端和客戶端:
- 鏈路層:是物理鏈接領域標準化的結果,也是最基本的領域,
專門定義LAN,WAN,MAN等網絡標準。 - IP層:複雜網絡中,負責路徑的選擇。IP本身是面向消息的,不可靠的協議。
- TCP/UDP層:已IP層提供的路勁信息爲基礎完成實際的數據傳輸,
因此也稱爲傳輸層。TCP比UDP複雜,TCP可以保證可靠的數據傳輸。 - 應用層:根據程序特點決定服務器端和客戶端之間數據傳輸規則。