2024-02-29-Linux高级网络编程(4-TCP编程)

4. TCP编程

4.1 TCP介绍

  1. 面向连接的流式协议;可靠、出错重传、且每收到一个数据都要给出相应的确认
  2. 通信之前需要建立链接
  3. 服务器被动链接,客户端是主动链接
image-20240229160146428

TCP编程流程

服务器:
1. 创建套接字 socket()
2. 将套接字与服务器网络信息结构体绑定 bind()
3. 将套接字设置为监听状态 listen()
4. 阻塞等待客户端的连接请求 accept()进行通信 recv()/send()
5. 关闭套接字 close()

客户端:
1. 创建套接字 socket()
2. 发送客户端连接请求 connect()
3. 进行通信 send()/recv()
4. 关闭套接字 close()

4.2 TCP编程-socket

#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type, int protocol);
功能:创建一个套接字,返回一个文件描述符;
参数:
	domain:通信域,协议族
		AF_UNIX		本地通信
		AF_INET		ipv4网络协议
		AF_INET6	ipv6网络协议
		AF_PACKET 	底层接口
	type:套接字的类型
		SOCK_STREAM 	流式套接字(tcp)
		SOCK_DGRAM		数据报套接字(udp)
         SOCK_RAW		原始套接字(用于链路层)
    protocol: 附加协议,如果不需要,则设置为0
 返回值:
        成功:文件描述符
        失败:-1
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    int socket_id;
    if ((socket_id = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("FAIL TO CREAET TCP SOCKET");
        exit(1);
    }
    printf("socket_id = %d \n", socket_id);
    return 0;
}

输出结果

socket_id = 3 

4.3 TCP客户端 -connect、send、recv

4.3.1 connect函数

#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
                   
功能:给服务器发送客户端的连接请求;
参数:
	sockfd:文件描述符,socket函数的返回值
	addr:要连接的服务器的网络信息结构体(需要自己设置);
	addrlen:add的长度返回值:
成功:0
失败:-1

4.3.2 send函数

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

功能:发送数据
参数:
	sockfd:文件描述符
		   客户端:socket函数的返回值;
		   服务器:accept函数的返回值;
    buf:发送的数据
    len:buf的长度
    flags:标志位
        0 阻塞
        MSG DONTWAIT 非阻塞
返回值:	
    成功:发送的字节数
    失败:-1

4.3.3 recv函数

#include <sys/types.h>
#include <sys/socket.h>
 ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能:接收数据
参数:	
	sockfd:文件描述符
		客户端:socket函数的返回值
		服务器:accept函数的返回值
    buf:保存接收到的数据
    len:buf的长度
    flags:标志位
			0 阻塞。
			MSG DONTWAIT 非阻塞
返回值:
	成功:接收的字节数
	失败:-1
注意:如果发送端关闭文件描述符或者关闭进程,则recv函数会返回0

4.4 TCP服务端- bind、listen、accept

4.4.1 bind函数

#include<sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将套接字与网络信息结构体绑定;
参数:
	sockfd:文件描述符,socket的返回值;
	addr:网络信息结构体,通用结构体(一般不用)struct sockaddr
		网络信息结构体 sockaddr_in
        	#include <netinet/in.h>
        	struct sockaddr_in
	addrlen:addr的长度
返回值:
    成功:0
    失败:-1

4.4.2 listen函数

#include <sys/socket.h>
int listen(intsockfd, int backlog)
功能: 将套接字由主动修改为被动
	使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接;
参数:
	sockfd: socket监听套接字
	backlog: 连接队列的长度,即同一时间允许客户端连接的个数;
返回值:
    成功:返回 0
    失败:其他

4.4.3 accept函数

#include <sys/types.h>         
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能:
	阻塞等待客户端的连接请求
参数:
	sockfd:文件描述符,socket函数的返回值;
	addr:接收到的客户端的信息结构体(自动填充,定义变量即可);
	addrlen:addr的长度
返回值:
	成功:新的文件描述符(只要有客户端连接,就会产生新的文件描述符; 这个新的文件描述符专门与指定的客户端进行通信的)
	失败:-1

4.5 close 函数、三次握手、四次挥手

4.5.1 close函数

  1. 使用 close 函数即可关闭套接字
    关闭一个代表已连接套接字将导致另一端接收到一个0长度的数据包
  2. 做服务器时
    1. 关闭监听套接字将导致服务器无法接收新的连接,但不会影响已经建立的连接
    2. 关闭 accept返回的已连接套接字将导致它所代表的连接被关闭,但不会影响服务器的监听
  3. 做客户端时
    关闭连接就是关闭连接,不意味着其他

4.5.2 三次握手

image-20240229172704257

4.5.3 四次挥手

image-20240229173029130

4.6 TCP并发服务器

TCP原本不是并发服务器,TCP服务器同一时间只能与一个客户端进行通信。

TCP不能实现并发的原因:TCP服务端有两个阻塞函数accept和recv,所以导致运行一个函数的时候,另一个函数无法执行;这样使得TCP无法保证一边连接客户端,一边与其他客户端通信。

那么,如何实现TCP并发服务器呢?

  1. 使用多进程实现TCP并发服务器
  2. 使用多线程实现TCP并发服务器

4.6.1 多进程实现并发

02_mulit_process_tcp_server.c

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

#define MAX_BUFFER_SIZE 1024
#define PORT 8080
#define MAX_CONNECTIONS 5

void handle_client(int client_socket) {
    char buffer[MAX_BUFFER_SIZE];
    ssize_t num_bytes;

    while ((num_bytes = recv(client_socket, buffer, MAX_BUFFER_SIZE, 0)) > 0) {
        buffer[num_bytes] = '\0';
        printf("Received message: %s\n", buffer);

        // 处理客户端请求,这里简单地将收到的消息原样发送回去
        send(client_socket, buffer, num_bytes, 0);
    }

    if (num_bytes == 0) {
        printf("Client disconnected\n");
    } else {
        perror("recv");
    }

    close(client_socket);
}

int main() {
    int server_socket, client_socket;
    struct sockaddr_in server_address, client_address;
    socklen_t client_address_size;
    pid_t child_pid;
    int status;

    // 创建套接字
    server_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (server_socket == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址和端口
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = INADDR_ANY;
    server_address.sin_port = htons(PORT);

    // 绑定套接字到指定地址和端口
    if (bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    // 监听连接请求
    if (listen(server_socket, MAX_CONNECTIONS) == -1) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    while (1) {
        // 接受客户端连接请求
        client_address_size = sizeof(client_address);
        client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_address_size);
        if (client_socket == -1) {
            perror("accept");
            exit(EXIT_FAILURE);
        }

        // 创建子进程来处理客户端请求
        child_pid = fork();
        if (child_pid == -1) {
            perror("fork");
            exit(EXIT_FAILURE);
        }

        if (child_pid == 0) {
            // 子进程
            close(server_socket);
            handle_client(client_socket);
            exit(EXIT_SUCCESS);
        } else {
            // 父进程
            close(client_socket);
            // 回收子进程资源,避免僵尸进程
            waitpid(-1, &status, WNOHANG);
        }
    }

    // 关闭服务器套接字
    close(server_socket);

    return 0;
}

03_multi_process_tcp_client.c

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

#define MAX_BUFFER_SIZE 1024
#define SERVER_IP "127.0.0.1"
#define PORT 8080

int main() {
    int client_socket;
    struct sockaddr_in server_address;
    char buffer[MAX_BUFFER_SIZE];
    ssize_t num_bytes;

    // 创建套接字
    client_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (client_socket == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 设置服务器地址和端口
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = inet_addr(SERVER_IP);
    server_address.sin_port = htons(PORT);

    // 连接到服务器
    if (connect(client_socket, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
        perror("connect");
        exit(EXIT_FAILURE);
    }

    printf("Connected to server\n");

    while (1) {
        printf("Enter a message (or 'q' to quit): ");
        fgets(buffer, MAX_BUFFER_SIZE, stdin);

        // 移除换行符
        buffer[strcspn(buffer, "\n")] = '\0';

        // 检查是否退出客户端
        if (strcmp(buffer, "q") == 0) {
            break;
        }

        // 发送消息给服务器
        if (send(client_socket, buffer, strlen(buffer), 0) == -1) {
            perror("send");
            exit(EXIT_FAILURE);
        }

        // 接收服务器的响应
        num_bytes = recv(client_socket, buffer, MAX_BUFFER_SIZE - 1, 0);
        if (num_bytes == -1) {
            perror("recv");
            exit(EXIT_FAILURE);
        } else if (num_bytes == 0) {
            printf("Server disconnected\n");
            break;
        }

        buffer[num_bytes] = '\0';
        printf("Server response: %s\n", buffer);
    }

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

    return 0;
}

image-20240229175602593

4.6.2 多线程实现并发

void * thread_fun(){
    
}

scoketfd = socket();
bind();
listen();
while(1){
    accept();
    //只要有客户端,就创建子线程与之通信
    pthread_create(,,thread_fun,);
    pthread_detach();
    
}

4.7 web服务器介绍

web服务器又称为www服务器、网站服务器等

4.7.1 特点

  1. 使用HTTP协议与客户机浏览器进行交流
  2. 不仅能够存储信息,还支持用户通过web服务器运行脚本和程序
  3. 服务器一般部署在UNIX、Linux或者window等操作系统上,

4.7.2 HTTP协议

概念:一种详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议
特点:

  1. 支持C/S架构
  2. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径,常用方法:GET、POST
  3. 无连接: 限制每次连接只处理一个请求
  4. 无状态: 即如果后续处理需要前面的信息,它必须重传,这样可能导致每次连接传送的数据量会增大

4.7.3 Web编程开发

image-20240301093438729
posted @ 2024-03-01 09:42  Yasuo_Hasaki  阅读(15)  评论(0)    收藏  举报
//雪花飘落效果