網路程式設計實驗一——TCP、UDP網路程式設計
一、實驗目的
1、利用TCP實現套接字通訊並理解TCP通訊的工作原理。
2、利用UDP實現套接字通訊並理解UDP通訊的工作原理。
實驗環境及準備
裝有Linux系統的計算機。
二、實驗原理
1、TCP/IP協議存在於OS中,網路服務通過OS提供。
2、應用程式要和作業系統互動,才能使用TCP/IP提供的網路通訊功能。
3、互動的介面:即應用程式介面(API)。
4、從網路的觀點看:TCP/IP和應用程式之間的介面。
三、實驗內容:
1. linux環境下C語言程式的編譯、除錯(分單個C語言原始檔和多個C語言原始檔的情況)。
單個C語言程式的編譯:
首先安裝GCC編譯器,在ubantu環境下,命令如下:
sudo apt-get install gcc
編輯器可以用 gedit(一種文字模式的編輯器)或者 vi (圖形模式的文字編輯器)。在這裡選用gedit。
從簡單的hello world開始。
通過gedit hello.c命令開啟編輯器介面,編寫程式:
編寫完畢後,儲存。
在命令列執行該程式:
從命令列上可以看出,”Hello World!”成功輸出。到現在為止,Linux環境下的第一個程式算是順利完成了。
多個C語言原始檔編譯:
這裡的例子為,編寫一個函式Print(),儲存在message.c中,在main.c中呼叫這個函式。
程式碼如下:
在命令列,進行執行:
在這裡我們是同時編譯了兩個.c檔案。因為在message.c檔案中,只存在一個輸出函式,可以在main.c中,包含這個.c檔案,這樣編譯的時候,只編譯main.c就可以了。
修改如下:
2. TCP客戶伺服器模型的實現
(1)實現原理:
一個完整TCP通訊過程需要依次經歷三個階段:首先,客戶機必須建立與伺服器的連線,所謂虛電路。然後,憑藉已建立好的連線,通訊雙方相互交換資料。最後,客戶機與伺服器雙雙終止連線,結束通訊過程。
基於TCP的網路程式設計開發分為伺服器端和客戶端兩部分,常見的核心步驟和流程如上圖所示。
(2)實驗程式碼:
-
server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { unsigned short port = 6666; // 本地埠 if(argc > 1) { port = atoi(argv[1]); } int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 建立通訊端點:套接字 if(sockfd < 0) { perror("socket"); exit(-1); } // 設定本地地址結構體 struct sockaddr_in my_addr; bzero(&my_addr, sizeof(my_addr)); // 清空 my_addr.sin_family = AF_INET; // ipv4 my_addr.sin_port = htons(port); // 埠 my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip // 繫結 int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); if( err_log != 0) { perror("binding"); close(sockfd); exit(-1); } err_log = listen(sockfd, 10); // 監聽,監聽套接字改為被動 if(err_log != 0) { perror("listen"); close(sockfd); exit(-1); } printf("listen client @port=%d...\n",port); while(1) { struct sockaddr_in client_addr; char cli_ip[INET_ADDRSTRLEN] = ""; socklen_t cliaddr_len = sizeof(client_addr); int connfd; // 等待連線 connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len); if(connfd < 0) { perror("accept"); continue; } inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN); printf("----------------------------------------------\n"); printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port)); char recv_buf[512] = ""; while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 ) // 接收資料 { printf("\nrecv data:\n"); printf("%s\n",recv_buf); write(connfd,recv_buf,sizeof(recv_buf));//將客戶端收到的資料在轉發回去 } close(connfd); //關閉已連線套接字 printf("client closed!\n"); } close(sockfd); //關閉監聽套接字 return 0; }
-
client.c
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc, char *argv[])
{
unsigned short port = 6666; // 伺服器的埠號
char *server_ip = "127.0.0.1"; // 伺服器ip地址
if( argc > 1 ) //函式傳參,可以更改伺服器的ip地址
{
server_ip = argv[1];
}
if( argc > 2 ) //函式傳參,可以更改伺服器的埠號
{
port = atoi(argv[2]);
}
int sockfd;
sockfd = socket(AF_INET, SOCK_STREAM, 0);// 建立通訊端點:套接字
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
// 設定伺服器地址結構體
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr)); // 初始化伺服器地址
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_port = htons(port); // 埠
inet_pton(AF_INET, server_ip, &server_addr.sin_addr); // ip
// 主動連線伺服器
int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if(err_log != 0)
{
perror("connect");
close(sockfd);
exit(-1);
}
printf("send data to %s:%d\n",server_ip,port);
//char send_buf[512] = "Hi, I am Mike.";
//send(sockfd, send_buf, strlen(send_buf), 0); // 向伺服器傳送資訊
//char recv_buf[512] = {0};
//recv(sockfd, recv_buf, sizeof(send_buf), 0); // 接收資料
//printf("recv_buf ========== %s\n", recv_buf);
while(1){
char send_buf[512] = {0};
printf("send:");
fgets(send_buf,sizeof(send_buf),stdin);//shuru
send_buf[strlen(send_buf)-1]='\0';
send(sockfd, send_buf, strlen(send_buf), 0); // 向伺服器傳送資訊
char recv_buf[512] = {0};
recv(sockfd, recv_buf, sizeof(send_buf), 0); // 接收資料
printf("recv_buf ========== %s\n", recv_buf);
}
close(sockfd);
return 0;
}
(3)實驗結果:
上面就為客戶端和服務端結果的截圖,客戶端輸入資料,服務端將資料返回。
3. UDP客戶伺服器模型的實現
(1)實驗原理:
基於 UDP 協議建立通訊的客戶機和伺服器,不需要維持長期的連線。因此 UDP 伺服器在一個單執行緒中,以迴圈迭代的方式即可處理來自不同客戶機的業務需求。具體流程如上圖所示。
(2)實驗程式碼:
- udp_server.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
int listenfd = socket (AF_INET, SOCK_DGRAM, 0);
if (listenfd == -1)
{
perror ("socket");
exit (EXIT_FAILURE);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind (listenfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
{
perror ("bind");
exit (EXIT_FAILURE);
}
char buf[1024];
struct sockaddr_in addrcli = {};
socklen_t addrlen = sizeof (addrcli);
ssize_t rcvd = recvfrom (listenfd, buf, sizeof (buf), 0, (struct sockaddr*)&addrcli, &addrlen);
if (rcvd == -1)
{
perror ("recvfrom");
exit (EXIT_FAILURE);
}
buf[rcvd] = '\0';
printf ("客戶端說:%s\n", buf);
printf ("伺服器說:");
fgets(buf,sizeof(buf),stdin);
ssize_t sent = sendto (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0, (struct sockaddr*)&addrcli, sizeof (addrcli));
if (sent == -1)
{
perror ("send");
exit (EXIT_FAILURE);
}
if (close (listenfd) == -1)
{
perror ("close");
exit (EXIT_FAILURE);
}
return 0;
}
-
udp_client.c
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> int main() { int listenfd = socket (AF_INET, SOCK_DGRAM, 0); if (listenfd == -1) { perror ("socket"); exit (EXIT_FAILURE); } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons (8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); char buf[1024] = "你好,伺服器"; printf ("客戶端說:%s\n", buf); ssize_t sent = sendto (listenfd, buf, strlen (buf) * sizeof (buf[0]), 0, (struct sockaddr*)&addr, sizeof (addr)); if (sent == -1) { perror ("send"); exit (EXIT_FAILURE); } struct sockaddr_in addrser = {}; socklen_t addrlen = sizeof (addrser); ssize_t rcvd = recvfrom (listenfd, buf, sizeof (buf), 0, (struct sockaddr*)&addrser, &addrlen); if (rcvd == -1) { perror ("recvfrom"); exit (EXIT_FAILURE); } buf[rcvd] = '\0'; printf ("伺服器說:%s\n", buf); if (close (listenfd) == -1) { perror ("close"); exit (EXIT_FAILURE); } return 0; }
(3)實驗結果
四、實驗感悟:
通過本實驗,對tcp\udp有了更深一步的理解。剛接觸時,只知道tcp是有連線、有確認的,而udp是無連線、無確認的,對於怎麼具體實現,沒有做深入研究。藉助這次實驗,算是具體實現了一遍tcp和udp,挺有成就感的。
在tcp實驗過程中,發現關閉服務端後,緊接著再次執行服務端時,會出現端口占用的問題。查了一下,,這就是所謂的“四次揮手”。