基於Socket的UDP和TCP編程介紹


一、概述

1)TCP客戶—服務器程序設計基本框架

TCP的三次握手與四次揮手(詳解+動圖)

  1. UDP客戶—服務器程序設計基本框架流程圖

  2. UDP和TCP的對比:

從上面的流程圖比較我們可以很明顯的看出UDP沒有三次握手過程。

簡單點說。UDP處理的細節比TCP少。UDP不能保證消息被傳送到(它也報告消息沒有傳送到)目的地。UDP也不保證數據包的傳送順序。UDP把數據發出去后只能希望它能夠抵達目的地。

TCP優缺點:

優點:
1.TCP提供以認可的方式顯式地創建和終止連接。
2.TCP保證可靠的、順序的(數據包以發送的順序接收)以及不會重復的數據傳輸。
3.TCP處理流控制。
4.允許數據優先
5.如果數據沒有傳送到,則TCP套接口返回一個出錯狀態條件。
6.TCP通過保持連續並將數據塊分成更小的分片來處理大數據塊。—無需程序員知道

缺點: TCP在轉移數據時必須創建(並保持)一個連接。這個連接給通信進程增加了開銷,讓它比UDP速度要慢。

UDP優缺點:
1.UDP不要求保持一個連接
2.UDP沒有因接收方認可收到數據包(或者當數據包沒有正確抵達而自動重傳)而帶來的開銷。
3.設計UDP的目的是用於短應用和控制消息
4.在一個數據包連接一個數據包的基礎上,UDP要求的網絡帶寬比TDP更小。

二、Socket編程

Socket接口是TCP/IP網絡的API,Socket接口定義了許多函數或例程,程序員可以用它們來開發TCP/IP網絡上的應用程序。要學Internet上的TCP/IP網絡編程,必須理解Socket接口。

Socket接口設計者先是將接口放在Unix操作系統里面的。如果了解Unix系統的輸入和輸出的話,就很容易了解Socket了。網絡的Socket數據傳輸是一種特殊的I/O,Socket也是一種文件描述符。Socket也具有一個類似於打開文件的函數調用Socket(),該函數返回一個整型的Socket描述符,隨后的連接建立、數據傳輸等操作都是通過該Socket實現的。常用的Socket類型有兩種:流式Socket(SOCK_STREAM)和數據報式Socket(SOCK_DGRAM)。流式是一種面向連接的Socket,針對於面向連接的TCP服務應用;數據報式Socket是一種無連接的Socket,對應於無連接的UDP服務應用。

1、socket調用庫函數主要有:

創建套接字

   int  Socket(int af,int type,int protocol)                 -------------->  socket(ip地址類型,數據傳輸方式,傳輸協議)
  1. af 為地址族(Address Family),也就是 IP 地址類型,常用的有 AF_INET 和 AF_INET6。AF 是“Address Family”的簡寫,INET是“Inetnet”的簡寫。AF_INET 表示 IPv4 地址,例如 127.0.0.1;AF_INET6 表示 IPv6 地址,例如 1030::C9B4:FF12:48AA:1A2B。

127.0.0.1,它是一個特殊IP地址,表示本機地址,后面的教程會經常用到。
你也可以使用 PF 前綴,PF 是“Protocol Family”的簡寫,它和 AF 是一樣的。例如,PF_INET 等價於 AF_INET,PF_INET6 等價於 AF_INET6。
2) type 為數據傳輸方式/套接字類型,常用的有 SOCK_STREAM(流格式套接字/面向連接的套接字) 和 SOCK_DGRAM(數據報套接字/無連接的套接字),我們已經在《套接字有哪些類型》一節中進行了介紹。

  1. protocol 表示傳輸協議,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分別表示 TCP 傳輸協議和 UDP 傳輸協議。

建立地址和套接字的聯系 (服務器端要用 bind() 函數將套接字與特定的 IP 地址和端口綁定起來,只有這樣,流經該 IP 地址和端口的數據才能交給套接字處理)

   int bind(int sock, struct sockaddr *addr, socklen_t addrlen)                   -------------->bind(套接字標識符,結構體指針,結構體變量的大小)

sock 為 socket 文件描述符,addr 為 sockaddr 結構體變量的指針,addrlen 為 addr 變量的大小,可由 sizeof() 計算得出。


//創建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

//創建sockaddr_in結構體變量
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));  //每個字節都用0填充
serv_addr.sin_family = AF_INET;  //使用IPv4地址
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
serv_addr.sin_port = htons(1234);  //端口

//將套接字和IP、端口綁定
bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

bind()和connect()函數:綁定套接字並建立連接

服務器端偵聽客戶端的請求

對於服務器端程序,使用 bind() 綁定套接字后,還需要使用 listen() 函數讓套接字進入被動監聽狀態,再調用 accept() 函數,就可以隨時響應客戶端的請求了。
listen( Sockid ,quenlen) --------------> listen( 套接字標識符 ,請求隊列的長度)
sock 為需要進入監聽狀態的套接字,backlog 為請求隊列的最大長度。

建立服務器/客戶端的連接 (面向連接TCP)

    客戶端請求連接 
    Connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen)                     -------------->Connect(套接字標識符,結構體指針,結構體變量的大小)   

當套接字處於監聽狀態時,可以通過 accept() 函數來接收客戶端請求。
newsockid=accept(Sockid,Clientaddr, paddrlen) --------------> accept(套接字標識符,客戶端的結構體指針變量, 結構體變量長度)
它的參數與 listen() 和 connect() 是相同的:sock 為服務器端套接字,addr 為 sockaddr_in 結構體變量,addrlen 為參數 addr 的長度,可由 sizeof() 求得。
accept() 返回一個新的套接字來和客戶端通信,addr 保存了客戶端的IP地址和端口號,而 sock 是服務器端的套接字,大家注意區分。后面和客戶端通信時,要使用這個新生成的套接字,而不是原來服務器端的套接字。
最后需要說明的是:listen() 只是讓套接字進入監聽狀態,並沒有真正接收客戶端請求,listen() 后面的代碼會繼續執行,直到遇到 accept()。accept() 會阻塞程序執行(后面代碼不能被執行),直到有新的請求到來。

發送/接收數據

    面向連接:send(sockid, buff, bufflen) 
    recv( ) 
    面向無連接:sendto(sockid,buff,…,addrlen) 
    recvfrom( )

send()/recv()和write()/read():發送數據和接收數據

釋放套接字

    close(sockid)

基於socket的服務端與客戶端之間的相互通信:

服務端的代碼:
/* socket_tcp_server.cpp */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/errno.h>   ///errno的頭文件,不能用errno.h

#define MAX_MSG_SIZE   256

int main(int argc, char const *argv[])
{
    /* code */
    int server_socket_fd;//服務端套接字標識符
    int client_socket_fd;
    struct sockaddr_in server_addr;//服務端的信息
    struct sockaddr_in client_addr;//客戶端的信息
    char msg[MAX_MSG_SIZE] = {0};//消息緩沖區
    char sendmsg[MAX_MSG_SIZE] = {0};//發送數據
    /* 創建套節字 */
    server_socket_fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if (server_socket_fd < 0) {
        fprintf(stderr,"create socket ERROR:%s\n",strerror(errno));
        exit(1);
    }
    /* 將套接字和IP、端口綁定 */
    memset(&server_addr, 0, sizeof(server_addr));  //每個字節都用0初始化
    server_addr.sin_family = AF_INET;//使用ipv4地址
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
    server_addr.sin_port = htons(4321);  //端口
    bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));

    /* 創建套接字監聽隊列 */
    if (listen(server_socket_fd,10) <0 ) {//監聽隊列最大的長度為10
        fprintf(stderr,"listen ERROR:%s\n",strerror(errno));
        close(server_socket_fd);
        exit(1);
    }

    socklen_t client_addr_size = sizeof(client_addr);//客戶端信息的長度
    /* 接受客戶請求 */
    client_socket_fd = accept(server_socket_fd,(struct sockaddr*)&client_addr,&client_addr_size);
    if (client_socket_fd <= 0) {
        fprintf(stderr,"accept ERROR:%s\n",strerror(errno));
    }else {
        while (1)
        {
            /* 接收到客戶端的請求后,服務端開始為客戶端服務 */
            /* 傳數據到客戶端 */
            //strcpy(msg,"你好啊,客戶端老弟,我是服務端渣渣輝啊!!");
            printf("請輸入你要傳遞給客戶端的信息:\n");
            gets(sendmsg);
            //write(client_socket_fd,sendmsg,MAX_MSG_SIZE);
            send(client_socket_fd,sendmsg,MAX_MSG_SIZE,0);
            memset(sendmsg,0,MAX_MSG_SIZE);

            printf("我是服務端小賤賤:正在為您服務ing!!!!\n");
            //int read_count = read(client_socket_fd,msg,MAX_MSG_SIZE);
            int read_count = recv(client_socket_fd,msg,MAX_MSG_SIZE,0);
            if (read_count > 0) {
                printf("received a connection from %s\n", inet_ntoa(client_addr.sin_addr));
                printf("the message is :%s from the client\n",msg);
                memset(msg,0,MAX_MSG_SIZE);
            }
        }       
    }
    /* 關閉套接字 */
    close(client_socket_fd);
    close(server_socket_fd);
    return 0;
}

客戶端的代碼:
/* socket_tcp_client.cpp */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define DATA_MAX_SIZE 256

int main(int argc, char const *argv[])
{
    /* code */
    int client_socket_fd;
    int result;
    struct sockaddr_in server_addr;//服務端的信息

    /* 創建套節字 */
    client_socket_fd = socket(AF_INET,SOCK_STREAM,0);
    /* 將套接字和IP、端口綁定 */
    memset(&server_addr, 0, sizeof(server_addr));  //每個字節都用0初始化
    server_addr.sin_family = AF_INET;//使用ipv4地址
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  //具體的IP地址
    server_addr.sin_port = htons(4321);  //端口
    result = connect(client_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    if(result == -1)  
    {  
        perror("ops:client\n");  
        exit(1);  
    } 
    /* 讀取服務器數據 */
    char data[DATA_MAX_SIZE] = {0};//接受數據
    char sendmsg[DATA_MAX_SIZE] = {0};//發送數據
    while (1)
    {
        /* 接受數據 */
        //int read_count = read(client_socket_fd,data,DATA_MAX_SIZE);
        int read_count = recv(client_socket_fd,data,DATA_MAX_SIZE,0);
        if (read_count > 0 ) {
             printf("the message is :%s from the server\n",data);
        }
        /* 發送數據 */
        printf("請輸入你要發給服務端的數據:\n");
        gets(sendmsg);
        //sendmsg[DATA_MAX_SIZE-1] = '\0';
        //write(client_socket_fd,sendmsg,DATA_MAX_SIZE);
        send(client_socket_fd,sendmsg,DATA_MAX_SIZE,0);
    }   
    /* 關閉套接字 */
    close(client_socket_fd);
    return 0;
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM