Linux&C语言简单使用多进程实现TCP并发服务器-传输层

多进程实现TCP并发服务器

在这里插入图片描述

前言

服务器模型分为两种,循环服务器、并发服务器。

循环服务器: 服务器在同一时刻只能处理同一个客户端的请求。
并发服务器: 服务器在同一时刻能处理多个客户端的请求。

TCP服务器默认的就是一个循环服务器,因为内部有两个阻塞的函数, accept recv 会相互影响
UDP服务器默认的就是一个并发服务器,因为只有一个阻塞的函数 recvfrom

原理

父进程处理 accept 函数,等待客户端连接,一旦有新的客户端连接到服务器, 就创建一个新的子进程专门来处理该客户端的读写请求。

代码实现

服务器—01server.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

#define ERRLOG(errmsg)                                       \
    do                                                       \
    {                                                        \
        printf("%s--%s(%d):", __FILE__, __func__, __LINE__); \
        perror(errmsg);                                      \
        exit(-1);                                            \
    } while (0)

//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[]);
//信号处理函数
void signal_handle(int signo);

int main(int argc, const char *argv[])
{
    //检测命令行参数个数
    if (3 != argc)
    {
        printf("Usage : %s <IP> <PORT>\n", argv[0]);
        exit(-1);
    }

    //创建套接字-填充服务器网络信息结构体-绑定-监听
    int sockfd = socket_bind_listen(argv);

    //用来保存客户端信息的结构体
    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(client_addr));
    socklen_t client_addr_len = sizeof(client_addr);

    char buff[128] = {0};
    int accept_fd;//文件描述符,专门用于和该客户端通信
    int ret= 0;

    while (1)
    {
        //阻塞等待客户端连接--一旦有客户端连接就会解除阻塞
        int accept_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
        if (-1 == accept_fd)
            ERRLOG("accept error");

        //inet_ntoa32位网络字节序二进制地址转换成点分十进制的字符串
        //ntohs将无符号2字节整型  网络-->主机
        printf("客户端 (%s:%d) 连接了\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        
        //创建子进程
        pid_t pid;
        pid = fork();
        if (pid < 0)
        {
            ERRLOG("fork error");
        }
        else if (pid == 0)
        {
            //这里是子进程执行
            while (1)
            {
                if (0 > (ret = recv(accept_fd, buff,sizeof(buff), 0)))
                {
                    perror("recv error");
                    break;
                }
                else if (0 == ret)//客户端侧CTRL+C
                {
                    printf("客户端 (%s:%d) 断开连接\n",
                           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                    break;
                }
                else
                {
                    if (0 == strcmp(buff, "quit"))
                    {
                        printf("客户端 (%s:%d) 退出了\n",
                               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                        break;
                    }
                    printf("客户端 (%s:%d) 发来数据:[%s]\n",
                           inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buff);
                    //组装回复给客户端的应答
                    strcat(buff, "---996");
                    //回复应答
                    if (0 > (ret = send(accept_fd, buff,sizeof(buff), 0)))
                    {
                        perror("send error");
                        break;
                    }
                }
            }
            //关闭该客户端的套接字
            close(accept_fd);
            exit(EXIT_SUCCESS);
        }
        else
        {
            //这里是父进程执行
            //当子进程结束的时候,父进程会收到一个SIGCHLD的信号
            if (signal(SIGCHLD, signal_handle) == SIG_ERR)
                perror("signal error");
            //父进程不用,可以关闭
            close(accept_fd);
        }
    }
    //关闭监听套接字  一般不关闭
    close(sockfd);

    return 0;
}

//信号处理函数
void signal_handle(int signo)
{
    wait(NULL);
    printf("父进程以阻塞的方式回收了子进程的资源\n");
}

//创建套接字-填充服务器网络信息结构体-绑定-监听
int socket_bind_listen(const char *argv[])
{
    // 1.创建套接字      //IPV4   //TCP
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sockfd)
        ERRLOG("socket error");

    // 2.填充服务器网络信息结构体
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr)); //清空
    server_addr.sin_family = AF_INET;             // IPV4

    //端口号  填 8888 9999 6789 ...都可以
    // atoi字符串转换成整型数
    //htons将无符号2字节整型  主机-->网络
    server_addr.sin_port = htons(atoi(argv[2]));

    // ip地址 要么是当前Ubuntu主机的IP地址 或者
    //如果本地测试的化  使用  127.0.0.1 也可以
    //inet_addr字符串转换成32位的网络字节序二进制值
    server_addr.sin_addr.s_addr = inet_addr(argv[1]);

    //结构体长度
    socklen_t server_addr_len = sizeof(server_addr);

    // 3.将套接字和网络信息结构体绑定
    if (-1 == bind(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
        ERRLOG("bind error");

    //将套接字设置成被动监听状态
    if (-1 == listen(sockfd, 10))
        ERRLOG("listen error");

    return sockfd;
}

客户端—02client.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

#define ERRLOG(errmsg) do{\
		printf("%s--%s(%d):", __FILE__, __func__, __LINE__);\
		perror(errmsg);\
		exit(-1);\
	}while(0)

int main(int argc, const char *argv[])
{
	//检测命令行参数个数
	if(3 != argc){
		printf("Usage : %s <IP> <PORT>\n", argv[0]);
		exit(-1);
	}

	//1.创建套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == sockfd)
		ERRLOG("socket error");
	

	//2.填充服务器网络信息结构体
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	//端口号  填 8888 9999 6789 ...都可以
	server_addr.sin_port = htons(atoi(argv[2]));
	//ip地址 要么是当前Ubuntu主机的IP地址 或者
	//如果本地测试的化  使用  127.0.0.1 也可以
	server_addr.sin_addr.s_addr = inet_addr(argv[1]);

	//结构体长度
	socklen_t server_addr_len = sizeof(server_addr);

	//与服务器建立连接
	if(-1 == connect(sockfd, (struct sockaddr *)&server_addr, server_addr_len))
		ERRLOG("connect error");
	
	printf("---连接服务器成功---\n");
	char buff[128] = {0};
	while(1){
		scanf("%s", buff);
		int ret = 0;
		if(0 > (ret = send(sockfd, buff,sizeof(buff), 0)))
			ERRLOG("send error");
		
		if(0 == strcmp(buff, "quit"))
			break;
		
		if(0 > (ret = recv(sockfd, buff,sizeof(buff), 0)))
			ERRLOG("recv error");
		
		printf("收到服务器回复:[%s]\n", buff);
	}
	//关闭套接字
	close(sockfd);

	return 0;
}

执行结果

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值