Socket網絡編程:TCP連接與UDP連接

socket編程概述

Linux網絡編程實質上就是socket編程,IP地址可以確定一個主機,端口號可以確定主機中的一個進程,socket = IP + port(端口號)
這樣便確定了主機中運行的進程,網絡通信實質上就是進程通信。

我們知道連接分爲兩種,可靠的TCP連接與不可靠的UDP連接,下面分別通過這兩種連接來熟悉socket編程.

一.TCP通信

  • 服務端:socket -> bind -> listen -> while(1){ accept -> doing ...} -> close
  • 客戶端:socket -> connect -> while (1) {doing ...} ->close

服務端詳細流程

  1. int socket(int __domain, int __type, int __protocol)
  • domain 使用的協議族:AF_INET(Ipv4), AF_INET6(Ipv6)
  • type 代表連接的方式:SOCK_STREAM(TCP), SOCK_DGRAM(UDP)
  • protocol 協議,一般爲0

服務端建立socket。

  1. int bind(int __fd, const sockaddr *__addr, socklen_t __len)
  • fd 之前創建的套接字
  • addr 需要綁定的 地址族,IP地址, port號等, 一般使用struct sockaddr_in類型 進行綁定,然後再在bind函數進行類型強轉
  • len addr的大小

將已經創建好的socket進行綁定好 IP地址,端口號等,如果想要進行網絡通信,就需要通過這個socket。

  1. int listen(int __fd, int __n)
  • fd socket套接字
  • n 同時連接的客戶端的最大數量

在綁定好socket之後,開始進行監聽,注意監聽是非阻塞,只是說明服務端開始監聽socket

  1. int accept(int __fd, sockaddr *__restrict__ __addr, socklen_t *__restrict__ __addr_len)
  • fd socket套接字
  • addr 接收到的client信息結構體, 一般使用struct sockaddr_in 類型再強轉
  • addr_len client信息結構體的大小,注意是傳入地址

accept函數一般是阻塞狀態,用於接收連接的服務器,注意返回值是返回一個新的 newfd, 之後與服務器與客戶端通信就是通過這個套接字進行通信

注意:此時sockfd只是用於接收客戶端連接成功後的newfd套接字, 之後的通信都將在套接字newfd中進行, 也就是說,sockfd並不參與數據的交換

  1. close

close掉所有的newfd,sockfd

客戶端詳細流程

  1. int socket(int __domain, int __type, int __protocol)
  • domain 使用的協議族:AF_INET(Ipv4), AF_INET6(Ipv6)
  • type 代表連接的方式:SOCK_STREAM(TCP), SOCK_DGRAM(UDP)
  • protocol 協議,一般爲0
  1. int connect(int __fd, const sockaddr *__addr, socklen_t __len)
  • fd 之前創建的socket套接字
  • addr 需要綁定的 地址族,IP地址, port號等, 一般使用struct sockaddr_in類型 進行綁定,然後再在connect函數進行類型強轉
  • len addr的大小

這個connect與服務端的bind函數相對應,連接上服務端已經綁定好的socket

注意,在bind之前可能會遇到:bind: Address already in use 這種情況
出現的原因是綁定的套接字在close之後還需要等待TIME_WAIT一段時間才能夠進行重新綁定。
此時我們可以使用以下語句,讓套接字可以立刻再次使用:

int on = 1;
ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
ERROR_CHECK(ret, -1, "setsockopt");

案例:服務端與客戶端進行TCP通信

server:

int main()
{
    int ret;
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    ERROR_CHECK(sockfd, -1, "socket");
    int on = 1;
    ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    ERROR_CHECK(ret, -1, "setsockopt");
    struct sockaddr_in sockinfo;
    bzero(&sockinfo, sizeof(sockinfo));
    sockinfo.sin_addr.s_addr = inet_addr("172.21.0.7");
    sockinfo.sin_port = htons(2000);
    sockinfo.sin_family = AF_INET;
    ret = bind(sockfd, (sockaddr*)&sockinfo, sizeof(sockinfo));
    ERROR_CHECK(ret, -1, "bind");
    ret = listen(sockfd, 10);
    ERROR_CHECK(ret, -1, "listen");
    int newfd = accept(sockfd, NULL, NULL);
    ERROR_CHECK(ret, -1, "accept");
    
    char buf[100] = {0};
    recv(newfd, buf, sizeof(buf), 0);
    cout << "receiving from client:" << buf << endl;
    send(newfd, "world", 5, 0);
    
    close(sockfd);
    close(newfd);
    return 0;
}

client:


int main()
{
    int ret;
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    ERROR_CHECK(sockfd, -1, "socket");
    struct sockaddr_in sockinfo;
    bzero(&sockinfo, sizeof(sockinfo));
    sockinfo.sin_addr.s_addr = inet_addr("172.21.0.7");
    sockinfo.sin_port = htons(2000);
    sockinfo.sin_family = AF_INET;
    ret = connect(sockfd, (sockaddr *)&sockinfo, sizeof(sockinfo));
    ERROR_CHECK(ret, -1, "connect");

    send(sockfd, "hello", 5, 0);
    char buf[100] = {0};
    recv(sockfd, buf, sizeof(buf), 0);
    cout << "receiving from server:" << buf << endl;

    close(sockfd);
    return 0;
}

output:
在這裏插入圖片描述

二.UDP通信

udp是不可靠的連接方法,所以客戶端的流程與服務端的流程都比較簡單

  • 服務端:socket -> bind -> recvfrom/sendto

  • 客戶端:socket -> recvfrom/sendto

主要掌握以下兩個函數:

  1. recvfrom(..., sockaddr *__restrict__ __addr, socklen_t *__restrict__ __addr_len)

前面參數與recv 相同,addr結構體用於存放發送方的IP地址與端口號。

  1. sendto(..., const sockaddr *__addr, socklen_t __addr_len)

前面的參數與send相同,addr結構體用於存放目標地址IP與端口號


server:

int main()
{
    int ret;
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    ERROR_CHECK(sockfd, -1, "socket");
    struct sockaddr_in sockinfo;
    socklen_t len = sizeof(sockinfo);
    bzero(&sockinfo, sizeof(sockinfo));
    sockinfo.sin_addr.s_addr = inet_addr("172.21.0.7");
    sockinfo.sin_port = htons(2012);
    sockinfo.sin_family = AF_INET;
    ret = bind(sockfd, (sockaddr *)&sockinfo, len);
    ERROR_CHECK(ret, -1, "bind");

    char buf[100] = {0};
    struct sockaddr_in client;
    recvfrom(sockfd, buf, sizeof(buf), 0, (sockaddr*)&client, &len);
    cout << " client " << inet_ntoa(client.sin_addr) << " :" << ntohs(client.sin_port) << endl;
    cout << "receiving :" << buf << endl;
    sendto(sockfd, "world", 5, 0, (sockaddr*)&sockinfo, len);

    close(sockfd);
    return 0;
}

client:

int main()
{
    int ret;
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    ERROR_CHECK(sockfd, -1, "socket");
    struct sockaddr_in sockinfo;
    socklen_t len = sizeof(sockinfo);
    bzero(&sockinfo, sizeof(sockinfo));
    sockinfo.sin_addr.s_addr = inet_addr("172.21.0.7");
    sockinfo.sin_port = htons(2012);
    sockinfo.sin_family = AF_INET;

    sendto(sockfd, "hello", 5, 0, (sockaddr*)&sockinfo, len);
    char buf[100] = {0};
    recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
    cout << "receving from server:" << buf << endl;

    close(sockfd);
    return 0;
}

output:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章