使用多进程实现一对多的TCP服务器
- 一、前言
- 二、新增使用的fork()函数
- 三、实现步骤
- 四、完整代码
- 五、TCP客户端
- 5.1、自己实现一个TCP客户端
- 5.2、Windows下可以使用NetAssist的网络助手工具
- 小结
一、前言
手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。
为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。本节在上一章节的基础上,改为多进程方式实现TCP服务器,为每个新接入的客户端分配进程,实现一个服务器程序处理多个客户端连接。主要目的是比较不同方式的利弊关系。
二、新增使用的fork()函数
函数原型:
#include <unistd.h>
pid_t fork(void);
fork()通过复制调用进程来创建一个新进程。新进程被称为子进程。调用进程被称为父进程。
子进程和父进程在单独的内存空间中运行。在执行fork()时,两个内存空间都具有相同的内容。其中一个进程执行的内存写入、文件映射(mmap)和取消映射(munmap),不会影响另一个进程。
返回值:
- 返回0,代表子进程。
- 返回非零,代表是父进程。
三、实现步骤
使用多进程方案,来一个连接请求则克隆一个子进程。
(1)创建socket。
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));return SOCKET_CREATE_FAILED;
}
(2)绑定地址。
struct sockaddr_in server;
memset(&server,0,sizeof(server));server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){printf("errno = %d, %s\n",errno,strerror(errno));close(listenfd);return SOCKET_BIND_FAILED;
}
(3)设置监听。
if(-1==listen(listenfd,BLOCK_SIZE)){printf("errno = %d, %s\n",errno,strerror(errno));close(listenfd);return SOCKET_LISTEN_FAILED;
}
(4)接收连接。
struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));close(listenfd);return SOCKET_ACCEPT_FAILED;
}
(5)为每个连接克隆子进程。
pid_t pid=fork();
if(pid==0)
{routine(clientfd);break;
}
else
{printf("pid = %d\n",pid);
}
(6)在子进程里面接收数据。
char buf[BUFFER_LENGTH]={0};
ret=recv(clientfd,buf,BUFFER_LENGTH,0);
if(ret==0) {printf("connection dropped\n");}
printf("recv --> %s\n",buf);
(7)在子进程里面发送数据。
if(-1==send(clientfd,buf,ret,0))
{printf("errno = %d, %s\n",errno,strerror(errno));
}
(8)关闭文件描述符。
close(listenfd);
四、完整代码
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>#include <errno.h>
#include <string.h>
#include <unistd.h>#include <pthread.h>#define LISTEN_PORT 8888
#define BLOCK_SIZE 10
#define BUFFER_LENGTH 1024enum ERROR_CODE{SOCKET_CREATE_FAILED=-1,SOCKET_BIND_FAILED=-2,SOCKET_LISTEN_FAILED=-3,SOCKET_ACCEPT_FAILED=-4
};void routine(int clientfd)
{while(1){// 5.char buf[BUFFER_LENGTH]={0};int ret=recv(clientfd,buf,BUFFER_LENGTH,0);if(ret==0) {printf("connection dropped\n");break;}printf("fd=%d recv --> %s\n",clientfd,buf);send(clientfd,buf,ret,0);}close(clientfd);
}int main(int argc,char **argv)
{// 1.int listenfd=socket(AF_INET,SOCK_STREAM,0);if(listenfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));return SOCKET_CREATE_FAILED;}// 2.struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family=AF_INET;server.sin_addr.s_addr=htonl(INADDR_ANY);server.sin_port=htons(LISTEN_PORT);if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){printf("errno = %d, %s\n",errno,strerror(errno));close(listenfd);return SOCKET_BIND_FAILED;}// 3.if(-1==listen(listenfd,BLOCK_SIZE)){printf("errno = %d, %s\n",errno,strerror(errno));close(listenfd);return SOCKET_LISTEN_FAILED;}printf("listen port: %d\n",LISTEN_PORT);struct sockaddr_in client;socklen_t len=sizeof(client);int clientfd=-1;while(1){// 4.memset(&client,0,sizeof(client));clientfd=accept(listenfd,(struct sockaddr*)&client,&len);if(clientfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));continue;}printf("accept successdul, fd = %d\n",clientfd);pid_t pid=fork();if(pid==0){routine(clientfd);break;}else{printf("pid = %d\n",pid);}}close(listenfd);return 0;
}
编译:
gcc -o server server.c
五、TCP客户端
5.1、自己实现一个TCP客户端
自己实现一个TCP客户端连接TCP服务器的代码:
#include <stdio.h>
#include <sys/socket.h>#include <netinet/in.h>
#include <arpa/inet.h>#include <errno.h>
#include <string.h>#include <unistd.h>
#include <stdlib.h>#define BUFFER_LENGTH 1024enum ERROR_CODE{SOCKET_CREATE_FAILED=-1,SOCKET_CONN_FAILED=-2,SOCKET_LISTEN_FAILED=-3,SOCKET_ACCEPT_FAILED=-4
};int main(int argc,char** argv)
{if(argc<3){printf("Please enter the server IP and port.");return 0;}printf("connect to %s, port=%s\n",argv[1],argv[2]);int connfd=socket(AF_INET,SOCK_STREAM,0);if(connfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));return SOCKET_CREATE_FAILED;}struct sockaddr_in serv;serv.sin_family=AF_INET;serv.sin_addr.s_addr=inet_addr(argv[1]);serv.sin_port=htons(atoi(argv[2]));socklen_t len=sizeof(serv);int rwfd=connect(connfd,(struct sockaddr*)&serv,len);if(rwfd==-1){printf("errno = %d, %s\n",errno,strerror(errno));close(rwfd);return SOCKET_CONN_FAILED;}int ret=1;while(ret>0){char buf[BUFFER_LENGTH]={0};printf("Please enter the string to send:\n");scanf("%s",buf);send(connfd,buf,strlen(buf),0);memset(buf,0,BUFFER_LENGTH);printf("recv:\n");ret=recv(connfd,buf,BUFFER_LENGTH,0);printf("%s\n",buf);}close(rwfd);return 0;
}
编译:
gcc -o client client.c
5.2、Windows下可以使用NetAssist的网络助手工具
下载地址:http://old.tpyboard.com/downloads/NetAssist.exe
小结
这里基于上一个章节的内容进行了升级,可以接受多个客户端同时连接,并为每个连接克隆一个子进程进行通信。
但是,使用多进程会非常消耗资源,特别是高并发的时候,会是系统内存爆满,开销巨大。那么有没有办法在一个线程中就可以完成高并发通信呢?答案是可以的,下一章节将介绍使用select实现一个线程完成多个连接的通信,也就是IO多路复用技术。