我是靠谱客的博主 火星上蚂蚁,最近开发中收集的这篇文章主要介绍网络编程之TCP服务器模型,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

1. 循环服务器

  1. 一次只能处理一个客户端,等该客户端退出后,才能处理下一个客户端

  2. 缺点:循环服务器所处理的客户端不能有耗时操作

i. 模型

sfd = socket();
bind();
listen();
while(1)
{
    newfd = accept();
    
    while(1)
    {
        recv();
        send();
    }
    close(newfd);
}
close(sfd);

ii. 代码示例

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
​
#define ERR_MSG(msg) do{
    printf("line = %dn", __LINE__);
    perror(msg); 
}while(0)
​
#define PORT 6666
#define IP  "192.168.1.12"  //ifconfig查找到本机IP
​
int main(int argc, const char *argv[])
{
    //1.创建字节流式套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
​
​
    //允许端口快速重用
    int reuse = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))<0)
    {
        ERR_MSG("setsockopt");
        return -1;
    }
​
​
    //绑定IP和端口
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;      //地址族
    sin.sin_port        = htons(PORT);  //端口号的网络字节序
    sin.sin_addr.s_addr = inet_addr(IP);    //IP地址的网络字节序
​
​
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("bind");
        return -1;
    }
    printf("绑定成功n");
​
​
    //listen 设置被动监听状态
    if(listen(sfd, 10) < 0)
    {
        ERR_MSG("listen");
        return -1;
    }
    printf("listen successn");
​
​
    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);
​
    while(1)
    {
        //accept
        int newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
        if(newfd < 0)
        {
            ERR_MSG("accept");
            return -1;
        }
​
        printf("[%s:%d] newfd = %dn", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port) ,newfd);
​
        char buf[128] = "";
        ssize_t res = 0;
        while(1)
        {
            bzero(buf, sizeof(buf));
            res = recv(newfd, buf, sizeof(buf), 0);
            if(res < 0)
            {
                ERR_MSG("recv");
                return -1;
            }
            else if(0 == res)
            {
                printf("客户端关闭n");
                break;
            }
​
            printf("[%s:%d] fd=%d:%sn", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd, buf);
​
            strcat(buf, "*_*");
            if(send(newfd, buf, sizeof(buf), 0) < 0)
            {
                ERR_MSG("send");
                return -1;
            }
            printf("发送成功n");
        }
        close(newfd);
    }
​
    close(sfd);
    return 0;
}

2. 并发服务器(重点)

  1. 可以同时处理多个客户端请求,创建子进程或者分支线程来处理客户端请求

  2. 父进程/主线程只负责连接,子进程/分支线程只负责与客户端交互;

1)多进程并发服务器

i. 模型

void handler(int sig)
{
    while(waitpid(-1, NULL, WNOHANG) > 0);
}
​
​
signal(SIGCHLD, handler);
sfd = socket();
bind();
listen();
while(1)
{
    newfd = accept();
    if(fork() == 0)
    {
        close(sfd);
        do_recv(); ------>循环 recv send 数据
        exit(0);
    }
    close(newfd);
}

ii.代码示例

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
​
#define ERR_MSG(msg) do{
    printf("line = %dn", __LINE__);
    perror(msg); 
}while(0)
​
#define PORT 6666
#define IP  "192.168.1.12"  //ifconfig查找到本机IP 
​
int do_recv(int newfd, struct sockaddr_in cin);
​
typedef void (*sighandler_t)(int);
​
void handler(int sig)
{
    while(waitpid(-1, NULL, WNOHANG) > 0);
}
​
int main(int argc, const char *argv[])
{
    //用信号的方式回收僵尸进程
    sighandler_t s = signal(SIGCHLD, handler);
    if(SIG_ERR == s)
    {
        ERR_MSG("signal");
        return -1;
    }
​
    //1.创建字节流式套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
​
​
    //允许端口快速重用
    int reuse = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))<0)
    {
        ERR_MSG("setsockopt");
        return -1;
    }
​
​
    //绑定IP和端口
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;      //地址族
    sin.sin_port        = htons(PORT);  //端口号的网络字节序
    sin.sin_addr.s_addr = inet_addr(IP);    //IP地址的网络字节序
​
​
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("bind");
        return -1;
    }
    printf("绑定成功n");
​
​
    //listen 设置被动监听状态
    if(listen(sfd, 10) < 0)
    {
        ERR_MSG("listen");
        return -1;
    }
    printf("listen successn");
​
    
    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);
    pid_t pid = 0;
    int newfd = 0;
​
    while(1)
    {
        //父进程专门用于连接
        //accept
        newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
        if(newfd < 0)
        {
            ERR_MSG("accept");
            return -1;
        }
​
        printf("[%s:%d] newfd = %d 连接成功nn", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port) ,newfd);
​
​
        //创建一个子进程,专门用于与客户端进行交互 
        if(fork() == 0)
        {
            //子进程只做交互,所以sfd没有用处
            close(sfd);
​
            //专门用于与客户端进行交互 
            do_recv(newfd, cin);
            //由于子进程只能做交互,所以当do_recv退出后,子进程没必要存在了,
            //所以需要将子进程退出;
            exit(0);
        }
​
        //由于父进程只需要连接,所以newfd没有用处
        close(newfd);
    }
    close(sfd);
    return 0;
}
​
​
int do_recv(int newfd, struct sockaddr_in cin)
{
    char buf[128] = "";
    ssize_t res = 0;
    while(1)
    {
        bzero(buf, sizeof(buf));
        res = recv(newfd, buf, sizeof(buf), 0);
        if(res < 0)
        {
            ERR_MSG("recv");
            return -1;
        }
        else if(0 == res)
        {
            printf("[%s:%d] newfd = %d 客户端退出nn", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port) ,newfd);
            break;
        }
​
        printf("[%s:%d] fd=%d:%sn", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd, buf);
​
        strcat(buf, "*_*");
        if(send(newfd, buf, sizeof(buf), 0) < 0)
        {
            ERR_MSG("send");
            return -1;
        }
        printf("发送成功n");
    }
    close(newfd);
    return 0;
}

2)多线程并发服务器

i. 模型

sfd = socket();
bind();
listen();
while(1)
{
    //主线程用于处理客户端的连接
    newfd = accept();
    
    //创建分支线程
    pthread_create(); ====>recv_cli_msg()的线程处理函数中,用于处理客户端交互;
​
}
​
void* recv_cli_msg(void* arg)
{
    pthread_detach(tid);
    while(1)
    {
        recv();
        send();
    }
}

ii. 代码示例

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
​
#define ERR_MSG(msg) do{
    printf("line = %dn", __LINE__);
    perror(msg); 
}while(0)
​
#define PORT 6666
#define IP  "192.168.1.12"  //ifconfig查找到本机IP
​
//需要传入到分支线程中的数据结构体
struct CliMsg
{
    struct sockaddr_in cin;
    int newfd;
};
​
void* recv_cli_msg(void* arg);
​
int main(int argc, const char *argv[])
{
    //1.创建字节流式套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd < 0)
    {
        ERR_MSG("socket");
        return -1;
    }
​
​
    //允许端口快速重用
    int reuse = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse))<0)
    {
        ERR_MSG("setsockopt");
        return -1;
    }
​
​
    //绑定IP和端口
    struct sockaddr_in sin;
    sin.sin_family      = AF_INET;      //地址族
    sin.sin_port        = htons(PORT);  //端口号的网络字节序
    sin.sin_addr.s_addr = inet_addr(IP);    //IP地址的网络字节序
​
​
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    {
        ERR_MSG("bind");
        return -1;
    }
    printf("绑定成功n");
​
​
    //listen 设置被动监听状态
    if(listen(sfd, 10) < 0)
    {
        ERR_MSG("listen");
        return -1;
    }
    printf("listen successn");
​
​
    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);
​
    int newfd = 0;
    pthread_t tid;
    struct CliMsg info;
​
    while(1)
    {
        //accept
        newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
        if(newfd < 0)
        {
            ERR_MSG("accept");
            return -1;
        }
​
        printf("[%s:%d] newfd = %dn", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port) ,newfd);
​
        info.cin = cin;
        info.newfd = newfd;
​
        //连接成功后,创建一个分支线程,用于与客户端进行交互
        if(pthread_create(&tid, NULL, recv_cli_msg, (void*)&info) < 0)
        {
            ERR_MSG("pthread_create");
            return -1;
        }
        
    }
​
    close(sfd);
    return 0;
}
​
void* recv_cli_msg(void* arg)
{
    //线程分离,自己分离自己
    pthread_detach(pthread_self());
​
    struct CliMsg info = *(struct CliMsg*)arg;
    struct sockaddr_in cin = info.cin;
    int newfd = info.newfd;
​
    char buf[128] = "";
    ssize_t res = 0;
    while(1)
    {
        bzero(buf, sizeof(buf));
        res = recv(newfd, buf, sizeof(buf), 0);
        if(res < 0)
        {
            ERR_MSG("recv");
            break;
        }
        else if(0 == res)
        {
            printf("[%s:%d] newfd = %d 客户端关闭n", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port) ,newfd);
            break;
        }
​
        printf("[%s:%d] fd=%d:%sn", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd, buf);
​
        strcat(buf, "*_*");
        if(send(newfd, buf, sizeof(buf), 0) < 0)
        {
            ERR_MSG("send");
            break;
        }
        printf("发送成功n");
    }
​
    close(newfd);
    pthread_exit(NULL);
}

3. 项目:基于TCP的文件服务

项目需求:

  1. 编写客户端和服务器

  2. 客户端可以查看服务器端目录中的文件名 opendir readdir

  3. 客户端可以从服务器中下载文件

  4. 客户端可以上传文件给服务器

4.项目代码 

GitHub - xuyongxiang/FileSever: TCP文件服务器

最后

以上就是火星上蚂蚁为你收集整理的网络编程之TCP服务器模型的全部内容,希望文章能够帮你解决网络编程之TCP服务器模型所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(44)

评论列表共有 0 条评论

立即
投稿
返回
顶部