socket

Socket 网络编程

什么是 Socket

Socket(套接字)是一种通信机制,应用程序通常通过它与另一台计算机进行通信。Socket 由两部分组成:一端称为客户端,另一端称为服务器端。客户端和服务器端通过 Socket 进行通信,客户端首先向服务器端发出连接请求,服务器端确认连接请求,然后双方建立连接。通信双方就可以通过 Socket 进行数据传输。

  • 来看看四层网络模型,被分为四层:应用层、传输层、网络层、物理层。

四层网络模型

而Socket的位置就在传输层,它位于应用层和传输层之间。

外显的来说,Socket在应用层的视角是一个正整数,另一端伸入传输层,Socket的整数会通过表被翻译成元组。比如TCP的Socket是一个四元组,记载了源目IP地址和源目端口,UDP对于的是一个二元组(为什么UDP只需要的是目的IP地址和目的端口?其实很直观,因为它只管发,不需要回复传回来,因此不需要表明自己的身份和位置)。

我们可以把应用层想成一个高层的房间,我们要将房间当中的各个物品送到其他楼中去。然而这个房间的地板就像是一个筛子,每个洞都有对应的一个正整数编号。从应用层的视角来说,我们就只需要把某个应用的信息丢进某个洞里面,然后等着从某个洞里面取出信息。

然后这一层的下一层,就好像一个个分筛机器,也就是传输层,里面会根据一个映射表,将这个洞对应的正整数翻译成为一个元组,或者说是一个键值对,其值为IP地址号和端口号(这里是为了保证应用层不至于每次都处理一个元组的信息,因为每个层都要各司其职)。传输层的工作就是将应用层的信息打包成一个个数据包,并将这些数据包送到网络层。

因此,对于UDP来说,一般的使用场景就是 DNS查询、音视频传输、游戏 等实时性要求强但可靠性要求弱的应用,即使有一点丢包和缺失,也不太会影响使用;而TCP则适合文件传输和网页这种需要可靠性的应用。

Socket 编程

注意一点:在socket当中,两个进程可以共用一个端口,但是一个进程只能绑定一个端口。

socketaddr_in结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13


struct sockaddr_in {

    short   sin_family;         // 地址族,AF_INET

    u_short sin_port;           // 端口号

    struct  sin_addr;           // IP地址

    char    sin_zero[8];        // 填充字节

};

host_ent结构体

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15


struct hostent {

    char  *h_name;             // 主机域名

    char **h_aliases;          // 主机别名列表

    short h_addrtype;          // 地址类型,AF_INET

    short h_length;            // 地址长度

    char **h_addr_list;        // IP地址列表

};

TCP Socket编程

当然以下都是伪代码

  1. 服务器端创建套接字

    1
    2
    3
    
    
    
    int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
  2. 服务器端绑定结构体和套接字: 绑定IP地址和端口(用于应用层和传输层)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    
    
    // 创建一个套接字地址结构体
    
    struct sockaddr_in server_addr;
    
    server_addr.sin_family = AF_INET;
    
    server_addr.sin_port = htons(8080);
    
    server_addr.sin_addr.s_addr = INADDR_ANY;
    
    bind(server_sockfd, server_addr);
    
  3. 服务器端accept套接字

    1
    2
    3
    
    
    
    connect_socket = accept(server_sockfd, client_addr);
    
  4. 客户端创建client套接字

    1
    2
    3
    
    
    
    int client_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
  5. 客户端绑定套接字和结构体: 绑定IP地址和端口(用于应用层和传输层)
    虽然这个步骤可以省略(因为服务器端的端口是常熟端口),但是为了区分客户端和服务器端,还是需要绑定IP地址和端口。

    1
    2
    3
    
    
    
    bind(client_sockfd, client_addr);
    
  6. 客户端连接自身套接字与服务器端套接字

    1
    2
    3
    
    
    
    connect(client_sockfd, server_addr);
    
  7. 客户端发送数据

    1
    2
    3
    
    
    
    client_sockfd = send(client_sockfd, send_buf);
    
  8. 服务器端读取数据

    1
    2
    3
    
    
    
    connect_socket = recv(server_sockfd, recv_buf);
    
  9. 关闭套接字

    1
    2
    3
    4
    5
    
    
    
    close(server_sockfd);
    
    close(client_sockfd);
    

UDP Socket编程

UDP Socket编程与TCP Socket编程基本相同,只是将SOCK_STREAM改为SOCK_DGRAM即可。

  1. 服务器端创建套接字

    1
    2
    3
    
    
    
    int server_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
  2. 服务器端绑定结构体和套接字: 绑定IP地址和端口(用于应用层和传输层)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    
    
    // 创建一个套接字地址结构体
    
    struct sockaddr_in server_addr;
    
    server_addr.sin_family = AF_INET;
    
    server_addr.sin_port = htons(8080);
    
    server_addr.sin_addr.s_addr = INADDR_ANY;
    
    bind(server_sockfd, server_addr);
    
  3. 客户端创建client套接字

    1
    2
    3
    
    
    
    int client_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    
  4. 客户端绑定套接字和结构体

    1
    2
    3
    
    
    
    bind(client_sockfd, client_addr);
    
  5. 客户端发送数据

    1
    2
    3
    
    
    
    client_sockfd = sendto(client_sockfd, send_buf, sizeof(send_buf), 0, server_addr);
    
  6. 服务器端读取数据

    1
    2
    3
    
    
    
    connect_socket = recvfrom(server_sockfd, recv_buf, sizeof(recv_buf), 0, client_addr);
    
  7. 关闭套接字

    1
    2
    3
    4
    5
    
    
    
    close(server_sockfd);
    
    close(client_sockfd);
    

Socket API (常见)

  1. socket() 创建套接字

    int socket(int domain, int type, int protocol);

    成功:返回指向新创建的socket的文件描述符 失败:返回-1

  2. bind() 绑定套接字,绑定IP地址和端口号

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    成功:返回0 失败:返回-1

  3. listen() 监听套接字状态

  4. accept() 接收连接请求

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    成功:返回指向新创建的socket的文件描述符 失败:返回-1

  5. connect() 调用以连接服务器

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

    成功:返回0 失败:返回-1

  6. send() 发送数据

  7. recv() 接收数据

  8. sendto() 发送数据到指定地址

  9. recvfrom() 接收数据来自指定地址

  10. close() 关闭套接字

If you have any questions, please contact me via the repo. Issues are welcome.
Built with Hugo
Theme Stack designed by Jimmy