文章目錄
- 1. 預備知識
- 1.1 源IP地址和目的IP地址
- 1.2 認識埠號
- 1.3 理解 "埠號" 和 "行程ID"
- 1.4 源埠號和目的埠號
- 1.5 認識UDP協議
- 1.6 認識TCP協議
- 1.7 網路位元組序
- 2. socket編程介面
- 2.1 socket 常見API
- 2.1.1 創建
- 2.1.2 系結
- 2.1.3 關閉
- 2.2 sockaddr結構
- 2.2.1 sockaddr 結構
- 2.2.2 sockaddr_in 結構
- 2.2.3 in_addr 結構
- 2.2.4 小結
- 2.3 地址轉換函式
- 2.4 例子
- 3. 簡單的UDP網路程式
- 3.1 UDP的socket API詳解
- 3.1.1 發送資料
- 3.1.2 接收資料
- 3.2 客戶端程式
- 3.3 服務端程式
- 3.4 封裝的UDP
- 3.4.1 udp_socket.hpp
- 3.4.2 udp_server.cpp
- 3.4.3 udp_client.cpp
1. 預備知識
1.1 源IP地址和目的IP地址
源IP地址:指的就是發送資料包的那個電腦的IP地址,
目的IP地址:就是想要發送到的那個電腦的IP地址,
1.2 認識埠號
埠號(port)是傳輸層協議的內容,
- 埠號是一個2位元組16位的整數;
- 埠號用來標識一個行程, 告訴作業系統, 當前的這個資料要交給哪一個行程來處理;
- IP地址 + 埠號能夠標識網路上的某一臺主機的某一個行程;
- 一個埠號只能被一個行程占用,
1.3 理解 “埠號” 和 “行程ID”
系統編程的pid 表示唯一一個行程;此處我們的埠號也是唯一表示一個行程,
一個行程可以系結多個埠號;
一個埠號不能被多個行程系結,
1.4 源埠號和目的埠號
傳輸層協議(TCP和UDP)的資料段中有兩個埠號,分別叫做源埠號和目的埠號,就是在描述 “資料是誰發的,要發給誰”,
1.5 認識UDP協議
我們先對UDP(User Datagram Protocol 用戶資料報協議)有一個直觀的認識;后面再詳細討論,
- 傳輸層協議
- 無連接
- 不可靠傳輸
- 面向資料報
1.6 認識TCP協議
此處我們也是對TCP(Transmission Control Protocol 傳輸控制協議)有一個直觀的認識;后面再詳細討論,
- 傳輸層協議
- 有連接
- 可靠傳輸
- 面向位元組流
1.7 網路位元組序
記憶體中的多位元組資料相對于記憶體地址有大端和小端之分, 磁盤檔案中的多位元組資料相對于檔案中的偏移地址也有大端小端之分, 網路資料流同樣有大端小端之分,
那么如何定義網路資料流的地址呢?
- 發送主機通常將發送緩沖區中的資料按記憶體地址從低到高的順序發出;
- 接收主機把從網路上接到的位元組依次保存在接識訓沖區中,也是按記憶體地址從低到高的順序保存;
- 因此,網路資料流的地址應這樣規定:先發出的資料是低地址,后發出的資料是高地址;
- TCP/IP協議規定,網路資料流應采用大端位元組序,即低地址高位元組;
- 不管這臺主機是大端機還是小端機, 都會按照這個TCP/IP規定的網路位元組序來發送/接收資料;
- 如果當前發送主機是小端, 就需要先將資料轉成大端; 否則就忽略, 直接發送即可,

為使網路程式具有可移植性, 使同樣的C代碼在大端和小端計算機上編譯后都能正常運行, 可以呼叫以下庫函式做網路位元組序和主機位元組序的轉換,
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong); // 主機位元組序轉網路位元組序,轉化4位元組
uint16_t htons(uint16_t hostshort); // 主機位元組序轉網路位元組序,轉化2位元組
uint32_t ntohl(uint32_t netlong); // 網路位元組序轉主機位元組序,轉化4位元組
uint16_t ntohs(uint16_t netshort); // 網路位元組序轉主機位元組序,轉化2位元組
- h表示host, n表示network, l表示32位長整數, s表示16位短整數,
- 例如htonl表示將32位的長整數從主機位元組序轉換為網路位元組序, 例如將IP地址轉換后準備發送,
- 如果主機是小端位元組序, 這些函式將引數做相應的大小端轉換然后回傳;
- 如果主機是大端位元組序, 這些函式不做轉換, 將引數原封不動地回傳,
2. socket編程介面
2.1 socket 常見API
2.1.1 創建
// 創建 socket 檔案描述符 (TCP/UDP, 客戶端 + 服務器)
int socket(int domain, int type, int protocol);
引數:
domain:地址域 —> 網路層使用什么協議
AF_INET: ipv4版本的ip協議
AF_INET6: ipv6版本的ip協議
AF_UNIX: 域套接字
type:套接字的型別
SOCK_DGRAM: 用戶資料報套接字 —> 默認協議是UDP協議
SOCK_STREAM: 流式套接字 —> 默認的協議是TCP協議
protocol:協議
SOCK_DGRAM: IPPROTO_UDP(17)
SOCK_STREAM: IPPROTO_TCP(6)
也可以傳遞0,表示使用套接字的默認協議
回傳值:套接字描述符,本質上還是一個檔案描述符
- socket()打開一個網路通訊埠, 如果成功的話, 就像open()一樣回傳一個檔案描述符;
- 應用程式可以像讀寫檔案一樣用read/write在網路上收發資料;
- 如果socket()呼叫出錯則回傳-1;
- 對于IPv4, family引數指定為AF_INET;
- 對于TCP協議, type引數指定為SOCK_STREAM, 表示面向流的傳輸協議;
- protocol引數的介紹從略, 指定為0即可,
2.1.2 系結
// 系結埠號 (TCP/UDP, 服務器)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addr_len);
引數:
sockfd:套接字描述符
addr:地址資訊
addr_len:傳入結構體的真實位元組數量
- 服務器程式所監聽的網路地址和埠號通常是固定不變的, 客戶端程式得知服務器程式的地址和埠號后就可以向服務器發起連接;
- 服務器需要呼叫bind系結一個固定的網路地址和埠號;
- bind()成功回傳0, 失敗回傳-1;
- bind()的作用是將引數sockfd和addr系結在一起;
- struct sockaddr *是一個通用指標型別, addr引數實際上可以接受多種協議的sockaddr結構體, 而它們的長度各不相同, 所以需要引數addr_len指定結構體的長度,
注意系結ip的時候:
- 可以直接系結網卡對應的ip地址;
- 也可以系結0.0.0.0表示當前機器當中的任意網卡的ip地址;
- 系結127.0.0.1表示系結本地回環地址,只訪問本地網路協議堆疊(訪問自己,測驗地址),
2.1.3 關閉
// 關閉套接字
close(int sockfd);
引數:
sockfd:套接字描述符
2.2 sockaddr結構
socket API是一層抽象的網路編程介面, 適用于各種底層網路協議, 如IPv4、IPv6,然而, 各種網路協議的地址格式并不相同,

2.2.1 sockaddr 結構
//通用的結構體:
struct sockaddr
{
sa_family_t sa_family; //地址型別, AF_xxx, 2位元組
char sa_data[14]; // 協議地址, 14位元組
};
2.2.2 sockaddr_in 結構
// AF_INET的結構體
struct sockaddr_in
{
sa_family_t sin_family; //地址型別, 2位元組
in_port_t sin_port; // 埠號, 2位元組
struct in_addr sin_addr; // IP地址, 4位元組
unsigned char sin_zero[8]; // 8位元組填充, 目的是與addr對齊
};
雖然socket api的介面是sockaddr, 但是我們真正在基于IPv4編程時, 使用的資料結構是sockaddr_in,
這個結構里主要有三部分資訊: 地址型別, 埠號, IP地址,
2.2.3 in_addr 結構
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
in_addr用來表示一個IPv4的IP地址,其實就是一個32位的整數,
2.2.4 小結
-
IPv4和IPv6的地址格式定義在netinet/in.h中, IPv4地址用sockaddr_in結構體表示, 包括16位地址型別, 16位埠號和32位IP地址,
-
IPv4、IPv6地址型別分別定義為常數AF_INET、AF_INET6. 這樣, 只要取得某種sockaddr結構體的首地址, 不需要知道具體是哪種型別的sockaddr結構體, 就可以根據地址型別欄位確定結構體中的內容,
-
socket API可以都用struct sockaddr *型別表示, 在使用的時候需要強制轉化成sockaddr_in; 這樣的好處是程式的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各種型別的sockaddr結構體指標做為引數,
2.3 地址轉換函式
基于IPv4的socket網路編程,sockaddr_in中的成員struct in_addr sin_addr表示32位 的IP 地址,但是我們通常用點分十進制的字串表示IP 地址,以下函式可以在字串表示 和in_addr表示之間轉換,
// 點分十進制轉網路二進制
#include <netinet/in.h>
in_addr_t inet_addr(const char *cp);
inet addr()函式的作用是:將Internet主機地址從IPv4數字和點符號轉換為按網路位元組順序的二進制資料,如果輸入無效,則回傳INADDR_NONE(通常為-1),
2.4 例子
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h> // 套接字
#include <netinet/in.h> // IP地址——> 網路位元組序
#include <arpa/inet.h> // 主機 ——> 網路位元組序
#include <iostream>
int main()
{
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//地址資訊
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(18989);
//私網IP:172.17.0.12
//1. 將點分十進制的IP地址轉化成無符號的4位元組的整數
//2. 將該整數轉化成為網路位元組序(二進制)
addr.sin_addr.s_addr = inet_addr("172.17.0.12");
// (struct sockaddr*)&addr 將addr_in強轉為addr
int ret = bind(sockfd, (sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return -1;
}
while(1)
{
sleep(1);
}
return 0;
}
3. 簡單的UDP網路程式

3.1 UDP的socket API詳解
3.1.1 發送資料
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
引數:
sockfd: 套接字描述符
buf: 要發送的內容
len: 發送資料的長度
flags: 0 表示阻塞發送
dest_addr: 要將資料發送到哪里去,對端的地址資訊
addr_len: 地址資訊的長度
3.1.2 接收資料
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addr1en) ;
引數:
sockfd: 套接字描述符
buf: 接收資料準備的緩沖區
len: 緩沖區接收的最大能力
flags: 0 表示阻塞接收
src_addr: 訊息發送端的地址資訊,接識訓來的訊息是從哪里來的
addrlen: 輸入輸出型引數,回傳的就是地址資訊的真實長度
3.2 客戶端程式
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <iostream>
int main()
{
//1.創建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
perror("socket");
return -1;
}
while(1)
{
//2.發送資料
char buf[1024] = { 0 };
printf("please enter message: ");
std::cin >> buf;
// 對端(服務端)的地址資訊
struct sockaddr_in svr_addr;
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(19999);
svr_addr.sin_addr.s_addr = inet_addr("172.17.0.12");
socklen_t svr_len = sizeof(svr_addr);
ssize_t send_size = sendto(sockfd, buf, strlen(buf), 0, (sockaddr*)&svr_addr, svr_len);
if(send_size < 0)
{
perror("sendto");
return -1;
}
//3.接收應答
memset(buf, '\0', sizeof(buf));
ssize_t recv_size = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, NULL, NULL);
if(recv_size < 0)
{
perror("recvfrom");
return -1;
}
//4.列印應答
printf("server say: %s\n", buf);
}
close(sockfd);
return 0;
}
3.3 服務端程式
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <iostream>
int main()
{
//1.創建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sockfd < 0)
{
perror("socket");
return -1;
}
//2.系結埠
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(19999);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return -1;
}
while(1)
{
//3.接收
char buf[1024] = { 0 };
// 對端(客戶端)的地址資訊
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
ssize_t recv_size = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (sockaddr*)&cli_addr, &cli_len);
if(recv_size < 0)
{
perror("recvfrom");
return -1;
}
//4.處理
printf("client say: %s\n", buf);
printf("please enter respond: ");
//5.回復應答
fflush(stdout); // 強制重繪標準輸出
std::cin >> buf;
ssize_t send_size = sendto(sockfd, buf, strlen(buf), 0, (sockaddr*)&cli_addr, cli_len);
if(send_size < 0)
{
perror("sendto");
return -1;
}
}
close(sockfd);
return 0;
}
3.4 封裝的UDP
3.4.1 udp_socket.hpp
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <string>
#include <iostream>
class UdpApi
{
public:
UdpApi()
{
sockfd_ = -1;
}
// 創建套接字
int CreateSocket()
{
sockfd_ = socket(AF_INET, SOCK_DGRAM, 17);
if(sockfd_ < 0)
{
perror("socket");
return -1;
}
return 0;
}
// 系結地址資訊
int Bind(std::string ip, uint16_t port)
{
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip.c_str());
int ret = bind(sockfd_, (sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return -1;
}
return 0;
}
// 發送資料
int SendData(std::string data, struct sockaddr_in* addr, socklen_t addrlen)
{
ssize_t send_size = sendto(sockfd_, data.c_str(), data.size(), 0, (sockaddr*)addr, addrlen);
if(send_size < 0)
{
perror("sendto");
return -1;
}
return send_size;
}
// 接收資料
int RecvData(std::string* data, struct sockaddr_in* addr, socklen_t* addrlen)
{
char buf[1024] = { 0 };
ssize_t recv_size = recvfrom(sockfd_, buf, sizeof(buf) - 1, 0, (sockaddr*)addr, addrlen);
if(recv_size < 0)
{
perror("recvfrom");
return -1;
}
data->assign(buf, strlen(buf));
return recv_size;
}
void Close()
{
close(sockfd_);
}
private:
int sockfd_;
};
3.4.2 udp_server.cpp
#include "udp_socket.hpp"
#define CHECK_ERT(p) if(p < 0){return -1;}
int main()
{
UdpApi ua; // 服務端的ua
CHECK_ERT(ua.CreateSocket());
CHECK_ERT(ua.Bind("0.0.0.0", 19999));
while(1)
{
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
std::string data;
ua.RecvData(&data, &cli_addr, &cli_len);
printf("client say: %s\n", data.c_str());
data.clear();
printf("please enter respond: ");
fflush(stdout);
std::cin >> data;
ua.SendData(data, &cli_addr, cli_len);
}
ua.Close();
return 0;
}
3.4.3 udp_client.cpp
#include "udp_socket.hpp"
#include <stdlib.h>
#define CHECK_ERT(p) if(p < 0){return -1;}
int main(int argc, char* argv[])
{
// ./udp_cli_package -ip [svr_ip] -port [svr_port]
if(argc != 5)
{
printf("./udp_cli_package -ip [svr_ip] -port [svr_port]\n");
return -1;
}
std::string svr_ip;
uint16_t svr_port;
for(int i = 0; i < argc; i++)
{
if(strcmp(argv[i], "-ip") == 0)
{
svr_ip = argv[i + 1];
}
else if(strcmp(argv[i], "-port") == 0)
{
svr_port = atoi(argv[i + 1]); //char* -> int
}
}
UdpApi ua; // 客戶端的ua
CHECK_ERT(ua.CreateSocket());
while(1)
{
struct sockaddr_in svr_addr;
svr_addr.sin_family = AF_INET;
svr_addr.sin_port = htons(svr_port);
svr_addr.sin_addr.s_addr = inet_addr(svr_ip.c_str());
std::string data;
printf("please input message: ");
fflush(stdout);
std::cin >> data;
ua.SendData(data, &svr_addr, sizeof(svr_addr));
data.clear();
ua.RecvData(&data, NULL, NULL);
printf("svr say:%s\n", data.c_str());
}
ua.Close();
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/240846.html
標籤:其他
