IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 实现一个多进程并发的服务器 -> 正文阅读

[系统运维]实现一个多进程并发的服务器

项目来源:登录—专业IT笔试面试备考平台_牛客网

该课程教授了如何使用socket API实现一个基于TCP协议的多进程并发的服务器。实现服务器之前,首先了解一下TCP通信的流程和所要用的各个API。

1.TCP通信流程

TCP通信中,发起连接请求的一端称为客户端,被动接受连接的一端称为服务端。

服务端:

1.创建一个用于监听的套接字
????????-监听:监听客户端的连接
????????-套接字:一个socket文件描述符……socket()
2.将这个监听文件的文件描述符和本地IP和端口绑定(也就是绑定socket地址信息)……bind()
????????-客户端连接服务器的时候使用的就是这个IP和端口
3.设置监听,监听文件描述符开始工作(客户端socket文件给服务端socket文件的读缓冲区发送数据,服务端socket就是监听自己的读缓冲区有无数据,该过程由OS完成)……listen()
? ? ? ? -监听本身不接受请求,发现缓冲区有连接后会转去下一步
4.阻塞等待,直到客户端发起连接,接收客户端连接后会产生另一个用于和客户端通信的socket……accept()
5.通信。……Window:recv()/send() Linux:read()/write()
6.通信结束,关闭连接。……close()

为什么监听到请求后要发起另一个socket用于和客户端通信?

假设有两个客户端先后发起连接而用同一个socket接受连接和进行通信,接受第一个客户端连接后socket开始通信,这个时候第二个客户端也发了一个请求过来,socket这边如果接受请求就得同时跟两个客户端进行通信,而socket文件中只能记下一个客户端的地址信息,这不就乱套了吗。因此必须将接受连接的socket和用于通信的socket区分开来,用一个socket专门接受连接,有多少个连接进来就新建多少个socket与之通信。

这也体现了TCP面向连接的特点,即通信只能是一对一的,一个socket对应一个socket。

?客户端:

1.创建一个用于通信的套接字。(端口号由os自动分配的)……socket()
2.连接服务器,需要制定目的服务器的IP和端口……connect()
3.连接成功,进行通信……send()
4.通信结束,断开连接……close()

2.socket API

所有socket API需要导入头文件#include<arpa/inet.h>

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

参数:

????????-domain:指定地址协议族。

????????常用取值:AF_INET(IPv4),AF_INET6(IPv6)

? ? ? ? -type:通信过程中使用的协议类型。

????????常用取值:SOCK_STREAM(流式协议),SOCK_DGRAM(报式协议)

? ? ? ? -protocol:具体使用的协议。

????????一般写0,os会根据前两个值自动推导出来。

返回值:

? ? ? ? 成功:返回套接字文件描述符

? ? ? ? 失败:-1

这里返回文件描述符又一次说明了socket的本质是文件。因此只要创建了socket,就可以在其他函数中通过其返回的文件描述符,像操作文件一样操作socket了。事实上其他函数也确实是这么做的。

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

参数:

? ? ? ? -sockfd:用于连接的socket文件描述符。

? ? ? ? -addr:服务端进程的socket信息

? ? ? ? 这里形参使用struct sockaddr*类型,实际传入的实参一般不是这种类型,需要进行类型转换

? ? ? ? -addrlen:addr的长度

返回值:

? ? ? ? 失败:-1

  • listen()
 int listen(int sockfd, int backlog);

参数:

? ? ? ? -sockfd:同上

? ? ? ? -backlog:请求队列长度

一个服务器进程可能会收到多个客户端请求,在上一个请求未完成之前新进来的请求会被放入一个队列结构的缓冲区,该缓冲区称为请求队列。

?返回值:

? ? ? ? 失败:-1

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

参数:

? ? ? ? -sockfd:同上

? ? ? ? -addr:用于返回客户端的socket地址信息

? ? ? ? -addrlen:addr的长度

返回值:

????????成功:返回一个新的socket文件描述符,该文件描述符用于与addr所指客户端进行通信

? ? ? ? 失败:-1

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

参数:同bind()

返回值:

? ? ? ? 成功:0

? ? ? ? 失败:-1

  • write()、read()
 ssize_t write(int fd, const void *buf, size_t count);
 ssize_t read(int fd, void *buf, size_t count);

参数:

? ? ? ? -fd:用于通信的socket文件描述符

? ? ? ? -buf:欲写/读的数据。write将数据写入写缓冲区,read从读缓冲区获取数据

? ? ? ? -count:buf长度

3.多并发服务器代码示例

该示例程序演示一个回声客户端,即客户端输入一条数据,服务端就返回一样的数据。

服务端每接受一次连接,就打印连接的客户端ip和端口号。

多个客户端可同时接入,服务端将会创建多个子进程为每个客户端请求新建socket维护通信。

服务端:server_process.c

//TCP通信多并发服务端

#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
    //创建socket
    int lfd = socket(AF_INET,SOCK_STREAM,0);
    if(lfd == -1){
        perror("socket");
        exit(-1);
    }

    //绑定
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    saddr.sin_addr.s_addr = INADDR_ANY;
    int ret = bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(ret == -1){
        perror("bind");
        exit(-1);
    }

    //监听
    ret = listen(lfd,128);
    if(ret == -1){
        perror("listen");
        exit(-1);
    }

    //循环等待客户端连接
    while(1){
        struct sockaddr_in cliaddr;
        int len = sizeof(cliaddr);

        //接受连接
        int cfd = accept(lfd,(struct sockaddr*)&cliaddr,&len);
        if(cfd == -1){
            perror("accept");
            exit(-1);
        }
        
        //每接到一个连接进入,则创建一个子进程于客户端通信
        pid_t pid = fork();
        if(pid == 0){
            //子进程创建成功
            //获取客户端的信息
            char cliIP[16];
            inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,cliIP,sizeof(cliIP));
            unsigned short cliPort = ntohs(cliaddr.sin_port);
            printf("client ip is : %s,port is %d\n",cliIP,cliPort);

            //接收客户端发来的数据
            char recvBuf[1024] = {0};
            while(1){
                int len =  read(cfd,&recvBuf,sizeof(recvBuf));
                if(len == -1){
                    perror("read");
                    exit(-1);
                }else if(len > 0){
                    printf("recv client data : %s\n",recvBuf);
                }else{
                    printf("client closed...");
                }
                /*
                第三个参数+1是为了将返回字符中的换行符带上,
                否则如果两次发送的字符串长度不等,可能会返回
                上一次遗留的字符
                */
                write(cfd,recvBuf,strlen(recvBuf)+1);

            }

            close(cfd);
            exit(0);
        }
    }
    close(lfd);
    return 0;
}

客户端:client.c

// TCP通信客户端

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

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

    //2.连接服务器端
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET,"192.168.59.129",&serveraddr.sin_addr.s_addr);
    serveraddr.sin_port=htons(9999);
    int ret = connect(fd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
    if(ret == -1)
    {
        perror("connect");
        exit(-1);
    }

    //3.通信
    int mnum = 10;
    char recvBuf[1024] = {0};
    while(mnum > 0)
    {
        //客户端给服务器端发送数据
        char * data;
        printf("Input:");
        scanf("%s",data);
        write(fd,data,strlen(data)+1);//同服务端write函数一样,此处要+1传递换行符

        //sleep(1);
        //读取服务端发送的数据
        int len = read(fd,recvBuf,sizeof(recvBuf));
        if(len == -1)
        {
            perror("read");
            exit(-1);
        }else if(len > 0){//表示获取到了数据,len是获取的数据的长度
            printf("recv server data: %s\n",recvBuf);
            mnum--;
        }else if(len == 0){//表示服务端断开连接
            printf("server closed...\n");
            break;
        }
        
    }
 

    //4.关闭连接
    close(fd);


    return 0;
}

?

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-07-05 23:43:39  更:2022-07-05 23:46:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 1:42:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码