嗨,老铁,欢迎来到我的博客!

如果觉得我的内容还不错的话,可以关注下我在 segmentfault.com 上的直播。我主要从事 PHP 和 Java 方面的开发,《深入 PHP 内核》作者之一。

[视频直播] PHP 进阶之路 - 亿级 pv 网站架构的技术细节与套路 直播中我将毫无保留的分享我这六年的全部工作经验和踩坑的故事,以及会穿插着一些面试中的 考点难点加分点

周梦康 发表于 2015-11-22 4909 次浏览 标签 : Linuxsocket

实验内容:多个客户端并发给服务器端发送消息,服务器端给客户端返回反转的内容。

服务器端做了两个版本,不限制子进程数版本预先分配指定进程数的改进。这一系列笔记代码我都存储在https://github.com/zhoumengkang/notes/tree/master/unix/socket

服务器端原理图:

多进程并发的面向连接服务器与客户端实践

心得与收获:客户端从sendrecv的过程是阻塞的,一直等待服务器端给socket fd写入内容返回。如果客户端没有使用recv来接收socket fd的返回信息,则不会等待直接往下继续执行。

客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <netdb.h>
  
#define SERV_PORT 8031
#define BUFSIZE	1024
void process(FILE * fp, int sockfd);
char * getMessage(char * sendline,int len,FILE * fp);
 
int main(int argc,char const * argv[])
{
    int fd;
    struct hostent * he;
    struct sockaddr_in server;

    if (argc != 2)
    {
    	printf("usage %s <IP Adrress>", argv[0]);
    	exit(1);
    }

    if ((he = gethostbyname(argv[1])) == NULL)
    {
    	perror("gethostbyname error");
    	exit(1);
    }

    if((fd = socket(AF_INET,SOCK_STREAM,0)) == -1){
        perror("create socket failed");
        exit(1);
    }
    
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(SERV_PORT);
    server.sin_addr = *((struct in_addr *) he->h_addr);

    if(connect(fd, (struct sockaddr *)&server, sizeof(server)) == -1)
    {
        perror("connect error");
        exit(1);
    }

    process(stdin,fd);

    close(fd);

    return 0;
}

void process(FILE * fp, int sockfd)
{
	char sendline[BUFSIZE], recvline[BUFSIZE];
	int numbytes;

	while(getMessage(sendline,BUFSIZE,fp) != NULL)
	{
		send(sockfd,sendline,strlen(sendline),0);

		if ((numbytes = recv(sockfd,recvline,BUFSIZE,0)) == 0)
		{
			printf("server terminated\n");
			return;
		}
        recvline[numbytes] = '\0';
		printf("server string return  :%s\n",recvline);
	}

	printf("client exit.\n");
}


char * getMessage(char * sendline,int len,FILE * fp)
{
	printf("input string to server:");
	return (fgets(sendline,len,fp));
}

服务器端代码

不固定进程数的并发模型设计

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <string.h>
    
#define SERV_PORT 8031
#define BUFSIZE 1024
void process_cli(int connectfd,struct sockaddr_in client);

int main(void)
{
    int lfd, cfd;
    struct sockaddr_in serv_addr,clin_addr;
    socklen_t clin_len;
    pid_t pid;
    char buf[1024];
    int len;
    
    if((lfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
        perror("create socket failed");
        exit(1);
    }
  
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(SERV_PORT);
       
    if(bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
    {
        perror("bind error");
        exit(1);
    }
        
    if(listen(lfd, 128) == -1)
    {
        perror("listen error");
        exit(1);
    }
   
    clin_len = sizeof(clin_addr);
  
    signal(SIGCLD,SIG_IGN);
  
    while(1)
    {
          
        if((cfd = accept(lfd, (struct sockaddr *)&clin_addr, &clin_len)) == -1)
        {
            perror("accept error");
            exit(1);
        }
  
        pid = fork();
  
        if (pid > 0)
        {
            // 在父进程中关闭连接的套接字描述符,只是把 cfd 的引用数减少1,在子进程中还在使用 cfd
            close(cfd);
        }
        else if (pid == 0)
        {
            // 子进程关闭 lfd 处理任务,使其回到 TIME_WAIT 状态值
            close(lfd);
            process_cli(cfd,clin_addr);
            exit(0);
        }
        else
        {
            perror("fork error");
            exit(1);
        }

    }
   
    close(lfd);
       
    return 0;
}

void process_cli(int connectfd,struct sockaddr_in client)
{
    int num,len;
    char recvbuf[BUFSIZE],sendbuf[BUFSIZE],client_name[BUFSIZE],client_port[5];

    snprintf(client_name,100,"%s:%d",inet_ntoa(client.sin_addr),client.sin_port);

    printf("you get a connection from %s\n", client_name);

    while(num = recv(connectfd,recvbuf,BUFSIZE,0))
    {
        recvbuf[num-1] = '\0';// 去掉收到消息末尾的回车
        printf("receive client (%s) message: %s\n", client_name,recvbuf);

        
        len = num-1;
        for (int i = 0; i < len; ++i)
        {
            sendbuf[i] = recvbuf[num -i -2];
        }
        sendbuf[len] = '\0';// 这样就不用执行 memset(sendbuf,0,sizeof(sendbuf));
        send(connectfd,sendbuf,len,0);
    }

    printf("client (%s) exit\n",client_name);

    close(connectfd);
}

客户端终端

[zhoumengkang@localhost unix]$ gcc client.c -o client
[zhoumengkang@localhost unix]$ ./client 127.0.0.1
input string to server:zmk
server string return  :kmz
input string to server:haha
server string return  :ahah
input string to server:hei
server string return  :ieh
input string to server:

服务器终端

[zhoumengkang@localhost unix]$ gcc server.c -std=c99 -o server
[zhoumengkang@localhost unix]$ ./server 
you get a connection from 127.0.0.1:40362
receive client (127.0.0.1:40362) message: zmk
receive client (127.0.0.1:40362) message: haha
receive client (127.0.0.1:40362) message: hei

这里还没有对子进程个数做限定,压测的时候,如果请求多,并发大,则会创建太多子进程而退出的情况,所以还需要对上面的代码的子进程做线程池管理。

固定进程数的并发模型

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <string.h>
    
#define SERV_PORT 8031
#define BUFSIZE 1024
#define BACKLOG 20

static void handle_fork(int lfd);

int main(void)
{
    int lfd, cfd;
    struct sockaddr_in serv_addr;
    
    pid_t pid[BACKLOG];
    char buf[BUFSIZE];
    int len;
    
    if((lfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
        perror("create socket failed");
        exit(1);
    }
  
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
      
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(SERV_PORT);
       
    if(bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1)
    {
        perror("bind error");
        exit(1);
    }
        
    if(listen(lfd, BACKLOG) == -1)
    {
        perror("listen error");
        exit(1);
    }
  
    signal(SIGCLD,SIG_IGN);
  
    for (int i = 0; i < BACKLOG; ++i)
    {
        pid[i] = fork();
        if (pid[i] == 0)
        {
            handle_fork(lfd);
        }
    }
   
    close(lfd);
       
    return 0;
}

static void handle_fork(int lfd)
{
    int cfd;
    struct sockaddr_in clin_addr;
    socklen_t clin_len;
    socklen_t num,len;
    char recvbuf[BUFSIZE],sendbuf[BUFSIZE],client_name[100];

    clin_len = sizeof(clin_addr);
    
    while(1)
    {
        if((cfd = accept(lfd, (struct sockaddr *)&clin_addr, &clin_len)) == -1)
        {
            perror("accept error");
            exit(1);
        }
        else
        {
            snprintf(client_name,100,"%s:%d",inet_ntoa(clin_addr.sin_addr),ntohs(clin_addr.sin_port));
            printf("you get a connection from %s\n", client_name);
        }

        while (num = recv(cfd,recvbuf,BUFSIZE,0))
        {
            recvbuf[num-1] = '\0';// 把客户端发送过来的最后的一个空格去掉
            printf("receive client (%s) message: %s\n", client_name,recvbuf);
            
            len = num-1;
            for (int i = 0; i < len; ++i)
            {
                sendbuf[i] = recvbuf[num -i -2];
            }
            sendbuf[len] = '\0';// 这样就不用执行 memset(sendbuf,0,sizeof(sendbuf));
            send(cfd,sendbuf,len,0);
        }

        printf("client (%s) exit\n",client_name);

        close(cfd);
    }

}


嗨,老铁,欢迎来到我的博客!

如果觉得我的内容还不错的话,可以关注下我在 segmentfault.com 上的直播。我主要从事 PHP 和 Java 方面的开发,《深入 PHP 内核》作者之一。

[视频直播] PHP 进阶之路 - 亿级 pv 网站架构的技术细节与套路 直播中我将毫无保留的分享我这六年的全部工作经验和踩坑的故事,以及会穿插着一些面试中的 考点难点加分点

评论列表