Socket是网络编程的一个抽象概念,它是对TCP/IP协议的封装,提供了一组接口,使得程序员可以更方便地使用网络功能。

Socket的原理

Socket的原理是基于TCP/IP协议的,它通过TCP/IP协议来传输数据。TCP/IP协议是一种网络通信协议,它定义了计算机之间如何进行通信。

TCP

TCP(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。它提供了一种可靠的、有序的、无重复的数据传输方式。TCP协议通过三次握手建立连接,通过四次挥手断开连接。在数据传输过程中,TCP协议会进行流量控制和拥塞控制,以保证数据的可靠传输。

三次握手:

  • 客户端向服务器发送一个SYN包,表示请求建立连接。
  • 服务器收到SYN包后,向客户端发送一个SYN-ACK包,表示同意建立连接。
  • 客户端收到SYN-ACK包后,向服务器发送一个ACK包,表示确认建立连接。

四次挥手:

  • 客户端向服务器发送一个FIN包,表示请求断开连接。
  • 服务器收到FIN包后,向客户端发送一个ACK包,表示确认断开连接。
  • 服务器向客户端发送一个FIN包,表示请求断开连接。
  • 客户端收到FIN包后,向服务器发送一个ACK包,表示确认断开连接。

Socket的使用

服务器端通信流程

  1. 创建用于监听的套接字, 这个套接字是一个文件描述符:

int lfd = socket( AF_INET, SOCK_STREAM, 0 );

socket接受三个参数,分别是地址族套接字类型协议类型
AF_INET表示使用IPv4协议,SOCK_STREAM表示使用TCP协议,0表示使用默认协议。

  1. 绑定套接字到指定的IP地址和端口号:
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 使用本地IP地址
serv_addr.sin_port = htons(8888); // 使用指定的端口号

bind( lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr) );

bind接受三个参数,分别是套接字描述符地址结构体地址结构体的大小。

  1. 监听套接字:
listen( lfd, 128 );

listen接受两个参数,分别是套接字描述符监听队列的长度

  1. 接受客户端连接:
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
int cfd = accept( lfd, (struct sockaddr*)&cli_addr, &cli_len );

accept接受三个参数,分别是套接字描述符客户端地址结构体客户端地址结构体的大小。

  1. 读取客户端发送的数据:
char buf[1024];
int n = read( cfd, buf, sizeof(buf) );

read接受三个参数,分别是套接字描述符缓冲区缓冲区大小

  1. 发送数据给客户端:
write( cfd, "hello world", 11 );

write接受三个参数,分别是套接字描述符数据数据大小

  1. 关闭套接字:
close( lfd );// 关闭监听套接字
close( cfd );// 关闭客户端套接字

close接受一个参数,即套接字描述符

服务端程序的完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main()
{
    // 1. 创建用于监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd == -1)
    {
        perror("socket");
        exit(1);
    }

    // 2. 绑定套接字到指定的IP地址和端口号
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(8888);

    if (bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
    {
        perror("bind");
        exit(1);
    }

    // 3. 监听套接字
    if (listen(lfd, 128) == -1)
    {
        perror("listen");
        exit(1);
    }

    // 4. 接受客户端连接
    struct sockaddr_in cli_addr;
    socklen_t cli_len = sizeof(cli_addr);
    int cfd = accept(lfd, (struct sockaddr*)&cli_addr, &cli_len);
    if (cfd == -1)
    {
        perror("accept");
        exit(1);
    }

    // 5. 读取客户端发送的数据
    char buf[1024];
    int n = read(cfd, buf, sizeof(buf));
    if (n == -1)
    {
        perror("read");
        exit(1);
    }
    printf("recv buf: %s\n", buf);

    // 6. 发送数据给客户端
    if (write(cfd, "hello world", 11) == -1)
    {
        perror("write");
        exit(1);
    }

    // 7. 关闭套接字
    close(lfd);
    close(cfd);

    return 0;
}

客户端通信流程

  1. 创建用于连接的套接字:
int cfd = socket( AF_INET, SOCK_STREAM, 0 );
  1. 连接服务器:
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 使用服务器的IP地址
serv_addr.sin_port = htons(8888); // 使用指定的端口号

connect( cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr) );
  1. 发送数据给服务器:
write( cfd, "hello world", 11 );
  1. 读取服务器发送的数据:
char buf[1024];
int n = read( cfd, buf, sizeof(buf) );
  1. 关闭套接字:
close( cfd );

客户端程序的完整代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

int main()
{
    // 1. 创建用于连接的套接字
    int cfd = socket(AF_INET, SOCK_STREAM, 0);
    if (cfd == -1)
    {
        perror("socket");
        exit(1);
    }

    // 2. 连接服务器
    struct sockaddr_in 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);

    if (connect(cfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
    {
        perror("connect");
        exit(1);
    }

    // 3. 发送数据给服务器
    if (write(cfd, "hello world", 11) == -1)
    {
        perror("write");
        exit(1);
    }

    // 4. 读取服务器发送的数据
    char buf[1024];

    int n = read(cfd, buf, sizeof(buf));
    if (n == -1)
    {
        perror("read");
        exit(1);
    }
    printf("recv buf: %s\n", buf);

    // 5. 关闭套接字
    close(cfd);

    return 0;
}