从 socket 谈起
在 Linux 一切皆文件的思想下,网络连接也只是一个文件描述符,被称为 socket。socket 就像是一个一个传送门,只要把另一个传送门的参数告诉它,它就会把你的消息传送过去。
socket 的中文翻译是“套接字”,但这个翻译实在和“鲁棒性”卧龙凤雏,所以还是用英文为妙。
C++ 提供了 socket
函数(sys/socket.h
),让我们定义这样一个传送门。
extern int socket (int __domain, int __type, int __protocol) __THROW;
这里的 socket 函数接收了 3 个参数:
-
__domain
(bits.socket.h
):地址类型。常用的包括 IPv4 地址AF_INET
、IPv6 地址AF_INET6
、本地通信AF_UNIX
等; -
__type
(bits/socket_type.h
):数据传输方式。常用的包括有序可靠传输SOCK_STREAM
、无连接不可靠传输SOCK_DGRAM
等; -
__protocol
(netinet/in.h
):协议类型。常用的包括 TCP 连接IPPROTO_TCP
、UDP 连接IPPROTO_UDP
。如果设置为0
,则会自动选择。
这个函数返回一个整数:如果 socket 建立成功,则返回 socket 的文件描述符;否则返回 -1
。
例如,我们可以这样创建一个 IPv4 的 TCP socket:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
以上定义了 socket 的连接方式,我们还需要向其中传入具体参数。首先要传入自己的 IP 和端口等信息,这里用到 bind
函数(sys/socket.h
):
bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));
其中,serv_addr
是一个 sockaddr_in
类型(netinet/in.h
)的结构体,包含了 IP、端口等信息。我们可以像下面这样定义它:
struct sockaddr_in serv_addr;
bzero(&serv_addr, sizeof(serv_addr)); // 清零
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
serv_addr.sin_port = htons(8888);
在服务器上,服务器使用 listen
函数(sys/socket.h
)来监听连接:
listen(sockfd, SOMAXCONN);
其中,SOMAXCONN
是最大连接数量,默认情况下是 4096。
客户端可以使用 connect
去连接服务器:
connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr));
服务器使用 accept
函数同意连接:
int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len);
accept
函数是阻塞的。除非收到客户端的 socket,否则不会继续执行代码。
完整程序:
-
server.cc
#include <arpa/inet.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serv_addr; bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(8888); bind(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)); listen(sockfd, SOMAXCONN); struct sockaddr_in clnt_addr; socklen_t clnt_addr_len = sizeof(clnt_addr); bzero(&clnt_addr, sizeof(clnt_addr)); int clnt_sockfd = accept(sockfd, (sockaddr*)&clnt_addr, &clnt_addr_len); printf("New client %d! IP: %s Port: %d\n", clnt_sockfd, inet_ntoa(clnt_addr.sin_addr), ntohs(clnt_addr.sin_port)); return 0; }
-
client.cc
#include <arpa/inet.h> #include <string.h> #include <sys/socket.h> int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serv_addr; bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(8888); connect(sockfd, (sockaddr*)&serv_addr, sizeof(serv_addr)); return 0; }
-
Makefile
CXX = g++ CXXFLAGS = -std=c++20 -Wall -g SOURCES = $(wildcard *.cc) TARGETS = $(SOURCES:.cc=) .PHONY: clean all all: $(TARGETS) %: %.cc $(CXX) $(CXXFLAGS) -o $@ $^ clean: rm -f $(TARGETS)
然后编译运行:
$make
$./server &
$./client
New client 4! IP: 127.0.0.1 Port: 58236
Comments