1. 程式人生 > >網路程式設計實驗一——TCP、UDP網路程式設計

網路程式設計實驗一——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實驗過程中,發現關閉服務端後,緊接著再次執行服務端時,會出現端口占用的問題。查了一下,,這就是所謂的“四次揮手”。