昨晚 Vv 讓我給她講講網絡編程,於是我就傻乎乎的帶她入了門...
以下內容為講課時制作的筆記~
1. socket() 函數
1.1 頭文件
#include<sys/socket.h>
1.2 函數參數
示例:int socket(int domain, int type, int protocol){...}
-
domain:設置協議域(協議族)
- AF_INET:IPV4
- AF_INET6:IPV6
- \(\cdots\)
協議族決定了 socket 的地址類型,在通信中必須采用對應類型的地址
-
type:指定 socket 類型
- SOCKET_STREAM:流式 socket,針對於面向連接的 TCP 服務應用
- SOCKET_DGRAM:數據報式 socket,針對於無連接的 UDP 服務應用
- \(\cdots\)
-
protocal:指定協議
- \(0\):自動選擇第二個參數類型對應的傳輸協議
- IPPROTO_TCP:TCP傳輸協議
- IPPROTO_UDP:UDP傳輸協議
- \(\cdots\)
type 和 protocal 不能隨意組合,如 SOCKET_STREAM 不能和 IPPROTO_UDP 組合
1.3 返回值
示例:int sock_fd = socket(AF_INET, SOCKET_DGRAM, 0);
-
sock_fd = -1
:套接字創建失敗 -
sock_fd = x(x >= 0)
:套接字創建成功,返回套接字的文件描述符(索引)套接字描述符是一個整數類型的值。每個進程的進程空間里都有一個套接字描述符表,該表中存放着套接字描述符和套接字數據結構的對應關系。該表中有一個字段存放新創建的套接字的描述符,另一個字段存放套接字數據結構的地址,因此根據套接字描述符就可以找到其對應的套接字數據結構。每個進程在自己的進程空間里都有一個套接字描述符表但是套接字數據結構都是在操作系統的內核緩沖里。
1.4 Socket是什么?
socket是對TCP/IP協議簇的封裝,它的出現只是使得程序員更方便地使用TCP/IP協議棧而已。socket本身並不是協議,它是應用層與TCP/IP協議族通信的中間軟件抽象層,是一組調用接口(TCP/IP網絡的API函數)
2. bind()函數
2.1 sockaddr
#include<arpa/inet.h>
struct sockaddr{
sa_family_t sin_family; // 協議族
char sa_data[14]; // 14 個字節,包含套接字中的目標地址和目標端口信息
};
2.2 sockaddr_in
#include<arpa/inet.h> // 或 #include<netinet/in.h>
struct in_addr{
In_addr_t s_addr; // 32位 IPv4 地址
};
struct sockaddr_in{
sa_family_t sin_family; // 協議族
uint16_t sin_port; // 16位 TCP/UDP 端口號 (端口號最大是 65535 = 2^16 - 1)
struct in_addr; // 32位 IP 地址
char sin_zero[8]; // 不使用 (為了讓sockaddr與sockaddr_in兩個數據結構保持大小相同而保留的空字節)
};
- sin_port 和 sin_addr 都必須是網絡字節序(NBO),一般可視化的數字都是主機字節序(HBO)。
- sockaddr_in 和 sockaddr 是並列的結構,指向 sockaddr_in 的結構體的指針也可以指向 sockadd 的結構體,並代替它。
2.3 函數參數
示例:int bind(sock_fd, const struct sockaddr* address, socklen_t address_len);
-
sock_fd:套接字描述符
-
address:sockaddr結構指針,該結構中包含了要綁定的地址和端口號
-
address_len:address緩沖區的長度
- socklen_t 即 unsigned int
- sizeof 的返回值也是 unsigned int
2.4 返回值
示例:
// 綁定 ip port
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9123); // htons 主機字節序轉網絡字節序
// 方法1:
// INADDR_ANY 是通配地址,即本機所有 ip 都綁定上。 INADDR_ANY 轉換過來就是0.0.0.0
inet_pton(AF_INET, INADDR_ANY, &addr.sin_addr.s_addr);
// 方法2:
// inet_addr()作用是將一個IP字符串轉化為一個網絡字節序的整數值,用於sockaddr_in.sin_addr.s_addr。
addr.sin_addr.s_addr = inet_addr("192.168.0.115");
int res = bind(sock_fd, (struct sockaddr *) &addr, sizeof(addr));
res = 0
:綁定成功res = -1
:綁定失敗
2.5 作用
將 addr 指向的 sockaddr 結構體中描述的一些屬性(IP地址、端口號、地址簇)與 socket 套接字綁定,也叫給套接字命名。
調用 bind() 后,就為 socket 套接字關聯了一個相應的地址與端口號,即發送到該地址該端口的數據可通過 socket 讀取和使用。當然也可通過該 socket 發送數據到指定目的。
對於Server,bind()是必須要做的事情,服務器啟動時需要綁定指定的端口來提供服務(以便於客戶向指定的端口發送請求),對於服務器 socket 綁定地址,一般而言將 IP 地址賦值為 INADDR_ANY(該宏值為0),即無論發送到系統中的哪個 IP 地址(當服務器有多張網卡時會有多個 IP 地址)的請求都采用該 socket 來處理,而無需指定固定 IP。
對於 Client,一般而言無需主動調用 bind(),一切由操作系統來完成。在發送數據前,操作系統會為套接字隨機分配一個可用的端口,同時將該套接字和本地地址信息綁定。
關於套接字更詳細的使用,可參考:https://github.com/qiyu56/network/tree/master/udp
3. sendto() 函數
3.1 函數參數
示例:int sendto(int sock_fd, const void *buf, int len, int flags, const struct sockaddr *address, socklen_t address_len);
-
sock_fd:套接字描述符
-
void *buf:UDP 數據報緩存區(包含待發送數據)
-
void* 指針可以指向任意類型的數據:
void *p; int *a; p = a; // a = (int *)p;
-
UDP 數據報緩存區:
- sendto 把數據放在 sendbuf(緩沖區),通知操作系統來取
- 操作系統在適當的時候過來取數據,並發到網絡
這意味着:存入數據和發送數據存在時間差(異步的),如果存入數據太快太多,緩沖區會滿。緩沖區滿的處理:
- 知道緩沖區有剩余空間(阻塞)
- 新發送的數據沒有存入緩沖區(直接丟掉)
丟包對 UDP 來說是很正常,在使用 UDP 時就應該允許丟包
-
-
len:UDP數據報的長度
-
flags:調用方式標志位(一般設置為 \(0\),先不掌握)
-
sockaddr *address:sockaddr結構指針,該結構中包含了要發送的地址和端口號
-
address_len:address緩沖區的長度
- socklen_t 即 unsigned int
- sizeof 的返回值也是 unsigned int
3.2 返回值
示例:
char buf[128] = "";
fgets(buf, sizeof(buf) , stdin);
int res = sendto(sock_fd , buf , strlen(buf) , 0, (struct sockaddr *) &server_addr, sizeof(server_addr));
res = x
:發送成功,\(x\) 為發送出去的字符數res = -1
:發送失敗
3.3 作用
把 UDP 數據報發給指定地址。
4. revcfrom() 函數
4.1 函數參數
示例:recvfrom(int socke_fd, const void *buf, int len, int flags, struct sockaddr *address, socklen_t *address_len)
-
sock_fd:套接字描述符
-
void *buf:UDP 數據報緩存區(包含所接收的數據)
-
UDP 數據報緩存區:
- 操作系統不停把從網絡上接收數據,緩存在 recvbuf(緩沖區) 里
- recvfrom從緩存區里接收數據
這意味着:不論你是否去取數據,操作總是把數據收下來存好,recfrom是從recvbuf里取走現成的數據,如果不及時取走,則緩沖區會滿。緩沖區滿的處理:
- 新的數據不被接收
- 刪除緩沖區里的現有的數據,存放新的數據。
-
-
len:UDP數據報的長度
-
flags:調用方式標志位(一般設置為 \(0\),先不掌握)
-
sockaddr *address:sockaddr結構指針,該結構中包含了發送方的地址和端口號(可以為 NULL)
-
address_len:socklen_t 指針,指向了 address 結構體的長度(可以為 NULL)
4.2 返回值
示例:
char buf[128] = "";
int recv_len = recvfrom(sock_fd, buf, sizeof(buf), 0, (struct sockaddr*)&client_addr, &client_len);
recv_len = x
:接收成功,\(x\) 為接收到的字符數res = -1
:接收失敗
4.3 作用
接收發送方的網絡數據。
5. 服務器代碼與客戶端代碼
Server.cpp
#include<bits/stdc++.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
#include<sys/types.h>
using namespace std;
int main(int argc , char *argv[]){
cout << "Server:\n";
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0) {
perror("socket 創建失敗");
return 0;
}
cout << "socket 創建成功!\n";
// 綁定 ip port
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9123);
// inet_pton(AF_INET, "192.168.0.111", &addr.sin_addr.s_addr);
addr.sin_addr.s_addr = inet_addr("192.168.0.115"); //INADDR_ANY 通配地址,即本機所有 ip 都綁定上。 INADDR_ANY 轉換過來就是0.0.0.0
int res = bind(sock_fd, (struct sockaddr *) &addr, sizeof(addr));
if(res < 0) {
perror("綁定失敗");
close(sock_fd);
return 0;
}
cout << "socket 綁定(命名)成功!\n";
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
while(1){
char buf[128] = "";
int recv_len = recvfrom(sock_fd, buf, sizeof(buf), 0, (struct sockaddr*)&client_addr, &client_len);
printf("來自 ip 地址為 %s 端口號為 %d 的信息:%s 信息的總長度為 %d\n" , inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buf, recv_len);
sendto(sock_fd, buf, recv_len, 0, (struct sockaddr*)&client_addr, sizeof(client_addr));
}
close(sock_fd);
return 0;
}
Client.cpp
#include<bits/stdc++.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
#include<sys/types.h>
using namespace std;
int main(int argc, char *argv[]){
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(9123); // 服務器端口
inet_pton(AF_INET, "192.168.0.115", &server_addr.sin_addr.s_addr);
int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(sock_fd < 0)
perror("");
while(1){
char buf[128] = "";
cin.getline(buf , sizeof(buf));
int res = sendto(sock_fd , buf , strlen(buf) , 0, (struct sockaddr *) &server_addr, sizeof(server_addr));
char read_buf[128] = "";
recvfrom(sock_fd, read_buf, sizeof(read_buf), 0, NULL, NULL);
printf("共發送 %d 個字符數\n" , res);
}
close(sock_fd);
return 0;
}