网络编程:socket 通信流程

98 阅读3分钟

Server 端流程

  1. socket 创建
int socket(int domain, int type, int protocol);
serv_sock = socket(PF_INET, SOCK_STREAM, 0);
  1. 网络地址信息初始化
struct sockaddr_in serv_addr;
char * serv_ip = "211.217.6.7";
char * serv_port = "9190";
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(serv_ip);
serv_addr.sin_port = htons(atoi(serv_port));
//serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//serv_addr.sin_port = htons(atoi(argv[1]));

这里 IP 地址是写死的,实际可以从参数传入,或者使用 INADDR_ANY,可自动获取服务端 IP 地址。 3. 向套接字分配网络地址 把初始化的地址信息分配给套接字,由bind函数完成

int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen)
sockfd: 要分配地址信息的套接字
myaddr:存有地址信息的结构体变量地址值
addrlen:第二个结构体变量的长度

如果成功,将 myaddr 的地址信息分配给 sockfd

int serv_sock;
struct sockaddr_in serv_addr;
//初始化 server 的套接字
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1) {
        error_handling("socket() error");
    }
    
    //初始化 server 的网络地址信息
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));
    
    //绑定网络地址信息到 server 的套接字
    if (bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1) {
        error_handling("bind() error");
    }
  1. 进入等待连接请求状态 调用listen进入等待连接请求状态,只有调用后,客户端才能进入可发出连接请求的状态(调用connect函数),如果提前,会报错。
/**
  sock: 希望进入连接请求状态的套接字文件描述符
  backlog:连接请求等待队列的长度,若为5,表示最多使5个连接请求进入队列
*/
int listen(int sock, int backlog) __DARWIN_ALIAS(listen);

int serv_sock;
listen(serv_sock, 5);

服务器端进入等待连接请求状态是指,客户端请求连接时,受理连接前使请求一直处于等待状态。

20DEE120-7171-43F3-B1EF-B6447FC369F9.png 5. 受理客户端连接请求 受理请求意味着进入可收发数据的状态。

/**
  sock: 服务器端套接字的文件描述符
  addr: 保存发起连接请求的客户端地址信息的变量地址值,调用函数后向传递来的地址变量参数填充客户端地址信息
  addrlen: 第二个参数addr结构体的长度,但是存有长度的变量地址。
*/
int accept(int sock, struct sockaddr * addr, socklen_t * addrlen);

struct sockaddr_in clnt_addr;
clnt_addr_size = sizeof(clnt_addr);

clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

accept调用成功时,内部产生用于数据 I/O 的套接字自动与客户端套接字连接。接下来的数据传输接受通过该 socket 进行。

E9837FEB-B0CE-4742-B8F1-8B943B688982.png

客户端流程

  1. 创建套接字
int sock;
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  1. 客户端网络地址初始化 声明sockaddr_in 结构体,初始化为要连接的服务器端的 IP 和端口
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
//两个参数分别为 server 端的 IP 和端口号.
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
serv_addr.sin_port = htons(atoi(argv[2]));
  1. 请求连接,调用connect
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
        error_handling("connect() error");
    }

Connect在以下情况会返回

  • 服务端接受连接请求
  • 网络异常导致请求中断

接收连接不代表服务器调用了 accept 函数,只是请求进入了等待队列,connect 返回后不立即进入数据交换阶段。

客户端的 IP 和端口在调用connect时自动生成,IP 是客户端的主机 IP,端口随机。

基于 TCP 的服务端和客户端调用关系

5071D4D5-87B1-490D-8C7A-DF237C0915B9.png