Linux网络编程(TCP Socket编程步骤)

一、网络编程中 TCP(传输控制协议)
二、基于TCP的socket
三、流程图
四、API表格形式
五、TCP Socket 服务器API

六、TCP Socket 客户端API

七、TCP服务器
八、TCP客户端
九、“三次握手"和"四次挥手”

一、网络编程中 TCP(传输控制协议)

网络编程中,TCP(传输控制协议)是一种面向连接的协议,它提供了可靠的数据传输和流控制功能。以下是关于TCP的详细解释:

TCP(传输控制协议)

TCP是一种面向连接的协议,它提供了以下特性和功能:

  1. 可靠性:TCP确保数据可靠地到达目标,通过使用序号和确认机制来追踪数据包的状态。如果数据包丢失或损坏,TCP会重传数据,以确保数据的完整性。

  2. 有序传输:TCP保证数据包按照发送的顺序到达目标。即使数据包在传输过程中被重新排序,TCP也会将它们重新排序以确保按正确顺序传递给应用程序。

  3. 流控制:TCP使用窗口机制来控制数据的传输速率,以防止过多的数据拥塞网络。发送方根据接收方的能力来调整发送速度。

  4. 全双工通信:TCP允许双方同时发送和接收数据,实现了全双工通信,其中服务器和客户端可以同时进行数据交换。

  5. 连接建立和断开:TCP连接是通过三次握手建立的,确保了双方的可靠通信。连接断开是通过四次挥手来完成的,释放连接资源。

  6. 面向字节流:TCP是面向字节流的协议,它不关心应用程序发送的消息的边界。因此,应用程序必须使用自己的消息边界标志。

TCP连接的建立和断开

TCP连接的建立和断开是通过三次握手和四次挥手来完成的:

  • 三次握手:连接建立时,客户端首先向服务器发送一个SYN(同步)标志的数据包,表示请求建立连接。服务器接收到请求后,回应一个ACK(确认)标志和一个SYN标志的数据包,表示接受连接请求。最后,客户端再次发送一个ACK标志的数据包,连接就建立了。

  • 四次挥手:连接断开时,其中一方(通常是客户端)发送一个FIN(结束)标志的数据包,表示不再发送数据。接收方(服务器)回应一个ACK标志的数据包,表示已收到结束请求。然后,接收方发送一个FIN标志的数据包,客户端回应一个ACK标志的数据包,连接断开。

TCP编程

在编写TCP网络应用程序时,通常涉及以下步骤:

  1. 创建套接字:使用socket()函数创建一个TCP套接字。

  2. 绑定地址和端口:使用bind()函数将套接字绑定到一个本地地址和端口,通常是服务器的地址和端口。

  3. 监听连接请求:对于服务器,使用listen()函数开始监听传入的连接请求。

  4. 接受连接:服务器使用accept()函数接受客户端的连接请求,创建一个新的套接字用于与客户端通信。

  5. 数据传输:通过新创建的套接字进行数据传输,使用send()和recv()函数。

  6. 关闭连接:使用close()函数关闭套接字以终止连接。

TCP是一种强大的协议,适用于需要可靠数据传输的应用程序,如Web服务器、电子邮件传输、文件传输和远程登录。它确保数据的完整性和可靠性,但与之相关的开销会导致一些额外的延迟。因此,在设计网络应用程序时,需要权衡可靠性和性能的需求。

二、基于TCP的socket

服务器端程序:

① 创建一个socket,用函数socket()

② 绑定IP地址、端口等信息到socket上,用函数bind()

③ 设置允许的最大连接数,用函数listen()

④ 接收客户端上来的连接,用函数accept()

⑤ 收发数据,用函数send()和recv(),或者read()和write()

⑥ 关闭网络连接close()

客户端程序:

① 创建一个socket,用函数socket()

② 设置要连接的对方的IP地址和端口等属性

③ 连接服务器,用函数connect()

④ 收发数据,用函数send()和recv(),或read()和write()

⑤ 关闭网络连接close()

三、流程图

下面是一个简单的流程图,用于说明如何开发一个基本的Socket服务器和客户端,并使用对应的API。这个流程包括了创建、绑定、监听服务器套接字,以及客户端创建套接字、连接服务器,服务器接受连接和进行数据交换的步骤。
在这里插入图片描述
这个流程图显示了Socket服务器和客户端的基本步骤。服务器首先创建、绑定和监听套接字,然后等待客户端连接。一旦连接建立,服务器和客户端之间可以进行数据交换。在客户端方面,它创建一个套接字,连接到服务器,然后进行数据交换。最后,连接在双方都使用close()函数来关闭。

请注意,这只是一个简单的示例,真实的Socket编程可能会更复杂,涉及到错误处理、多线程或多进程处理、非阻塞套接字等方面。您可以根据具体需求进行更详细的开发和调试。

四、API表格形式

以下是一个以表格形式整理的Socket服务器和客户端开发步骤以及对应的API使用:

Socket 服务器(Socket Server)

步骤API 使用
1. 创建服务器套接字socket() 函数
2. 绑定服务器套接字到地址和端口bind() 函数
3. 监听传入的连接请求listen() 函数
4. 等待连接请求accept() 函数 (在循环中)
5. 连接已建立,处理数据交换使用 accept() 返回的新套接字
6. 服务器发送数据给客户端send() 函数
7. 服务器接收客户端发送的数据recv() 函数
8. 关闭连接close() 函数

Socket 客户端(Socket Client)

步骤API 使用
1. 创建客户端套接字socket() 函数
2. 连接服务器connect() 函数
3. 客户端发送数据给服务器send() 函数
4. 客户端接收服务器发送的数据recv() 函数
5. 关闭连接close() 函数

这个表格提供了更具结构的信息,展示了服务器和客户端的各个步骤以及相应的API函数。希望这可以帮助你更清晰地理解Socket编程的流程。

TCP(传输控制协议)是一种面向连接的协议,用于在网络上可靠地传输数据。以下是TCP套接字编程的基本流程:

  1. 创建套接字:使用socket()函数创建一个TCP套接字。

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("Socket creation failed");
        exit(1);
    }
    
    • AF_INET:表示使用IPv4地址族。
    • SOCK_STREAM:表示使用TCP协议。
  2. 绑定地址和端口:使用bind()函数将套接字绑定到本地地址和端口。

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT); // 端口号
    server_addr.sin_addr.s_addr = INADDR_ANY; // 本地地址
    
    if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("Binding failed");
        exit(1);
    }
    
    • PORT:指定服务器监听的端口号。
  3. 监听连接请求:使用listen()函数开始监听客户端连接请求。

    if (listen(sockfd, BACKLOG) < 0) {
        perror("Listening failed");
        exit(1);
    }
    
    • BACKLOG:定义等待连接队列的最大长度。
  4. 接受连接:使用accept()函数接受客户端连接请求并创建一个新的套接字。

    struct sockaddr_in client_addr;
    socklen_t addr_len = sizeof(client_addr);
    
    int new_sockfd = accept(sockfd, (struct sockaddr *)&client_addr, &addr_len);
    if (new_sockfd < 0) {
        perror("Accepting connection failed");
        exit(1);
    }
    
  5. 发送和接收数据:使用send()和recv()函数向客户端发送数据并接收客户端的数据。

    char buffer[MAX_BUFFER_SIZE];
    ssize_t bytes_received = recv(new_sockfd, buffer, MAX_BUFFER_SIZE, 0);
    if (bytes_received < 0) {
        perror("Receive failed");
        exit(1);
    }
    
    // 处理接收到的数据
    
    ssize_t bytes_sent = send(new_sockfd, "Hello, client!", strlen("Hello, client!"), 0);
    if (bytes_sent < 0) {
        perror("Send failed");
        exit(1);
    }
    
    • MAX_BUFFER_SIZE:定义接收缓冲区的大小。
  6. 关闭套接字:使用close()函数关闭TCP套接字。

    close(new_sockfd);
    close(sockfd);
    

这是一个简单的TCP服务器的流程,它绑定到指定的地址和端口,接受客户端连接,然后与客户端进行数据交换。TCP通信提供可靠性和流控制,适用于需要可靠传输的应用程序。

需要注意的是,TCP是一种面向连接的协议,因此在通信之前必须建立连接,然后进行数据传输。这使得TCP适用于需要可靠性保证的应用程序,如文件传输、电子邮件和Web服务。在TCP通信中,通信的两端是通过套接字建立的连接进行通信的,每个连接都有唯一的本地和远程地址,以保证数据的可靠传输。

五、TCP Socket 服务器API

1. socket函数 创建套接字(连接协议)

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

socket 函数是用于创建套接字的系统调用。

  • 功能

    • socket 函数用于创建一个套接字,套接字是用于在计算机网络中进行通信的端点。它允许应用程序通过网络协议(如TCP或UDP)发送和接收数据。
  • 参数

    1. domain(地址族):指定套接字的地址族或协议族。常见的地址族包括:

      • AF_INET:IPv4地址族,用于Internet协议。
      • AF_INET6:IPv6地址族,用于新一代的Internet协议。
      • AF_UNIX:UNIX本地套接字,用于同一台计算机上的进程间通信。
      • 其他地址族根据需要选择。
    2. type(套接字类型):指定套接字的类型,影响套接字的通信方式。常见的套接字类型包括:

      • SOCK_STREAM:流套接字,提供面向连接的、可靠的、基于字节流的通信,通常用于TCP协议。
      • SOCK_DGRAM:数据报套接字,提供无连接的、不可靠的通信,通常用于UDP协议。
      • SOCK_RAW:原始套接字,允许应用程序访问协议栈中的底层数据包,通常需要特殊权限。
    3. protocol(协议):指定使用的协议,通常为0以自动选择与地址族和套接字类型匹配的协议。在大多数情况下,可以将此参数设置为0。

  • 返回值

    • socket 函数成功时返回新创建的套接字的文件描述符(非负整数),用于后续的套接字操作。
    • 如果发生错误,函数返回-1,并设置全局变量 errno 以指示错误类型。
  • 协议选择

    • socket 函数的 domaintype 参数通常用于决定选择哪种协议。例如,AF_INETSOCK_STREAM 通常与TCP协议相关联,而 AF_INETSOCK_DGRAM 通常与UDP协议相关联。但在某些情况下,可以显式指定协议参数,例如:
      • protocol = IPPROTO_TCP:明确指定使用TCP协议。
      • protocol = IPPROTO_UDP:明确指定使用UDP协议。

这些信息有助于理解 socket 函数的功能、参数和返回值,以及如何选择适当的协议。根据应用程序的需求,可以选择不同的地址族、套接字类型和协议来创建不同类型的套接字。

2. 设置服务器地址结构 为套接字添加信息(IP和端口号)

2.1 sockaddr和sockaddr_in函数 套接字地址信息

struct sockaddrstruct sockaddr_in 是在IPv4网络编程中经常使用的两个结构体,用于表示套接字地址信息。下面是它们的详细解释:

struct sockaddr

  • struct sockaddr 是一个通用的套接字地址结构体,通常用于处理多种地址族(address family),包括IPv4和IPv6。
  • 它的定义如下:
    struct sockaddr {
        unsigned short sa_family;  // 地址族,通常为 AF_INET (IPv4) 或 AF_INET6 (IPv6)
        char sa_data[14];          // 地址数据,包含地址信息
    };
    
  • struct sockaddr 主要包含了地址族信息(sa_family)和地址数据(sa_data)。sa_data 是一个14字节的数组,其中包含了具体的地址信息。在使用 struct sockaddr 时,通常需要根据 sa_family 来确定如何解释 sa_data

struct sockaddr_in

  • struct sockaddr_in 是用于表示IPv4套接字地址的结构体。它是对 struct sockaddr 的扩展,专门用于IPv4地址。
  • 它的定义如下:
    struct sockaddr_in {
        short sin_family;           // 地址族,通常为 AF_INET
        unsigned short sin_port;    // 端口号
        struct in_addr sin_addr;    // IPv4地址
        char sin_zero[8];           // 未使用,通常置零
    };
    
  • struct sockaddr_in 包含了地址族信息(sin_family,通常为 AF_INET)、端口号(sin_port)、IPv4地址(sin_addr)和一个8字节的未使用字段(sin_zero)。
  • 这个结构体非常适用于IPv4套接字编程,因为它允许方便地表示IPv4地址和端口号。

在实际的IPv4套接字编程中,通常会使用 struct sockaddr_in 结构体来表示IPv4地址和端口,以及将其与套接字相关联。struct sockaddr 通常用于底层的网络编程,而 struct sockaddr_in 更常见于应用层网络编程,因为它更方便地表示IPv4地址和端口。

2.2. inet_aton和inet_ntoa函数 (地址转换)

inet_atoninet_ntoa 是两个函数,用于处理IPv4地址的转换。它们通常在网络编程中用于将字符串表示的IPv4地址和二进制形式的IPv4地址相互转换。

  1. inet_aton 函数把字符串形式的“192.168.1.123”转为网络能识别的格式

    • inet_aton 函数用于将一个字符串表示的IPv4地址转换为二进制形式的IPv4地址。
    • 函数原型如下:
      int inet_aton(const char *cp, struct in_addr *inp);
      
    • 参数:
      • cp:一个指向包含IPv4地址字符串的C字符串的指针。
      • inp:一个指向 struct in_addr 结构体的指针,用于存储转换后的二进制IPv4地址。
    • 返回值:
      • 如果转换成功,函数返回非零值(通常为1)。
      • 如果转换失败,函数返回0,表示输入的IPv4地址字符串无效。

    示例:

    struct in_addr addr;
    if (inet_aton("192.168.0.1", &addr)) {
        // 转换成功,addr 包含了二进制IPv4地址
    } else {
        // 转换失败,字符串无效
    }
    
  2. inet_ntoa 函数把网络格式的ip地址转为字符串形式

    • inet_ntoa 函数用于将一个二进制形式的IPv4地址转换为字符串表示的IPv4地址。
    • 函数原型如下:
      char *inet_ntoa(struct in_addr in);
      
    • 参数:
      • in:一个 struct in_addr 结构体,包含二进制IPv4地址。
    • 返回值:
      • 函数返回一个指向以NUL结尾的字符串的指针,其中包含了IPv4地址的字符串表示。
      • 注意:inet_ntoa 使用一个静态缓冲区来存储转换后的字符串,因此每次调用它都会覆盖前一次的结果,所以需要小心使用。

    示例:

    struct in_addr addr;
    addr.s_addr = htonl(INADDR_LOOPBACK);  // 使用本地回环地址 127.0.0.1
    char *ip_str = inet_ntoa(addr);
    printf("IPv4 address: %s\n", ip_str);  // 打印字符串表示的IPv4地址
    

这些函数在处理IPv4地址时非常有用,可以方便地进行字符串表示和二进制表示之间的转换。inet_aton 用于将IPv4地址字符串转换为二进制格式,而 inet_ntoa 用于将二进制格式的IPv4地址转换为字符串。

3. bind函数 绑定服务器套接字

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

bind 函数用于将一个套接字绑定到一个特定的地址和端口号,以便在该地址上监听传入的连接。下面是 bind 函数的详细信息:

  • 功能bind 函数用于将指定的套接字与特定的网络地址和端口绑定在一起,从而将套接字关联到指定的通信地址。这通常用于服务器端创建监听套接字。

  • 参数

    1. sockfd:要绑定的套接字的文件描述符。
    2. addr:一个指向 struct sockaddr 结构体的指针,其中包含了要绑定的地址信息。这个结构体的具体类型(struct sockaddr_instruct sockaddr_in6)取决于套接字的地址族(address family)。
    3. addrlenaddr 结构体的大小(以字节为单位)。
  • 返回值

    • 如果 bind 函数成功,它会返回0。
    • 如果发生错误,它会返回-1,并设置全局变量 errno 以指示错误类型。
  • 注意事项

    • 要成功地使用 bind,套接字必须事先创建,通常是使用 socket 函数创建的。
    • addr 结构体的内容应该与套接字的地址族和类型匹配。例如,如果使用 AF_INETSOCK_STREAM 创建的套接字,addr 应该是 struct sockaddr_in 类型,并包含IPv4地址和端口号。
    • 通常,服务器端在调用 bind 后还需要调用 listen 函数,以开始监听传入的连接请求。

bind 函数在服务器端网络编程中非常重要,因为它允许服务器指定要监听的地址和端口,以等待客户端的连接请求。通过将套接字绑定到特定地址和端口,服务器可以确保客户端可以找到并与之通信。

4. listen函数 服务器端创建(监听)套接字

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);

listen 函数用于在服务器端创建监听套接字,主要用于准备接受客户端的连接请求。它有以下功能:

  1. 功能:

    • listen 函数用于告知操作系统,套接字(通常是服务器套接字)已准备好接受客户端的连接请求。一旦调用 listen,套接字进入监听状态,可以接受连接请求。
    • listen 函数还通过参数 backlog 来指定了等待连接的最大队列长度。这个参数表示可以排队等待服务的客户端连接的数量上限,即在未连接队列中可以排队等待的连接请求数。
  2. 内核维护的两个队列:

    • 已连接队列(Completed Connection Queue):已经建立连接的客户端连接会进入这个队列,等待服务器接受。
    • 未连接队列(Incomplete Connection Queue):尚未完全建立连接的客户端请求会进入这个队列,等待服务器调用 accept 函数来接受连接。

    在未连接队列中,操作系统会保存客户端的连接请求,这些请求还未完成TCP的三次握手过程。这允许服务器先接受连接请求,然后再完成握手。

  3. TCP的三次握手:

    • 在服务器调用 listen 函数之后,它可以开始接受客户端连接请求。
    • 当客户端尝试连接到服务器时,它发送一个SYN(同步)请求,表示建立连接。这个请求进入未连接队列。
    • 服务器接受到这个请求后,向客户端发送一个SYN-ACK(同步-确认)响应,表示愿意建立连接。此时,连接请求从未连接队列移动到已连接队列。
    • 最后,客户端收到服务器的SYN-ACK响应后,发送一个ACK(确认),确认连接的建立。此时,TCP的三次握手完成,连接正式建立,可以开始数据传输。

listen 函数的作用是告知操作系统,服务器套接字已准备好接受客户端连接请求,并通过指定 backlog 参数来控制未连接队列中等待连接的客户端连接数上限。一旦连接请求到达服务器,它们将会排队等待在未连接队列中,直到服务器接受连接或达到 backlog 上限。

这个机制允许服务器在接受连接之前排队等待连接请求,同时处理其他任务。当连接完成三次握手后,连接进入已连接队列,服务器可以继续与客户端通信。

  1. 参数:
    • sockfd:这是一个已创建的套接字描述符,通常是一个服务器套接字,已经绑定到服务器地址和端口上,用于监听客户端的连接请求。
    • backlog:这是一个整数值,表示等待连接队列的最大长度,也就是可以排队等待服务的客户端连接的数量上限。这个参数决定了在未连接队列中能够排队的连接数上限。

总之,listen 函数告知操作系统,服务器套接字已准备好接受客户端连接请求,同时通过 backlog 参数来控制未连接队列中等待连接的客户端连接数上限。一旦连接请求到达服务器,它们将会排队等待在未连接队列中,直到服务器接受连接或达到 backlog 上限。

5. accept函数 接受客户端连接请求

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

accept 函数是用于接受客户端连接请求的套接字函数。它的功能、参数和返回值如下:

功能:

  • accept 函数用于从已连接队列中接受客户端连接请求,创建一个新的套接字,用于与客户端进行通信。一旦接受连接,服务器和客户端之间的数据传输就可以开始。

参数:

  • sockfd:这是一个监听套接字(通常是服务器套接字),它已经通过 socketbindlisten 函数准备好接受客户端连接。
  • addr:这是一个指向 struct sockaddr 类型的指针,用于存储客户端的地址信息。通常,可以将其设置为 NULL,表示不需要获取客户端地址。
  • addrlen:这是一个整数,表示 addr 结构体的长度,通常是 sizeof(struct sockaddr)

返回值:

  • 如果成功,accept 函数返回一个新的套接字描述符,该描述符用于与客户端通信。这个新套接字是一个专门用于连接到客户端的套接字,它是唯一的。
  • 如果发生错误,accept 返回 -1,并设置 errno 变量以指示错误的类型。

一旦 accept 函数成功,服务器可以使用返回的新套接字与客户端建立通信。这个新套接字是一个全双工的通信通道,允许数据在服务器和客户端之间双向传输。

通常,服务器会在循环中多次调用 accept 函数,以便接受多个客户端的连接请求。每次调用 accept 都会返回一个新的套接字,用于处理与一个客户端的通信。这使得服务器能够同时与多个客户端建立连接,并提供并发服务。

6. 数据收发

6.1 send函数 发送数据

send 函数是用于将数据从套接字发送到目标的函数。它通常用于网络编程中,将数据从客户端发送到服务器,或者从服务器发送数据给客户端。以下是 send 函数的详细信息:

函数原型:

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:

  • sockfd:这是一个已经建立连接的套接字,用于发送数据。
  • buf:这是一个指向包含要发送数据的缓冲区的指针。
  • len:这是要发送的数据的字节数。
  • flags:这是一个用于指定发送选项的整数。常见的选项包括 0(无选项)和 MSG_DONTWAIT(非阻塞发送)等。

返回值:

  • 如果成功,send 函数返回已发送数据的字节数,可以小于 len,表示未能发送完所有数据。
  • 如果发生错误,send 返回 -1,并设置 errno 变量以指示错误的类型。

功能:

  • send 函数用于将数据从套接字发送到目标。这个目标可以是另一台计算机上的套接字,也可以是同一台计算机上的本地套接字。发送的数据可以包含任何字节,无论是文本数据还是二进制数据。
  • send 函数通常是阻塞的,即它将等待直到所有数据发送完毕或发生错误。但可以通过 flags 参数将套接字设置为非阻塞模式,以使 send 函数在无法发送数据时立即返回。

在网络编程中,send 函数通常用于将数据发送到套接字,以便在服务器和客户端之间进行通信。客户端使用 send 函数将请求发送给服务器,服务器使用 send 函数将响应发送回客户端。这允许双方在网络上进行数据交换,实现各种通信和应用。

6.2 recv函数 接收数据

recv 函数是用于从套接字接收数据的函数。它通常用于网络编程中,从网络连接中读取数据。以下是 recv 函数的详细信息:

函数原型:

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数:

  • sockfd:这是一个已经建立连接的套接字,用于接收数据。
  • buf:这是一个指向存储接收数据的缓冲区的指针。
  • len:这是要接收的数据的最大字节数,通常是缓冲区的大小。
  • flags:这是一个用于指定接收选项的整数。常见的选项包括 0(无选项)和 MSG_WAITALL(阻塞接收,等待接收完整的数据包)等。

返回值:

  • 如果成功,recv 函数返回接收到的数据的字节数。可以小于 len,表示接收的数据不满足请求的字节数。
  • 如果连接已关闭,recv 返回 0,表示对端已关闭连接。
  • 如果发生错误,recv 返回 -1,并设置 errno 变量以指示错误的类型。

功能:

  • recv 函数用于从套接字接收数据,将数据从网络中读取到缓冲区中。这个数据可以是来自另一台计算机的数据,也可以是同一台计算机上的本地套接字的数据。接收的数据可以包含任何字节,无论是文本数据还是二进制数据。
  • recv 函数通常是阻塞的,即它将等待直到接收到数据或发生错误。但可以通过 flags 参数将套接字设置为非阻塞模式,以使 recv 函数在没有数据可接收时立即返回。

在网络编程中,recv 函数通常用于从套接字接收来自网络连接的数据。客户端使用 recv 函数从服务器接收响应数据,服务器使用 recv 函数从客户端接收请求数据。这允许双方在网络上进行数据交换,实现各种通信和应用。

六、TCP Socket 客户端API

6. connect函数 客户端连接主机

connect 函数是用于在客户端建立与服务器的连接的函数。它通常用于网络编程中,用于客户端套接字连接到服务器套接字。以下是 connect 函数的详细信息:

函数原型:

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

参数:

  • sockfd:这是客户端套接字的文件描述符,用于建立连接。
  • addr:这是一个指向服务器地址信息的结构体指针,通常是 struct sockaddr 的指针,其中包含了服务器的 IP 地址和端口号等信息。
  • addrlen:这是服务器地址信息的长度,通常是 sizeof(struct sockaddr)

返回值:

  • 如果成功,connect 函数返回 0,表示连接已建立。
  • 如果发生错误,connect 返回 -1,并设置 errno 变量以指示错误的类型。

功能:

  • connect 函数用于在客户端建立与服务器的连接。客户端将其套接字连接到服务器的套接字,以便进行数据交换。一旦连接建立,客户端和服务器之间可以相互发送和接收数据。
  • connect 函数通常是阻塞的,即它将等待直到连接成功或发生错误。连接成功后,客户端可以使用该套接字向服务器发送请求,并接收服务器的响应。

在网络编程中,connect 函数是客户端建立与服务器通信的第一步。客户端使用 connect 函数连接到服务器,然后可以使用 send 函数发送请求,使用 recv 函数接收响应。这允许客户端与服务器之间进行双向通信,实现各种网络应用。

七、TCP服务器

以下是一个简单的示例,演示如何使用Socket编程创建一个基本的TCP服务器和客户端,实现服务器向客户端发送消息并接收客户端的响应。这个示例使用C语言。

TCP socket server

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

int main() 
{
    int server_socket, client_socket;
    struct sockaddr_in server_addr, client_addr;
    socklen_t client_len = sizeof(client_addr);
    char message[] = "Hello, client!";
    char buffer[1024];

    // 创建服务器套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);

    if (server_socket == -1) {
        perror("Socket creation failed");
        exit(1);
    }

    // 设置服务器地址结构
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(12345);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    // 绑定服务器套接字
    if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("Binding failed");
        exit(1);
    }

    // 监听传入的连接请求
    if (listen(server_socket, 5) == 0) {
        printf("Listening for incoming connections...\n");
    } else {
        perror("Listening failed");
        exit(1);
    }

    // 接受客户端连接请求
    client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_len);

    if (client_socket < 0) {
        perror("Acceptance failed");
        exit(1);
    }

    // 从客户端接收数据
    recv(client_socket, buffer, sizeof(buffer), 0);
    printf("Client says: %s\n", buffer);

    // 向客户端发送数据
    send(client_socket, message, sizeof(message), 0);
    printf("Message sent to client.\n");

    // 关闭套接字
    close(server_socket);
    close(client_socket);

    return 0;
}

八、TCP客户端

TCP socket client

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

int main() 
{
    int client_socket;
    struct sockaddr_in server_addr;
    char message[] = "Hello, server!";
    char buffer[1024];

    // 创建客户端套接字
    client_socket = socket(AF_INET, SOCK_STREAM, 0);

    if (client_socket == -1) {
        perror("Socket creation failed");
        exit(1);
    }

    // 设置服务器地址结构
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(12345);
    server_addr.sin_addr.s_addr = INADDR_ANY;

    // 连接到服务器
    if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == 0) {
        printf("Connected to server.\n");
    } else {
        perror("Connection failed");
        exit(1);
    }

    // 向服务器发送数据
    send(client_socket, message, sizeof(message), 0);
    printf("Message sent to server.\n");

    // 从服务器接收数据
    recv(client_socket, buffer, sizeof(buffer), 0);
    printf("Server says: %s\n", buffer);

    // 关闭套接字
    close(client_socket);

    return 0;
}

这个示例演示了一个简单的TCP服务器和客户端,服务器向客户端发送消息并接收客户端的响应。你可以根据需要扩展和定制这些示例以满足特定的应用程序需求。请注意,示例中的错误处理较为简单,实际应用程序应包含更完善的错误处理。

九、“三次握手"和"四次挥手”

"三次握手"和"四次挥手"是TCP(Transmission Control Protocol,传输控制协议)连接的建立和终止过程中的关键步骤。这两个过程用于确保数据在客户端和服务器之间可靠地传输,并在连接结束时进行优雅的关闭。下面详细讲解这两个过程:

三次握手 (Three-Way Handshake)

三次握手是建立TCP连接的过程,它确保客户端和服务器之间的通信能够开始。以下是三次握手的步骤:

  1. 客户端发送同步序列号(SYN)包:客户端向服务器发送一个TCP包,其中包含一个随机的初始序列号(ISN)以及设置了SYN标志的包。SYN标志表示客户端请求建立连接。

  2. 服务器确认同步序列号(SYN-ACK)包:服务器收到客户端的SYN包后,确认收到,并向客户端发送一个带有自己的ISN以及设置了SYN和ACK标志的包。SYN标志表示服务器愿意建立连接,而ACK标志表示确认收到了客户端的请求。

  3. 客户端确认(ACK)包:客户端收到服务器的SYN-ACK包后,发送一个带有ACK标志的包,表示它也确认连接已建立。此时,客户端和服务器之间的连接已经建立,可以开始数据传输。

四次挥手 (Four-Way Handshake)

四次挥手是终止TCP连接的过程,它用于优雅地关闭连接,确保数据在关闭之前完全传输。以下是四次挥手的步骤:

  1. 客户端发送终止请求(FIN包):当客户端希望关闭连接时,它发送一个带有FIN标志的包,表示它已完成数据传输。

  2. 服务器确认(ACK包):服务器收到客户端的FIN包后,发送一个带有ACK标志的包,表示确认收到了终止请求。此时,服务器可以继续向客户端发送数据,但不再接受新的数据。

  3. 服务器发送终止请求(FIN包):当服务器完成数据传输后,它发送一个带有FIN标志的包,表示它也希望关闭连接。

  4. 客户端确认(ACK包):客户端收到服务器的FIN包后,发送一个带有ACK标志的包,表示确认收到了终止请求。此时,连接已完全关闭。

在四次挥手过程中,两端的终止请求和确认分别由FIN和ACK标志来完成。这确保了数据在连接关闭之前都得到了传输。三次握手和四次挥手是TCP连接的基本过程,用于建立和终止连接,以确保可靠的数据传输。

原因

TCP采用三次握手和四次挥手的机制是为了确保网络通信的可靠性和数据的完整性。下面是为什么采用这种方式的一些原因:

三次握手的原因:

  1. 双向确认: 通过三次握手,客户端和服务器都可以确认彼此的能力和可访问性。客户端发送SYN,服务器确认并发送SYN-ACK,客户端再次确认,确保双方都能够相互通信。

  2. 防止旧连接的问题: 如果之前的连接还在网络中存在,而客户端或服务器不知道,新的连接可能会受到旧连接的干扰。三次握手可以帮助识别旧连接是否存在,从而避免问题。

  3. 序列号的初始化: 通过三次握手,客户端和服务器可以交换初始的序列号,这对于后续数据包的有序传输非常重要。

四次挥手的原因:

  1. 确保数据完整性: 四次挥手允许双方确认彼此已经完成了数据传输。这是因为在关闭连接之前,可能还有数据在传输中。客户端和服务器可以相互通知对方,确保数据被完整传输。

  2. 防止数据丢失: 在关闭连接之前,可能还有数据在网络中传输。四次挥手允许客户端和服务器等待一段时间,以确保对方已经接收到最后的数据包。

  3. 保护连接状态: 在四次挥手中,客户端和服务器可以确认彼此已经关闭连接。这对于释放资源和维护网络状态非常重要。

总的来说,采用三次握手和四次挥手是为了确保数据的可靠传输和连接的正确建立和终止。这些机制有助于处理网络中的不确定性和问题,以提供可靠的通信服务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖喱年糕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值