Ch3nyang's blog

home

home

person

about

hive

project

collections_bookmark

mindclip

rss_feed

rss

socket 编程

calendar_month 2024-06
archive 网络
tag socket tag network tag c tag cpp

从 socket 谈起

在 Linux 一切皆文件的思想下,网络连接也只是一个文件描述符,被称为 socket。socket 就像是一个一个传送门,只要把另一个传送门的参数告诉它,它就会把你的消息传送过去。

socket 的中文翻译是“套接字”,但这个翻译实在和“鲁棒性”卧龙凤雏,所以还是用英文为妙。

C++ 提供了 socket 函数(sys/socket.h),让我们定义这样一个传送门。

extern int socket (int __domain, int __type, int __protocol) __THROW;

这里的 socket 函数接收了 3 个参数:

  • __domainbits.socket.h):地址类型。常用的包括 IPv4 地址 AF_INET、IPv6 地址 AF_INET6、本地通信 AF_UNIX 等;
  • __typebits/socket_type.h):数据传输方式。常用的包括有序可靠传输 SOCK_STREAM、无连接不可靠传输 SOCK_DGRAM 等;
  • __protocolnetinet/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

Share This Post