Multi-process concurrent TCP server model (including client) (network programming C language implementation)

Abstract : We all know that the communication between different PCs needs to be implemented using socket sockets, but the server can only receive messages from one client at a time, so in order to allow the server to receive connections and messages from multiple clients Passing, we introduced the concept of multi-process concurrency. You can tell by the name - you need to use processes, and of course there are multi-threaded concurrency. Today we can talk about processes. The same is true for threads.

        Basic principle : Every time a client is connected, a child process is created, and the child process is responsible for processing connfd (client request) and the 
parent process handles sockfd (connection request).

Common functions :

socket() create socket
bind() bind local address and port
connect() establish connection
listen() set listening socket
accept() accept TCP connection
recv(), read(), recvfrom() data receiving
send (), write(), sendto() send data
close(), shutdown() close the socket
 

Each function uses the method and parameter return value :

socket() creates a socket

int socket (int domain, int type, int protocol);
domain 是地址族
PF_INET // internet 协议
PF_UNIX // unix internal协议
PF_NS // Xerox NS协议
PF_IMPLINK // Interface Message协议
type // 套接字类型
SOCK_STREAM // 流式套接字
SOCK_DGRAM // 数据报套接字
SOCK_RAW // 原始套接字
protocol 参数通常置为0

connect() to establish a connection

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
返回值:0 或 -1
sockfd : socket返回的文件描述符
serv_addr : 服务器端的地址信息
addrlen : serv_addr的长度

bind() Bind the local address and port

int bind (int sockfd, struct sockaddr* addr, int addrLen);
sockfd 由socket() 调用返回
addr 是指向 sockaddr_in 结构的指针,包含本机IP 地址和端口号
struct sockaddr_in
                u_short sin_family // protocol family
                u_short sin_port // port number
                struct in_addr sin_addr //IP address (32-bits)
addrLen : sizeof (struct sockaddr_in)


listen() sets the listening socket

sockfint listen (int sockfd, int backlog);

sockfd:监听连接的套接字
backlog(一般填5或10)
        指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。
        DoS(拒绝服务)攻击即利用了这个原理,非法的连接占用了全部的连接数,造成正常的
        连接请求被拒绝。
返回值: 0 或 -1
完成 listen() 调用后, socket 变成了监听socket(listening socket)

 accept() accepts a TCP connection

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) ;
返回值:已建立好连接的套接字或-1
sockfd : 监听套接字
addr : 对方地址
addrlen:地址长度

rend( ) send data

ssize_t send(int socket, const void *buffer, size_t length,int flags);
返回值:
成功:实际发送的字节数
失败:-1, 并设置errno
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 发送方式(通常为0)


recv( ) receive data

ssize_t recv(int socket, const void *buffer, size_t length,int flags);
返回值:
 成功:实际接收的字节数
失败:-1, 并设置errno
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 接收方式(通常为0)

read() and write() often replace recv() and send(). Usually, it is best to use read()/write() and recv()/send() uniformly depending on the programmer's preference.

The TCP server and client building process is :

server :

socket()  -->  bind() --> listen() --> accept() --> read/write   or   recv/send 
-->close()

client :

socket()  --> connect() --> read/write   or   recv/send  -->close()

Knowing the process, we can create the server and client

Let's look at the server setup first :

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/wait.h>
#include <signal.h>
#define N 1024

int sock_func(int port);
int recv_data(int connfd);
void signal_handler(int sig);

int main(int argc, char *argv[])
{ 
    signal(SIGCHLD,signal_handler);  //子进程结束时 回收资源

    int sockfd = sock_func(8090);  //调用服务器创建函数,传递端口参数 局域网可自行设置(0-65535,但建议>1024)
    				    //公网的话要在(5000-65535)以内,前面的被大公司些用了。不可用
    struct sockaddr_in cliaddr;  
    int len = sizeof(cliaddr);  
    while(1)  //循环等待客户端连接
    {
        int connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);//等待并接收客户端的连接请求
        if(connfd == -1)   //出错处理
        {
            perror("accept");
            return -1;
        }

        printf("accept is successful\n");
        printf("client ip: %s port is:%d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port)); //打印客户端IP和端口
        pid_t pid = fork();  //创建子进程
        if (pid<0)
        {
            perror("fork");
            return -1;
        }
        else if(pid == 0) //子进程
        {
            close(sockfd);  //关闭继承过来的本机套接字描述符
            recv_data(connfd);  //传递客户端套接字去到接收数据函数模块
            return 0;
        }
        else
        {
            continue; //跳出本次循环开始等待下一个客户端连接
        }
    }
    close(sockfd);
    return 0;
} 

int sock_func(int port)  //创建服务器模块
{

    int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建套接字
    if(sockfd == -1)
    {
        perror("scoket");
        return -1;
    }
    printf("socket is succssful\n");
    
	int on = 1;
	int k = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));//端口复用函数:解决端口号被系统占用的情况
	if(k == -1)
	{
		perror("setsockopt");
		return -1;
	}
    struct sockaddr_in saddr; 
    bzero(&saddr,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr("0");  //"0"代表匹配本机ip
    saddr.sin_port =htons(port);  //绑定端口
    char buf[N]={0};
    int ret1 = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));//绑定服务器IP地址和端口
    if(ret1 == -1)
    {
        perror("bind");
        return -1;
    }
    printf("bind is successful\n");
    int ret2 = listen(sockfd,5);//设置监听
    if(ret2 == -1)
    {
        perror("listen");
        return -1;
    }
    printf("linstening......\n");
    return sockfd;

}
int recv_data(int connfd) //接收数据模块
{
    char buf[N]={0}; //接收数据缓冲区
    while(1)  //循环接收客户端发送的数据
    {
        int n = recv(connfd,buf,N,0);//接收客户端发送的数据
        if(n<0) //出错
        {
            perror("recv");
            return -1;
        }
        else if(n == 0)  //客户端关闭
        {

            close(connfd);//关闭客户端套接字
            break;
        }
        printf(">>:%s\n",buf); //打印客户端发送的消息
        memset(buf,0,sizeof(buf));  // 清空本次数据
    }
}
void signal_handler(int sig)//回收子进程资源
{
    waitpid(-1,NULL,WNOHANG);//非阻塞等待子进程结束,回收子进程资源
}

Let's take a look at the code implementation of the client (this can connect to any TCP server):

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define N 1024
int main(int argc, char *argv[])
{ 
    if(argc<3)  //输入参数不匹配情况
    {
        printf("%s <ip> <port>\n",argv[0]);
        return -1;
    }
    char buf[N]={0};  //定义缓冲区存放要发送的数据
    struct sockaddr_in saddr;
    socklen_t peerlen;
    saddr.sin_family = AF_INET;  //使用ipv4
    saddr.sin_addr.s_addr = inet_addr(argv[1]);  //输入服务器ip
    saddr.sin_port =htons(atoi(argv[2]));  //输入服务器端口
    int sockfd = socket(AF_INET,SOCK_STREAM,0);  //创建套接字
    if(sockfd == -1) //出错处理
    {
        perror("socket");
        return -1;
    }
    int ret1 = connect(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));//连接服务器
    if(ret1 == -1)
    {
        perror("connet");
        return -1;
    }
    printf("connet is seccussful\n");
    int ret2;
    while(1)   //循环发送信息
    {   memset(buf,0,sizeof(buf)); //每次清空缓冲区
        fgets(buf,N,stdin);  //写入缓冲区
        buf[strlen(buf)-1]='\0';
        ret2 = send(sockfd,buf,strlen(buf),0);//发送至服务器
        if(ret2 == -1)
        {
            perror("send");
            return -1;
        }
        if(strcmp(buf,"quit\n")==0) //设置退出条件
        {
            printf("quiting....\n");
            break;
        }
    }
    close(sockfd);

    return 0;
} 

Let's take a look at the running results : (client on the left, server on the right)

 Well, quickly take the code and play with the friends you took!

Today's code is shared here, where I wrote something wrong, I hope you can correct me a lot!

In the next issue, I will share the construction of broadcast and multicast, welcome to visit.

Guess you like

Origin blog.csdn.net/weixin_56187542/article/details/126412447