多进程并发TCP服务器模型(含客户端)(网络编程 C语言实现)

摘要:大家都知道不同pc间的通信需要用到套接字sockte来实现,但是服务器一次只能收到一个客户端发来的消息,所以为了能让服务器可以接收多个客户端的连接与消息的传递,我们就引入了多进程并发这样一个概念。听名字就可以知道--需要用到进程,当然也有多线程并发今天我们讲进程的就可以了,线程的同理。

        基本原理: 每连接一个客户端,创建一个子进程,子进程负责处理connfd(客户请求) 
父进程处理sockfd(连接请求)。

常用函数

socket() 创建套接字
bind() 绑定本机地址和端口
connect() 建立连接
listen() 设置监听套接字
accept() 接受TCP连接
recv(), read(), recvfrom() 数据接收
send(), write(), sendto() 数据发送
close(), shutdown() 关闭套接字
 

各函数使用方法和参数返回值

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() 建立连接

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

bind() 绑定本机地址和端口

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() 设置监听套接字

sockfint listen (int sockfd, int backlog);

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

 accept() 接受TCP连接

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

rend( )  发送数据

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


recv( )  接收数据

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

read()和write()经常会代替recv()和send(),通常情况下,看程序员的偏好使用read()/write()和recv()/send()时最好统一。

TCP服务器和客户端搭建流程是

服务器

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

客户端

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

知道流程我们就可以来创建服务器与客户端了

先来看服务器的搭建

#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);//非阻塞等待子进程结束,回收子进程资源
}

再来看看客户端的代码实现(这个可以连接任意一个TCP的服务器):

#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;
} 

来看看运行结果:(左边为客户端,右边为服务器)

 好了,快拿代码去和拿的小伙伴们玩吧!

今天的代码就分享到这里了,哪里写的不对的,希望各位姥爷多多指正!

下期分享广播和组播的搭建,欢迎来访。

猜你喜欢

转载自blog.csdn.net/weixin_56187542/article/details/126412447