循环服务器

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

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define ERR_LOG(msg) do{\
    perror(msg);\
    printf("%d %s %s\n", __LINE__, __func__, __FILE__);\
}while(0)
#define N 128
#define PORT 7777
#define IP "192.168.1.102"
int main(int argc, const char *argv[])
{
    //1.创建套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd < 0)
    {
        ERR_LOG("socket");
        return -1;
    }
    //2.绑定服务器ip和端口
    struct sockaddr_in sin;
    sin.sin_family         = AF_INET;
    sin.sin_port         = htons(PORT);
    sin.sin_addr.s_addr = inet_addr(IP);
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) <0)
    {
        ERR_LOG("bind");
        return -1;
    }
    //3.将套接字设置为被动监听状态
    if(listen(sfd, 5) < 0)
    {
        ERR_LOG("listen");
        return -1;
    }
    printf("监听套接字成功\n");
    //4.获取连接后的套接字
    struct sockaddr_in cin;
    socklen_t clen = sizeof(cin);

    while(1)
    {
        int newfd = accept(sfd, (struct sockaddr*)&cin, &clen);
        if(newfd < 0)
        {
            ERR_LOG("accept");
            return -1;
        }
        printf("newfd = %d\n", newfd);

        //5.循环发送接收
        int res = 0;
        char buf[N] = "";
        while(1)
        {
            bzero(buf, N);
            res = recv(newfd, buf, N, 0);
            if(res < 0)
            {
                ERR_LOG("recv");
                return -1;
            }
            else if(0 == res)
            {
                printf("对方关闭\n");
                break;
            }
            printf("%s\n", buf);

            strcat(buf, "*_*");
            if((res=send(newfd, buf, N, 0)) < 0)
            {
                ERR_LOG("send");
                return -1;
            }
            printf("发送成功%d\n", res);
        }
        close(newfd);
    }
    //6.关闭套接字
    close(sfd);
    return 0;
}

并发服务器

  1. 可以同时处理多个客户端请求,创建子进程或者分支线程来处理客户端的请求
  2. 父进程/主线程只负责连接,子进程/分支线程只负责与客户端进行交互

多进程并发服务器

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <signal.h>
#include <stdlib.h>
#define ERR_LOG(msg) do{\
    perror(msg);\
    printf("%d %s %s\n", __LINE__, __func__, __FILE__);\
}while(0)
#define N 128
#define PORT 8888
#define IP "0.0.0.0"
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(s == SIG_ERR)
    {
        ERR_LOG("signal");
        return -1;
    }
    //1.创建套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd < 0)
    {
        ERR_LOG("socket");
        return -1;
    }
    //允许地址快速重用
    int reuse = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) < 0)
    {
        perror("setsockopt");
        return -1;
    }
    //2.绑定服务器ip和端口
    struct sockaddr_in sin;
    sin.sin_family         = AF_INET;
    sin.sin_port         = htons(PORT);
    sin.sin_addr.s_addr = inet_addr(IP);
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) <0)
    {
        ERR_LOG("bind");
        return -1;
    }
    //3.将套接字设置为被动监听状态
    if(listen(sfd, 5) < 0)
    {
        ERR_LOG("listen");
        return -1;
    }
    printf("监听套接字成功\n");
    //4.获取连接后的套接字
    struct sockaddr_in cin;
    socklen_t clen = sizeof(cin);
    while(1)
    {
        int newfd = accept(sfd, (struct sockaddr*)&cin, &clen);
        if(newfd < 0)
        {
            ERR_LOG("accept");
            return -1;
        }
        //打印连接成功的客户端信息
        char ip[20] = "";
        inet_ntop(AF_INET, &cin.sin_addr, ip, 20);
        printf("[%s:%d] fd:%d连接成功\n", ip, ntohs(cin.sin_port), newfd);
        pid_t pid = fork();
        if(pid > 0)
        {
            //父进程,关闭newfd,因为newfd对于父进程没有作用.
            close(newfd);
        }
        else if(0 == pid)
        {
            //子进程,与客户端进程交互
            close(sfd);

            int res = 0;
            char buf[N] = "";
            while(1)
            {
                //收发
                bzero(buf, N);
                res = recv(newfd, buf, N, 0);
                if(res < 0)
                {
                    ERR_LOG("recv");
                    exit(1);
                }
                else if(0 == res)
                {
                    printf("对方关闭\n");
                    break;
                }
                printf("%s\n", buf);

                strcat(buf, "*_*");
                if((res=send(newfd, buf, N, 0)) < 0)
                {
                    ERR_LOG("send");
                    exit(1);
                }
                printf("发送成功%d\n", res);
            }
            //注意,子进程不需要回到accept函数位置,所以直接退出.
            exit(1);
        }
        else
        {
            ERR_LOG("fork");
            return -1;
        }
    }
    //6.关闭套接字
    close(sfd);
    return 0;
}

多线程并发服务器

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#define ERR_LOG(msg) do{\
    perror(msg);\
    printf("%d %s %s\n", __LINE__, __func__, __FILE__);\
}while(0)
#define N 128
#define PORT 9999
#define IP "0.0.0.0"
typedef struct
{
    int newfd;
    struct sockaddr_in cin;
}__cli_info;
int show_info(struct sockaddr_in,int);
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_LOG("socket");
        return -1;
    }
    //本地端口快速重用
    int reuse = 1;
    setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
    //2.绑定服务器ip和端口
    struct sockaddr_in sin;
    sin.sin_family         = AF_INET;
    sin.sin_port         = htons(PORT);
    sin.sin_addr.s_addr = inet_addr(IP);
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) <0)
    {
        ERR_LOG("bind");
        return -1;
    }
    //3.将套接字设置为被动监听状态
    if(listen(sfd, 5) < 0)
    {
        ERR_LOG("listen");
        return -1;
    }
    printf("监听套接字成功\n");
    //4.获取连接后的套接字
    struct sockaddr_in cin;
    socklen_t clen = sizeof(cin);
    int newfd = 0;
    while(1)
    {
        newfd = accept(sfd, (struct sockaddr*)&cin, &clen);
        if(newfd < 0)
        {
            ERR_LOG("accept");
            return -1;
        }
        //打印连接成功的客户端信息
        show_info(cin, newfd);
        //创建分支线程
        __cli_info cinfo;
        cinfo.newfd = newfd;
        cinfo.cin = cin;
        pthread_t tid = -1;
        if(pthread_create(&tid, NULL, recv_cli_msg, (void*)&cinfo) !=0)
        {
            ERR_LOG("pthread_create");
            return -1;
        }
    }
    //6.关闭套接字
    close(sfd);
    return 0;
}
//线程处理函数
void* recv_cli_msg(void* arg)
{
    //分离线程,线程退出后自动回收线程资源
    pthread_detach(pthread_self());
    //下面方式是错误的,因为通过指针间接访问的是main函数中的newfd的地址空间内容
    //每次连接成功accept后,*pnewfd的值都会更新.
    //int* pnewfd = (int*)arg;
    __cli_info cli = *(__cli_info*)arg;
    int res = 0;
    char buf[N] = "";
    while(1)
    {
        bzero(buf, N);
        res = recv(cli.newfd, buf, N, 0);
        if(res < 0)
        {
            ERR_LOG("recv");
            break;
        }
        else if(0 == res)
        {
            printf("对方关闭\n");
            break;
        }
        char ip[20] = "";
        inet_ntop(AF_INET, &cli.cin.sin_addr, ip ,20);
        printf("[%s:%d]:%s\n", ip, ntohs(cli.cin.sin_port), buf);
    }
    close(cli.newfd);
    pthread_exit(NULL);
}
//打印客户端信息
int show_info(struct sockaddr_in cin, int newfd)
{
    char ip[20] = "";
    if(inet_ntop(AF_INET, &cin.sin_addr, ip, 20)<0)
    {
        ERR_LOG("inet_ntop");
        return -1;
    }
    printf("[%s:%d] fd:%d连接成功\n", ip,ntohs(cin.sin_port), newfd);
    return 0;
}
Last modification:2021 年 04 月 19 日 10 : 26 : 16