歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

UNIX網絡編程:socket套接字(TCP與UDP)

套接字簡介:

套接字是網絡編程中的一種通信機制,是支持TCP/IP的網絡通信的基本操作單元,可以看做是不同主機之間的進程進行雙向通信的端點,簡單的說就是通信的兩方的一種約定,用套接字中的相關函數來完成通信過程。應用層通過傳輸層進行數據通信時,TCP和UDP會遇到同時為多個應用程序進程提供並發服務的問題。憑借這種機制,客戶/服務器系統的開發工作既可以在本地單機上進行,也可以跨網絡進行,Linux所提供的功能(如打印服務,ftp等)通常都是通過套接字來進行通信的,套接字的創建和使用與管道是有區別的,因為套接字明確地將客戶和服務器區分出來,套接字可以實現將多個客戶連接到一個服務器。

套接字創建流程以及所用到的函數:

首先socket通信中需要以下幾個結構體:

struct in_addr
{
        in_addr_t  s_addr;          //32位IPV4地址
}

struct sockaddr_in
{
       uint8_t         sin_len;           //結構體長度
       sa_family_t   sin_family;      //協議族
       in_port_t      sin_port;         //端口號
       struct in_addr   sin_addr;   //32位 IPV4地址
       char                   sin_zero[8]    //保留
}

1、創建套接字:
int socket(int domain, int type, int protocol); //使用前創建一個新的套接字

參數1(domain):選擇創建的套接字所用的協議族;常用的有以下選擇:
AF_INET : IPv4協議;
AF_INET6: IPv6協議;
AF_LOCAL: Unix域協議;
AF_ROUTE:路由套接口;
AF_KEY :密鑰套接口。
參數2(type):指定套接口類型,所選類型有:
SOCK_STREAM:字節流套接字;
SOCK_DGRAM : 數據報套接字;
SOCK_RAW : 原始套接口。
procotol: 使用的特定協議,一般使用默認協議(NULL)。
2、指定本地地址
int bind(int socket, const struct sockaddr *address, socklen_t address_len); //將套接字地址與所創建的套接字號聯系起來。
參數1(socket) : 是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
參數2(address):指向特定協議的地址指針。
參數3(address_len):上面地址結構的長度。
返回值:沒有錯誤,bind()返回0,否則SOCKET_ERROR。
3、建立套接字連接

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);        //客戶端請求連接
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);       //服務器接受連接工作

參數1(socket) : 是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
參數2(address):指向特定協議的地址指針。
參數3(addrlen):上面地址結構的長度。
返回值:沒有錯誤,bind()返回0,否則SOCKET_ERROR。
4、監聽連接
int listen(int sockfd, int backlog); //用於面向連接服務器,表明它願意接收連接。
參數1(sockfd):是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
參數2(backlog):所監聽的端口隊列大小。
5、數據傳輸
ssize_t send(int sockfd, const void *buf, size_t len, int flags); //發送數據
ssize_t recv(int sockfd, void *buf, size_t len, int flags); 接收數據

參數1(buf):指向存有傳輸數據的緩沖區的指針
參數2(len):緩沖區長度。
參數3(flags):flags的值或為0,或由下面常值中選擇一個:
MSGD_DONTROUTE:不查路由表 (send可選)
MSG_DONTWAIT : 本操作不阻塞 (send、recv均可選)
MSG_OOB : 發送或接收帶外數據。 (send、recv均可選)
MSG_PEEK : 查看外來的消息 (recv可選)
MSG_WAITALL : 等待所有數據 (recv可選)

6、關閉套接字
int close(int fd); //關閉套接字
參數(fd):是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。

TCP與UDP:
(TCP)傳輸控制協議,面向連接。是一種提供可靠數據傳輸的通用協議。
(UDP)用戶數據報協議,是一個面向無連接的協議。采用該協議不需要兩個應用程序先建立連接。UDP協議不提供差錯恢復,不能提供數據重傳,因此該協議傳輸數據安全性差。

TCP示現客戶—服務器程序:
TCP客戶—服務器程序的執行流程圖:
這裡寫圖片描述

TCP客戶—服務器程序代碼:
Ser.cpp:

#include
#include
#include
#include
#include
#include
#include
using namespace std;

#define SERVER_PORT  5050               //端口號
#define SERVER_IP    "192.168.3.254"    //服務器ip
#define QUEUE_SIZE   5                  //所監聽端口隊列大小

int main(int argc, char *argv[])
{
    //創建一個套接字,並檢測是否創建成功
    int sockSer;                        
    sockSer = socket(AF_INET, SOCK_STREAM, 0);
    if(sockSer == -1){
        perror("socket");
    }

    //設置端口可以重用,可以多個客戶端連接同一個端口,並檢測是否設置成功
    int yes = 1;
    if(setsockopt(sockSer, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
        perror("setsockopt");
    }

    struct sockaddr_in addrSer,addrCli;        //創建一個記錄地址信息的結構體
    addrSer.sin_family = AF_INET;              //所使用AF_INET協議族
    addrSer.sin_port = htons(SERVER_PORT);     //設置地址結構體中的端口號
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //設置其中的服務器ip

    //將套接字地址與所創建的套接字號聯系起來。並檢測是否綁定成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("bind");

    listen(sockSer, QUEUE_SIZE);       //監聽端口隊列是否由連接請求,如果有就將該端口設置位可連接狀態,等待服務器接收連接

    printf("Server Wait Client Accept......\n");
    //如果監聽到有連接請求接受連接請求。並檢測是否連接成功,成功返回0,否則返回-1
    int sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
    if(sockConn == -1)
        perror("accept");
    else
    {
        printf("Server Accept Client OK.\n");
        printf("Client IP:> %s\n", inet_ntoa(addrCli.sin_addr));
        printf("Client Port:> %d\n",ntohs(addrCli.sin_port));
    }

    char sendbuf[256];         //申請一個發送緩存區
    char recvbuf[256];         //申請一個接收緩存區
    while(1)
    {
        printf("Ser:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit",4) == 0)    //如果所要發送的數據為"quit",則直接退出。
            break;
        send(sockConn, sendbuf, strlen(sendbuf)+1, 0);   //發送數據
        recv(sockConn, recvbuf, 256, 0);    //接收客戶端發送的數據
        printf("Cli:> %s\n",recvbuf);
    }

    close(sockSer);         //關閉套接字
    return 0;
}

cli.cpp:

#include
#include
#include
#include
#include
#include
#include
using namespace std;

#define SERVER_PORT  5050
#define SERVER_IP    "192.168.3.254"

int main(int argc, char *argv[])
{
    //創建客戶端套接字號,並檢測是否創建成功
    int sockCli;
    sockCli = socket(AF_INET, SOCK_STREAM, 0);
    if(sockCli == -1)
        perror("socket");

    //創建一個地址信息結構體,並對其內容進行設置
    struct sockaddr_in addrSer;     
    addrSer.sin_family = AF_INET;         //使用AF_INET協議族
    addrSer.sin_port = htons(SERVER_PORT);  //設置端口號
    addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //設置服務器ip

    bind(sockCli,(struct sockaddr*)&addrCli, sizeof(struct sockaddr));    //將套接字地址與所創建的套接字號聯系起來

    //創建一個與服務器的連接,並檢測連接是否成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = connect(sockCli,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("connect");
    else
        printf("Client Connect Server OK.\n");

    char sendbuf[256];     //申請一個發送數據緩存區
    char recvbuf[256];     //申請一個接收數據緩存區
    while(1)
    {
        recv(sockCli, recvbuf, 256, 0);    //接收來自服務器的數據
        printf("Ser:> %s\n",recvbuf);
        printf("Cli:>");
        scanf("%s",sendbuf);
        if(strncmp(sendbuf,"quit", 4) == 0)    //如果客戶端發送的數據為"quit",則退出。
            break;
        send(sockCli, sendbuf, strlen(sendbuf)+1, 0);   //發送數據
    }
    close(sockCli);       //關閉套接字
    return 0;
}

運行結果:
這裡寫圖片描述

UDP實現客戶端—服務器通信:

這裡寫圖片描述

相對與TCP來說,UDP安全性差,面向無鏈接。所以UDP地實現少了連接與接收連接的操作。所以在收發數據時就不能再用send()和recvfrom()了,而是用sendto()和recvto()之名從哪收發數據。

sendto():
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
參數1(sockfd):是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
參數2(buf):指向存有發送數據的緩沖區的指針
參數3(len):緩沖區長度。
參數4(flags):flags的值或為0,或由下面常值中選擇一個:
MSGD_DONTROUTE:不查路由表 (send可選)
MSG_DONTWAIT : 本操作不阻塞 (send、recv均可選)
MSG_OOB : 發送或接收帶外數據。 (send、recv均可選)
MSG_PEEK : 查看外來的消息 (recv可選)
MSG_WAITALL : 等待所有數據 (recv可選)
參數5(dest_addr):指向目的地址的指針。
參數6(addrlen) : 目的地址的長度。

recvfrom():
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
參數1(sockfd):是由socket()調用返回的並且未作連接的套接字描述符(套接字號)。
參數2(buf):指向存有接收數據的緩沖區的指針
參數3(len):緩沖區長度。
參數4(flags):flags的值或為0,或由下面常值中選擇一個:
MSGD_DONTROUTE:不查路由表 (send可選)
MSG_DONTWAIT : 本操作不阻塞 (send、recv均可選)
MSG_OOB : 發送或接收帶外數據。 (send、recv均可選)
MSG_PEEK : 查看外來的消息 (recv可選)
MSG_WAITALL : 等待所有數據 (recv可選)
參數5(src_addr):指向源地址的指針。
參數6(addrlen) : 源地址的長度。

程序代碼:
ser.cpp:

#include
#include
#include
#include
#include
#include

int main()
{
    //創建一個套接字,並檢測是否創建成功
    int sockSer = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockSer == -1)
        perror("socket");

    struct sockaddr_in addrSer;  //創建一個記錄地址信息的結構體 
    addrSer.sin_family = AF_INET;    //使用AF_INET協議族 
    addrSer.sin_port = htons(5050);     //設置地址結構體中的端口號
    addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //設置通信ip

    //將套接字地址與所創建的套接字號聯系起來,並檢測是否綁定成功
    socklen_t addrlen = sizeof(struct sockaddr);
    int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
    if(res == -1)
        perror("bind");

    char sendbuf[256];    //申請一個發送數據緩存區
    char recvbuf[256];    //申請一個接收數據緩存區
    struct sockaddr_in addrCli;
    while(1)
    {
        recvfrom(sockSer,recvbuf,256,0,(struct  sockaddr*)&addrCli, &addrlen);     //從指定地址接收客戶端數據
        printf("Cli:>%s\n",recvbuf);

        printf("Ser:>");    
        scanf("%s",sendbuf);
        sendto(sockSer,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr*)&addrCli, addrlen);    //向客戶端發送數據
    }
    return 0;
}

cli.cpp:

#include
#include
#include
#include
#include
#include

int main()
{
    //創建一個套接字,並檢測是否創建成功
    int sockCli = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockCli == -1){
        perror("socket");
    }

    addrSer.sin_family = AF_INET;    //使用AF_INET協議族 
    addrSer.sin_port = htons(5050);     //設置地址結構體中的端口號
    addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //設置通信ip
    socklen_t addrlen = sizeof(struct sockaddr);


    char sendbuf[256];    //申請一個發送數據緩存區
    char recvbuf[256];    //申請一個接收數據緩存區

    while(1){
        //向客戶端發送數據
        printf("Cli:>");
        scanf("%s",sendbuf);
        sendto(sockCli, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr*)&addrSer, addrlen);   
        接收來自客戶端的數據
        recvfrom(sockCli, recvbuf, BUFFER_SIZE, 0, (struct sockaddr*)&addrSer, &addrlen);
        printf("Ser:>%s\n", recvbuf);

    }

    return 0;
}

運行結果:

這裡寫圖片描述

UDP的池先中沒有對客戶端與服務器所發送數據是否為“quit”進行判斷。判斷方法和TCP中的相同。可以將其完善。

Copyright © Linux教程網 All Rights Reserved